Fix conflixts

This commit is contained in:
neslihanturan 2019-06-10 18:37:05 +03:00
commit 2e1cb07a51
26 changed files with 426 additions and 533 deletions

View file

@ -5,22 +5,10 @@ import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.ServiceConnection; import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.database.Cursor; import android.database.Cursor;
import android.database.DataSetObserver; import android.database.DataSetObserver;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.CursorLoader;
import androidx.loader.content.Loader;
import androidx.cursoradapter.widget.CursorAdapter;
import androidx.appcompat.app.AlertDialog;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -28,6 +16,16 @@ import android.widget.Adapter;
import android.widget.CheckBox; import android.widget.CheckBox;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.cursoradapter.widget.CursorAdapter;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.CursorLoader;
import androidx.loader.content.Loader;
import java.util.ArrayList; import java.util.ArrayList;
import javax.inject.Inject; import javax.inject.Inject;
@ -57,6 +55,7 @@ import fr.free.nrw.commons.settings.Prefs;
import fr.free.nrw.commons.upload.UploadService; import fr.free.nrw.commons.upload.UploadService;
import fr.free.nrw.commons.utils.ConfigUtils; import fr.free.nrw.commons.utils.ConfigUtils;
import fr.free.nrw.commons.utils.DialogUtil; import fr.free.nrw.commons.utils.DialogUtil;
import fr.free.nrw.commons.utils.PermissionUtils;
import fr.free.nrw.commons.utils.ViewUtil; import fr.free.nrw.commons.utils.ViewUtil;
import io.reactivex.Observable; import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
@ -66,7 +65,7 @@ import timber.log.Timber;
import static fr.free.nrw.commons.contributions.ContributionDao.Table.ALL_FIELDS; import static fr.free.nrw.commons.contributions.ContributionDao.Table.ALL_FIELDS;
import static fr.free.nrw.commons.contributions.ContributionsContentProvider.BASE_URI; import static fr.free.nrw.commons.contributions.ContributionsContentProvider.BASE_URI;
import static fr.free.nrw.commons.location.LocationServiceManager.LOCATION_REQUEST; import static fr.free.nrw.commons.contributions.MainActivity.CONTRIBUTIONS_TAB_POSITION;
import static fr.free.nrw.commons.settings.Prefs.UPLOADS_SHOWING; import static fr.free.nrw.commons.settings.Prefs.UPLOADS_SHOWING;
import static fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween; import static fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween;
@ -351,35 +350,6 @@ public class ContributionsFragment
} }
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
Timber.d("onRequestPermissionsResult");
switch (requestCode) {
case LOCATION_REQUEST: {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Timber.d("Location permission granted, refreshing view");
// No need to display permission request button anymore
locationManager.registerLocationManager(getActivity());
} else {
if (store.getBoolean("displayLocationPermissionForCardView", true)) {
// Still ask for permission
DialogUtil.showAlertDialog(getActivity(),
getString(R.string.nearby_card_permission_title),
getString(R.string.nearby_card_permission_explanation),
this::displayYouWontSeeNearbyMessage,
this::enableLocationPermission,
checkBoxView,
false);
}
}
}
break;
default:
// This is needed to allow the request codes from the Fragments to be routed appropriately
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
/** /**
* Replace whatever is in the current contributionsFragmentContainer view with * Replace whatever is in the current contributionsFragmentContainer view with
* mediaDetailPagerFragment, and preserve previous state in back stack. * mediaDetailPagerFragment, and preserve previous state in back stack.
@ -496,7 +466,7 @@ public class ContributionsFragment
if (store.getBoolean("displayNearbyCardView", true)) { if (store.getBoolean("displayNearbyCardView", true)) {
checkGPS(); checkPermissionsAndShowNearbyCardView();
if (nearbyNotificationCardView.cardViewVisibilityState == NearbyNotificationCardView.CardViewVisibilityState.READY) { if (nearbyNotificationCardView.cardViewVisibilityState == NearbyNotificationCardView.CardViewVisibilityState.READY) {
nearbyNotificationCardView.setVisibility(View.VISIBLE); nearbyNotificationCardView.setVisibility(View.VISIBLE);
} }
@ -509,77 +479,39 @@ public class ContributionsFragment
fetchCampaigns(); fetchCampaigns();
} }
/** private void checkPermissionsAndShowNearbyCardView() {
* Check GPS to decide displaying request permission button or not. if (PermissionUtils.hasPermission(getActivity(), Manifest.permission.ACCESS_FINE_LOCATION)) {
*/ onLocationPermissionGranted();
private void checkGPS() { } else if (shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION)
if (!locationManager.isProviderEnabled()) { && store.getBoolean("displayLocationPermissionForCardView", true)
Timber.d("GPS is not enabled"); && (((MainActivity) getActivity()).viewPager.getCurrentItem() == CONTRIBUTIONS_TAB_POSITION)) {
nearbyNotificationCardView.permissionType = NearbyNotificationCardView.PermissionType.ENABLE_GPS; nearbyNotificationCardView.permissionType = NearbyNotificationCardView.PermissionType.ENABLE_LOCATION_PERMISSION;
if (store.getBoolean("displayLocationPermissionForCardView", true)) { showNearbyCardPermissionRationale();
DialogUtil.showAlertDialog(getActivity(),
getString(R.string.nearby_card_permission_title),
getString(R.string.nearby_card_permission_explanation),
this::displayYouWontSeeNearbyMessage,
this::enableGPS,
checkBoxView,
false);
}
} else {
Timber.d("GPS is enabled");
checkLocationPermission();
} }
} }
private void checkLocationPermission() { private void requestLocationPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { PermissionUtils.checkPermissionsAndPerformAction(getActivity(),
if (locationManager.isLocationPermissionGranted(requireContext())) { Manifest.permission.ACCESS_FINE_LOCATION,
nearbyNotificationCardView.permissionType = NearbyNotificationCardView.PermissionType.NO_PERMISSION_NEEDED; this::onLocationPermissionGranted,
locationManager.registerLocationManager(getActivity()); this::displayYouWontSeeNearbyMessage,
} else { -1,
nearbyNotificationCardView.permissionType = NearbyNotificationCardView.PermissionType.ENABLE_LOCATION_PERMISSION; -1);
// If user didn't selected Don't ask again
if (shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION)
&& store.getBoolean("displayLocationPermissionForCardView", true)) {
DialogUtil.showAlertDialog(getActivity(),
getString(R.string.nearby_card_permission_title),
getString(R.string.nearby_card_permission_explanation),
this::displayYouWontSeeNearbyMessage,
this::enableLocationPermission,
checkBoxView,
false);
}
}
} else {
// If device is under Marshmallow, we already checked for GPS
nearbyNotificationCardView.permissionType = NearbyNotificationCardView.PermissionType.NO_PERMISSION_NEEDED;
locationManager.registerLocationManager(getActivity());
}
} }
private void enableLocationPermission() { private void onLocationPermissionGranted() {
if (!getActivity().isFinishing()) { nearbyNotificationCardView.permissionType = NearbyNotificationCardView.PermissionType.NO_PERMISSION_NEEDED;
((MainActivity) getActivity()).locationManager.requestPermissions(getActivity()); locationManager.registerLocationManager(getActivity());
}
} }
private void enableGPS() { private void showNearbyCardPermissionRationale() {
new AlertDialog.Builder(getActivity()) DialogUtil.showAlertDialog(getActivity(),
.setMessage(R.string.gps_disabled) getString(R.string.nearby_card_permission_title),
.setCancelable(false) getString(R.string.nearby_card_permission_explanation),
.setPositiveButton(R.string.enable_gps, this::displayYouWontSeeNearbyMessage,
(dialog, id) -> { this::requestLocationPermission,
Intent callGPSSettingIntent = new Intent( checkBoxView,
android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS); false);
Timber.d("Loaded settings page");
((MainActivity) getActivity()).startActivityForResult(callGPSSettingIntent, 1);
})
.setNegativeButton(R.string.menu_cancel_upload, (dialog, id) -> {
dialog.cancel();
displayYouWontSeeNearbyMessage();
})
.create()
.show();
} }
private void displayYouWontSeeNearbyMessage() { private void displayYouWontSeeNearbyMessage() {
@ -589,7 +521,6 @@ public class ContributionsFragment
private void updateClosestNearbyCardViewInfo() { private void updateClosestNearbyCardViewInfo() {
curLatLng = locationManager.getLastLocation(); curLatLng = locationManager.getLastLocation();
compositeDisposable.add(Observable.fromCallable(() -> nearbyController compositeDisposable.add(Observable.fromCallable(() -> nearbyController
.loadAttractionsFromLocation(curLatLng, curLatLng, true, false)) // thanks to boolean, it will only return closest result .loadAttractionsFromLocation(curLatLng, curLatLng, true, false)) // thanks to boolean, it will only return closest result
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())

View file

@ -46,7 +46,6 @@ import io.reactivex.schedulers.Schedulers;
import timber.log.Timber; import timber.log.Timber;
import static android.content.ContentResolver.requestSync; import static android.content.ContentResolver.requestSync;
import static fr.free.nrw.commons.location.LocationServiceManager.LOCATION_REQUEST;
public class MainActivity extends AuthenticatedActivity implements FragmentManager.OnBackStackChangedListener { public class MainActivity extends AuthenticatedActivity implements FragmentManager.OnBackStackChangedListener {
@ -69,8 +68,8 @@ public class MainActivity extends AuthenticatedActivity implements FragmentManag
public boolean isAuthCookieAcquired = false; public boolean isAuthCookieAcquired = false;
public ContributionsActivityPagerAdapter contributionsActivityPagerAdapter; public ContributionsActivityPagerAdapter contributionsActivityPagerAdapter;
public final int CONTRIBUTIONS_TAB_POSITION = 0; public static final int CONTRIBUTIONS_TAB_POSITION = 0;
public final int NEARBY_TAB_POSITION = 1; public static final int NEARBY_TAB_POSITION = 1;
public boolean isContributionsFragmentVisible = true; // False means nearby fragment is visible public boolean isContributionsFragmentVisible = true; // False means nearby fragment is visible
private Menu menu; private Menu menu;
@ -362,12 +361,6 @@ public class MainActivity extends AuthenticatedActivity implements FragmentManag
} }
} }
private boolean deviceHasCamera() {
PackageManager pm = getPackageManager();
return pm.hasSystemFeature(PackageManager.FEATURE_CAMERA) ||
pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT);
}
public class ContributionsActivityPagerAdapter extends FragmentPagerAdapter { public class ContributionsActivityPagerAdapter extends FragmentPagerAdapter {
FragmentManager fragmentManager; FragmentManager fragmentManager;
private boolean isContributionsListFragment = true; // to know what to put in first tab, Contributions of Media Details private boolean isContributionsListFragment = true; // to know what to put in first tab, Contributions of Media Details
@ -451,15 +444,6 @@ public class MainActivity extends AuthenticatedActivity implements FragmentManag
return "android:switcher:" + viewId + ":" + index; return "android:switcher:" + viewId + ":" + index;
} }
/**
* In first tab we can have ContributionsFragment or Media details fragment. This method
* is responsible to update related boolean
* @param isContributionsListFragment true when contribution fragment should be visible, false
* means user clicked to MediaDetails
*/
private void updateContributionFragmentTabContent(boolean isContributionsListFragment) {
this.isContributionsListFragment = isContributionsListFragment;
}
} }
@Override @Override
@ -469,26 +453,6 @@ public class MainActivity extends AuthenticatedActivity implements FragmentManag
controller.handleActivityResult(this, requestCode, resultCode, data); controller.handleActivityResult(this, requestCode, resultCode, data);
} }
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
if (requestCode == LOCATION_REQUEST) {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Timber.d("Location permission given");
((ContributionsFragment)contributionsActivityPagerAdapter
.getItem(0)).locationManager.registerLocationManager(this);
} else {
// If nearby fragment is visible and location permission is not given, send user back to contrib fragment
if (!isContributionsFragmentVisible) {
viewPager.setCurrentItem(CONTRIBUTIONS_TAB_POSITION);
// TODO: If contrib fragment is visible and location permission is not given, display permission request button
}
}
}
}
@Override
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();
setNotificationCount(); setNotificationCount();

View file

@ -3,11 +3,12 @@ package fr.free.nrw.commons.di;
import android.app.Activity; import android.app.Activity;
import android.content.ContentProviderClient; import android.content.ContentProviderClient;
import android.content.Context; import android.content.Context;
import androidx.collection.LruCache;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
import com.google.gson.Gson; import com.google.gson.Gson;
import org.wikipedia.dataclient.WikiSite;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -16,6 +17,7 @@ import java.util.Map;
import javax.inject.Named; import javax.inject.Named;
import javax.inject.Singleton; import javax.inject.Singleton;
import androidx.collection.LruCache;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
import fr.free.nrw.commons.BuildConfig; import fr.free.nrw.commons.BuildConfig;
@ -24,7 +26,6 @@ import fr.free.nrw.commons.auth.AccountUtil;
import fr.free.nrw.commons.auth.SessionManager; import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.data.DBOpenHelper; import fr.free.nrw.commons.data.DBOpenHelper;
import fr.free.nrw.commons.kvstore.JsonKvStore; import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.location.LocationServiceManager; import fr.free.nrw.commons.location.LocationServiceManager;
import fr.free.nrw.commons.settings.Prefs; import fr.free.nrw.commons.settings.Prefs;
import fr.free.nrw.commons.upload.UploadController; import fr.free.nrw.commons.upload.UploadController;

View file

@ -4,6 +4,8 @@ import android.content.Context;
import com.google.gson.Gson; import com.google.gson.Gson;
import org.wikipedia.dataclient.ServiceFactory;
import org.wikipedia.dataclient.WikiSite;
import org.wikipedia.json.GsonUtil; import org.wikipedia.json.GsonUtil;
import java.io.File; import java.io.File;
@ -20,6 +22,7 @@ import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.mwapi.ApacheHttpClientMediaWikiApi; import fr.free.nrw.commons.mwapi.ApacheHttpClientMediaWikiApi;
import fr.free.nrw.commons.mwapi.MediaWikiApi; import fr.free.nrw.commons.mwapi.MediaWikiApi;
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient; import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient;
import fr.free.nrw.commons.review.ReviewInterface;
import okhttp3.Cache; import okhttp3.Cache;
import okhttp3.HttpUrl; import okhttp3.HttpUrl;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
@ -108,4 +111,16 @@ public class NetworkingModule {
return GsonUtil.getDefaultGson(); return GsonUtil.getDefaultGson();
} }
@Provides
@Singleton
@Named("commons-wikisite")
public WikiSite provideCommonsWikiSite() {
return new WikiSite(BuildConfig.COMMONS_URL);
}
@Provides
@Singleton
public ReviewInterface provideReviewInterface(@Named("commons-wikisite") WikiSite commonsWikiSite) {
return ServiceFactory.get(commonsWikiSite, BuildConfig.COMMONS_URL, ReviewInterface.class);
}
} }

View file

@ -1,20 +1,13 @@
package fr.free.nrw.commons.location; package fr.free.nrw.commons.location;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.pm.PackageManager;
import android.location.Location; import android.location.Location;
import android.location.LocationListener; import android.location.LocationListener;
import android.location.LocationManager; import android.location.LocationManager;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log; import android.util.Log;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@ -45,78 +38,6 @@ public class LocationServiceManager implements LocationListener {
this.locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); this.locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
} }
/**
* Returns the current status of the location provider.
*
* @return true if the location provider is enabled
*/
public boolean isProviderEnabled() {
return (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) || locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER));
}
/**
* Returns whether the location permission is granted.
* @return true if the location permission is granted
*/
public boolean isLocationPermissionGranted(@NonNull Context context) {
return ContextCompat.checkSelfPermission(context,
Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED;
}
/**
* Requests the location permission to be granted.
*
* @param activity the activity
*/
public void requestPermissions(Activity activity) {
if (activity.isFinishing()) {
return;
}
ActivityCompat.requestPermissions(activity,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
LOCATION_REQUEST);
}
/**
* The permission explanation dialog box is now displayed just once for a particular activity. We are subscribing
* to updates from multiple providers so its important to show the dialog just once. Otherwise it will be displayed
* once for every provider, which in our case currently is 2.
* @param activity
* @return
*/
public boolean isPermissionExplanationRequired(Activity activity) {
if (activity.isFinishing()) {
return false;
}
boolean showRequestPermissionRationale = ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.ACCESS_FINE_LOCATION);
if (showRequestPermissionRationale && !locationExplanationDisplayed.contains(activity)) {
locationExplanationDisplayed.add(activity);
return true;
}
return false;
}
/**
* Gets the last known location in cases where there wasn't time to register a listener
* (e.g. when Location permission just granted)
* @return last known LatLng
*/
@SuppressLint("MissingPermission")
public LatLng getLKL(@NonNull Context context) {
if (isLocationPermissionGranted(context)) {
Location lastKL = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
if (lastKL == null) {
lastKL = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
}
if (lastKL == null) {
return null;
}
return LatLng.from(lastKL);
} else {
return null;
}
}
public LatLng getLastLocation() { public LatLng getLastLocation() {
if (lastLocation == null) { if (lastLocation == null) {
return null; return null;

View file

@ -1,6 +1,7 @@
package fr.free.nrw.commons.mwapi; package fr.free.nrw.commons.mwapi;
import android.text.TextUtils; import android.text.TextUtils;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@ -10,8 +11,6 @@ import com.google.gson.reflect.TypeToken;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.wikipedia.dataclient.mwapi.MwQueryPage; import org.wikipedia.dataclient.mwapi.MwQueryPage;
import org.wikipedia.dataclient.mwapi.MwQueryResponse; import org.wikipedia.dataclient.mwapi.MwQueryResponse;
import org.wikipedia.dataclient.mwapi.RecentChange;
import org.wikipedia.util.DateUtil;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Type; import java.lang.reflect.Type;
@ -20,7 +19,6 @@ import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Random;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
@ -86,7 +84,7 @@ public class OkHttpJsonApiClient {
public Single<Integer> getUploadCount(String userName) { public Single<Integer> getUploadCount(String userName) {
HttpUrl.Builder urlBuilder = wikiMediaToolforgeUrl.newBuilder(); HttpUrl.Builder urlBuilder = wikiMediaToolforgeUrl.newBuilder();
urlBuilder urlBuilder
.addPathSegments("/uploadsbyuser.py") .addPathSegments("uploadsbyuser.py")
.addQueryParameter("user", userName); .addQueryParameter("user", userName);
if (ConfigUtils.isBetaFlavour()) { if (ConfigUtils.isBetaFlavour()) {
@ -112,7 +110,7 @@ public class OkHttpJsonApiClient {
public Single<Integer> getWikidataEdits(String userName) { public Single<Integer> getWikidataEdits(String userName) {
HttpUrl.Builder urlBuilder = wikiMediaToolforgeUrl.newBuilder(); HttpUrl.Builder urlBuilder = wikiMediaToolforgeUrl.newBuilder();
urlBuilder urlBuilder
.addPathSegments("/wikidataedits.py") .addPathSegments("wikidataedits.py")
.addQueryParameter("user", userName); .addQueryParameter("user", userName);
if (ConfigUtils.isBetaFlavour()) { if (ConfigUtils.isBetaFlavour()) {
@ -132,7 +130,9 @@ public class OkHttpJsonApiClient {
return 0; return 0;
} }
GetWikidataEditCountResponse countResponse = gson.fromJson(json, GetWikidataEditCountResponse.class); GetWikidataEditCountResponse countResponse = gson.fromJson(json, GetWikidataEditCountResponse.class);
return countResponse.getWikidataEditCount(); if (null != countResponse) {
return countResponse.getWikidataEditCount();
}
} }
return 0; return 0;
}); });
@ -421,80 +421,4 @@ public class OkHttpJsonApiClient {
private Map<String, String> getContinueValues(String keyword) { private Map<String, String> getContinueValues(String keyword) {
return defaultKvStore.getJson("query_continue_" + keyword, mapType); return defaultKvStore.getJson("query_continue_" + keyword, mapType);
} }
/**
* Returns recent changes on commons
*
* @return list of recent changes made
*/
@Nullable
public Single<List<RecentChange>> getRecentFileChanges() {
final int RANDOM_SECONDS = 60 * 60 * 24 * 30;
final String FILE_NAMESPACE = "6";
Random r = new Random();
Date now = new Date();
Date startDate = new Date(now.getTime() - r.nextInt(RANDOM_SECONDS) * 1000L);
String rcStart = DateUtil.iso8601DateFormat(startDate);
HttpUrl.Builder urlBuilder = HttpUrl
.parse(commonsBaseUrl)
.newBuilder()
.addQueryParameter("action", "query")
.addQueryParameter("format", "json")
.addQueryParameter("formatversion", "2")
.addQueryParameter("list", "recentchanges")
.addQueryParameter("rcstart", rcStart)
.addQueryParameter("rcnamespace", FILE_NAMESPACE)
.addQueryParameter("rcprop", "title|ids")
.addQueryParameter("rctype", "new|log")
.addQueryParameter("rctoponly", "1");
Request request = new Request.Builder()
.url(urlBuilder.build())
.build();
return Single.fromCallable(() -> {
Response response = okHttpClient.newCall(request).execute();
if (response.body() != null && response.isSuccessful()) {
String json = response.body().string();
MwQueryResponse mwQueryPage = gson.fromJson(json, MwQueryResponse.class);
return mwQueryPage.query().getRecentChanges();
}
return new ArrayList<>();
});
}
/**
* Returns the first revision of the file
*
* @return Revision object
*/
@Nullable
public Single<MwQueryPage.Revision> getFirstRevisionOfFile(String filename) {
HttpUrl.Builder urlBuilder = HttpUrl
.parse(commonsBaseUrl)
.newBuilder()
.addQueryParameter("action", "query")
.addQueryParameter("format", "json")
.addQueryParameter("formatversion", "2")
.addQueryParameter("prop", "revisions")
.addQueryParameter("rvprop", "timestamp|ids|user")
.addQueryParameter("titles", filename)
.addQueryParameter("rvdir", "newer")
.addQueryParameter("rvlimit", "1");
Request request = new Request.Builder()
.url(urlBuilder.build())
.build();
return Single.fromCallable(() -> {
Response response = okHttpClient.newCall(request).execute();
if (response.body() != null && response.isSuccessful()) {
String json = response.body().string();
MwQueryResponse mwQueryPage = gson.fromJson(json, MwQueryResponse.class);
return mwQueryPage.query().firstPage().revisions().get(0);
}
return null;
});
}
} }

View file

@ -1,20 +1,11 @@
package fr.free.nrw.commons.nearby; package fr.free.nrw.commons.nearby;
import android.Manifest;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.constraintlayout.widget.ConstraintLayout;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
import com.google.android.material.snackbar.Snackbar;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.appcompat.app.AlertDialog;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -22,12 +13,19 @@ import android.widget.FrameLayout;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
import com.google.android.material.snackbar.Snackbar;
import com.google.gson.Gson; import com.google.gson.Gson;
import java.util.List; import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import butterknife.BindView; import butterknife.BindView;
import butterknife.ButterKnife; import butterknife.ButterKnife;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
@ -38,6 +36,7 @@ import fr.free.nrw.commons.location.LocationServiceManager;
import fr.free.nrw.commons.location.LocationUpdateListener; import fr.free.nrw.commons.location.LocationUpdateListener;
import fr.free.nrw.commons.utils.FragmentUtils; import fr.free.nrw.commons.utils.FragmentUtils;
import fr.free.nrw.commons.utils.NetworkUtils; import fr.free.nrw.commons.utils.NetworkUtils;
import fr.free.nrw.commons.utils.PermissionUtils;
import fr.free.nrw.commons.utils.ViewUtil; import fr.free.nrw.commons.utils.ViewUtil;
import fr.free.nrw.commons.wikidata.WikidataEditListener; import fr.free.nrw.commons.wikidata.WikidataEditListener;
import io.reactivex.Observable; import io.reactivex.Observable;
@ -45,6 +44,8 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers; import io.reactivex.schedulers.Schedulers;
import timber.log.Timber; import timber.log.Timber;
import static fr.free.nrw.commons.contributions.MainActivity.CONTRIBUTIONS_TAB_POSITION;
import static fr.free.nrw.commons.contributions.MainActivity.NEARBY_TAB_POSITION;
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED; 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.LOCATION_SLIGHTLY_CHANGED;
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.MAP_UPDATED; import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.MAP_UPDATED;
@ -193,8 +194,8 @@ public class NearbyFragment extends CommonsDaggerSupportFragment
bottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() { bottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
@Override @Override
public void onStateChanged(View bottomSheet, int newState) { public void onStateChanged(View bottomSheet, int unusedNewState) {
prepareViewsForSheetPosition(newState); prepareViewsForSheetPosition();
} }
@Override @Override
@ -210,9 +211,8 @@ public class NearbyFragment extends CommonsDaggerSupportFragment
/** /**
* Sets camera position, zoom level according to sheet positions * Sets camera position, zoom level according to sheet positions
* @param bottomSheetState expanded, collapsed or hidden
*/ */
public void prepareViewsForSheetPosition(int bottomSheetState) { private void prepareViewsForSheetPosition() {
// TODO // TODO
} }
@ -246,7 +246,7 @@ public class NearbyFragment extends CommonsDaggerSupportFragment
* *
* @param locationChangeType defines if location changed significantly or slightly * @param locationChangeType defines if location changed significantly or slightly
*/ */
public void refreshView(LocationServiceManager.LocationChangeType locationChangeType) { private void refreshView(LocationServiceManager.LocationChangeType locationChangeType) {
Timber.d("Refreshing nearby places"); Timber.d("Refreshing nearby places");
if (lockNearbyView) { if (lockNearbyView) {
return; return;
@ -324,7 +324,7 @@ public class NearbyFragment extends CommonsDaggerSupportFragment
* button. It populates places for custom location. * button. It populates places for custom location.
* @param customLatLng Custom area which we will search around * @param customLatLng Custom area which we will search around
*/ */
public void refreshViewForCustomLocation(LatLng customLatLng, boolean refreshForCurrentLocation) { void refreshViewForCustomLocation(LatLng customLatLng, boolean refreshForCurrentLocation) {
if (customLatLng == null) { if (customLatLng == null) {
// If null, return // If null, return
return; return;
@ -568,130 +568,8 @@ public class NearbyFragment extends CommonsDaggerSupportFragment
* This method first checks if the location permissions has been granted and then register the location manager for updates. * This method first checks if the location permissions has been granted and then register the location manager for updates.
*/ */
private void registerLocationUpdates() { private void registerLocationUpdates() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (locationManager.isLocationPermissionGranted(requireContext())) {
locationManager.registerLocationManager(getActivity());
} else {
// Should we show an explanation?
if (locationManager.isPermissionExplanationRequired(getActivity())) {
new AlertDialog.Builder(getActivity())
.setMessage(getString(R.string.location_permission_rationale_nearby))
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
requestLocationPermissions();
dialog.dismiss();
})
.setNegativeButton(android.R.string.cancel, (dialog, id) -> {
showLocationPermissionDeniedErrorDialog();
dialog.cancel();
})
.create()
.show();
} else { locationManager.registerLocationManager(getActivity());
// No explanation needed, we can request the permission.
requestLocationPermissions();
}
}
} else {
locationManager.registerLocationManager(getActivity());
}
}
/**
* Requests location permission if activity is not null
*/
private void requestLocationPermissions() {
if (!getActivity().isFinishing()) {
locationManager.requestPermissions(getActivity());
}
}
/**
* Will warn user if location is denied
*/
private void showLocationPermissionDeniedErrorDialog() {
new AlertDialog.Builder(getActivity())
.setMessage(R.string.nearby_needs_permissions)
.setCancelable(false)
.setPositiveButton(R.string.give_permission, (dialog, which) -> {
//will ask for the location permission again
checkGps();
})
.setNegativeButton(R.string.cancel, (dialog, which) -> {
//dismiss dialog and send user to contributions tab instead
dialog.cancel();
((MainActivity)getActivity()).viewPager.setCurrentItem(((MainActivity)getActivity()).CONTRIBUTIONS_TAB_POSITION);
})
.create()
.show();
}
/**
* Checks device GPS permission first for all API levels
*/
private void checkGps() {
Timber.d("checking GPS");
if (!locationManager.isProviderEnabled()) {
Timber.d("GPS is not enabled");
new AlertDialog.Builder(getActivity())
.setMessage(R.string.gps_disabled)
.setCancelable(false)
.setPositiveButton(R.string.enable_gps,
(dialog, id) -> {
Intent callGPSSettingIntent = new Intent(
android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS);
Timber.d("Loaded settings page");
startActivityForResult(callGPSSettingIntent, 1);
})
.setNegativeButton(R.string.menu_cancel_upload, (dialog, id) -> {
showLocationPermissionDeniedErrorDialog();
dialog.cancel();
})
.create()
.show();
} else {
Timber.d("GPS is enabled");
checkLocationPermission();
}
}
/**
* This method ideally should be called from inside of CheckGPS method. If device GPS is enabled
* then we need to control app specific permissions for >=M devices. For other devices, enabled
* GPS is enough for nearby, so directly call refresh view.
*/
private void checkLocationPermission() {
Timber.d("Checking location permission");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (locationManager.isLocationPermissionGranted(requireContext())) {
refreshView(LOCATION_SIGNIFICANTLY_CHANGED);
} else {
// Should we show an explanation?
if (locationManager.isPermissionExplanationRequired(getActivity())) {
// Show an explanation to the user *asynchronously* -- don't block
// this thread waiting for the user's response! After the user
// sees the explanation, try again to request the permission.
new AlertDialog.Builder(getActivity())
.setMessage(getString(R.string.location_permission_rationale_nearby))
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
requestLocationPermissions();
dialog.dismiss();
})
.setNegativeButton(android.R.string.cancel, (dialog, id) -> {
showLocationPermissionDeniedErrorDialog();
dialog.cancel();
})
.create()
.show();
} else {
// No explanation needed, we can request the permission.
requestLocationPermissions();
}
}
} else {
refreshView(LOCATION_SIGNIFICANTLY_CHANGED);
}
} }
private void showErrorMessage(String message) { private void showErrorMessage(String message) {
@ -748,8 +626,12 @@ public class NearbyFragment extends CommonsDaggerSupportFragment
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
// Resume the fragment if exist // Resume the fragment if exist
resumeFragment(); if (((MainActivity) getActivity()).viewPager.getCurrentItem() == NEARBY_TAB_POSITION) {
checkPermissionsAndPerformAction(this::resumeFragment);
} else {
resumeFragment();
} }
}
/** /**
* Perform nearby operations on nearby tab selected * Perform nearby operations on nearby tab selected
@ -758,8 +640,16 @@ public class NearbyFragment extends CommonsDaggerSupportFragment
public void onTabSelected(boolean onOrientationChanged) { public void onTabSelected(boolean onOrientationChanged) {
Timber.d("On nearby tab selected"); Timber.d("On nearby tab selected");
this.onOrientationChanged = onOrientationChanged; this.onOrientationChanged = onOrientationChanged;
performNearbyOperations(); checkPermissionsAndPerformAction(this::performNearbyOperations);
}
private void checkPermissionsAndPerformAction(Runnable runnable) {
PermissionUtils.checkPermissionsAndPerformAction(getActivity(),
Manifest.permission.ACCESS_FINE_LOCATION,
runnable,
() -> ((MainActivity) getActivity()).viewPager.setCurrentItem(CONTRIBUTIONS_TAB_POSITION),
R.string.location_permission_title,
R.string.location_permission_rationale_nearby);
} }
/** /**
@ -769,8 +659,8 @@ public class NearbyFragment extends CommonsDaggerSupportFragment
locationManager.addLocationListener(this); locationManager.addLocationListener(this);
registerLocationUpdates(); registerLocationUpdates();
lockNearbyView = false; lockNearbyView = false;
checkGps();
addNetworkBroadcastReceiver(); addNetworkBroadcastReceiver();
refreshView(LOCATION_SIGNIFICANTLY_CHANGED);
} }
@Override @Override

View file

@ -298,7 +298,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
@Override @Override
public void requestLocationPermissions(LocationServiceManager locationServiceManager) { public void requestLocationPermissions(LocationServiceManager locationServiceManager) {
if (!getActivity().isFinishing()) { if (!getActivity().isFinishing()) {
locationServiceManager.requestPermissions(getActivity()); locationServiceManager.requestPermissions();
} }
} }

View file

@ -12,13 +12,20 @@ import android.view.View;
import android.widget.Button; import android.widget.Button;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.TextView; import android.widget.TextView;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import androidx.drawerlayout.widget.DrawerLayout; import androidx.drawerlayout.widget.DrawerLayout;
import butterknife.BindView;
import butterknife.ButterKnife;
import com.facebook.drawee.view.SimpleDraweeView; import com.facebook.drawee.view.SimpleDraweeView;
import com.google.android.material.navigation.NavigationView; import com.google.android.material.navigation.NavigationView;
import com.viewpagerindicator.CirclePageIndicator; import com.viewpagerindicator.CirclePageIndicator;
import java.util.ArrayList;
import javax.inject.Inject;
import butterknife.BindView;
import butterknife.ButterKnife;
import fr.free.nrw.commons.Media; import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.auth.AuthenticatedActivity; import fr.free.nrw.commons.auth.AuthenticatedActivity;
@ -29,8 +36,6 @@ import fr.free.nrw.commons.utils.ViewUtil;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable; import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers; import io.reactivex.schedulers.Schedulers;
import java.util.ArrayList;
import javax.inject.Inject;
public class ReviewActivity extends AuthenticatedActivity { public class ReviewActivity extends AuthenticatedActivity {
@ -139,7 +144,11 @@ public class ReviewActivity extends AuthenticatedActivity {
compositeDisposable.add(reviewHelper.getRandomMedia() compositeDisposable.add(reviewHelper.getRandomMedia()
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(this::updateImage)); .subscribe(media -> {
if (media != null) {
updateImage(media);
}
}));
return true; return true;
} }

View file

@ -1,39 +1,63 @@
package fr.free.nrw.commons.review; package fr.free.nrw.commons.review;
import org.apache.commons.lang3.StringUtils;
import org.wikipedia.dataclient.mwapi.MwQueryPage; import org.wikipedia.dataclient.mwapi.MwQueryPage;
import org.wikipedia.dataclient.mwapi.RecentChange; import org.wikipedia.dataclient.mwapi.RecentChange;
import org.wikipedia.util.DateUtil;
import java.util.List; import java.util.Date;
import java.util.Random; import java.util.Random;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
import androidx.annotation.Nullable;
import androidx.core.util.Pair;
import fr.free.nrw.commons.Media; import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.mwapi.MediaWikiApi; import fr.free.nrw.commons.mwapi.MediaWikiApi;
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient; import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient;
import io.reactivex.Observable;
import io.reactivex.Single; import io.reactivex.Single;
@Singleton @Singleton
public class ReviewHelper { public class ReviewHelper {
private static final int MAX_RANDOM_TRIES = 5;
private static final String[] imageExtensions = new String[]{".jpg", ".jpeg", ".png"}; private static final String[] imageExtensions = new String[]{".jpg", ".jpeg", ".png"};
private final OkHttpJsonApiClient okHttpJsonApiClient; private final OkHttpJsonApiClient okHttpJsonApiClient;
private final MediaWikiApi mediaWikiApi; private final MediaWikiApi mediaWikiApi;
private final ReviewInterface reviewInterface;
@Inject @Inject
public ReviewHelper(OkHttpJsonApiClient okHttpJsonApiClient, MediaWikiApi mediaWikiApi) { public ReviewHelper(OkHttpJsonApiClient okHttpJsonApiClient,
MediaWikiApi mediaWikiApi,
ReviewInterface reviewInterface) {
this.okHttpJsonApiClient = okHttpJsonApiClient; this.okHttpJsonApiClient = okHttpJsonApiClient;
this.mediaWikiApi = mediaWikiApi; this.mediaWikiApi = mediaWikiApi;
this.reviewInterface = reviewInterface;
} }
Single<Media> getRandomMedia() { /**
return getRandomFileChange() * Fetches recent changes from MediaWiki API
.flatMap(fileName -> okHttpJsonApiClient.getMedia(fileName, false)); * Calls the API to get 10 changes in the last 1 hour
* Earlier we were getting changes for the last 30 days but as the API returns just 10 results
* its best to fetch for just last 1 hour.
* @return
*/
private Observable<RecentChange> getRecentChanges() {
final int RANDOM_SECONDS = 60 * 60;
Random r = new Random();
Date now = new Date();
Date startDate = new Date(now.getTime() - r.nextInt(RANDOM_SECONDS) * 1000L);
String rcStart = DateUtil.iso8601DateFormat(startDate);
return reviewInterface.getRecentChanges(rcStart)
.map(mwQueryResponse -> mwQueryResponse.query().getRecentChanges())
.map(recentChanges -> {
//Collections.shuffle(recentChanges);
return recentChanges;
})
.flatMapIterable(changes -> changes)
.filter(recentChange -> isChangeReviewable(recentChange));
} }
/** /**
@ -43,57 +67,62 @@ public class ReviewHelper {
* - Checks if the file is nominated for deletion * - Checks if the file is nominated for deletion
* - Retries upto 5 times for getting a file which is not nominated for deletion * - Retries upto 5 times for getting a file which is not nominated for deletion
* *
* @return Random file change
*/
public Single<Media> getRandomMedia() {
return getRecentChanges()
.flatMapSingle(change -> getRandomMediaFromRecentChange(change))
.onExceptionResumeNext(Observable.just(new Media("")))
.filter(media -> !StringUtils.isBlank(media.getFilename()))
.firstOrError();
}
/**
* Returns a proper Media object if the file is not already nominated for deletion
* Else it returns an empty Media object
* @param recentChange
* @return * @return
*/ */
private Single<String> getRandomFileChange() { private Single<Media> getRandomMediaFromRecentChange(RecentChange recentChange) {
return okHttpJsonApiClient.getRecentFileChanges() return Single.just(recentChange)
.map(this::findImageInRecentChanges) .flatMap(change -> mediaWikiApi.pageExists("Commons:Deletion_requests/" + change.getTitle()))
.flatMap(title -> mediaWikiApi.pageExists("Commons:Deletion_requests/" + title) .flatMap(isDeleted -> {
.map(pageExists -> new Pair<>(title, pageExists))) if (isDeleted) {
.map((Pair<String, Boolean> pair) -> { return Single.just(new Media(""));
if (!pair.second) {
return pair.first;
} }
throw new Exception("Already nominated for deletion"); return okHttpJsonApiClient.getMedia(recentChange.getTitle(), false);
}).retry(MAX_RANDOM_TRIES); });
} }
Single<MwQueryPage.Revision> getFirstRevisionOfFile(String fileName) { /**
return okHttpJsonApiClient.getFirstRevisionOfFile(fileName); * Gets the first revision of the file from filename
* @param filename
* @return
*/
Observable<MwQueryPage.Revision> getFirstRevisionOfFile(String filename) {
return reviewInterface.getFirstRevisionOfFile(filename)
.map(response -> response.query().firstPage().revisions().get(0));
} }
@Nullable /**
private String findImageInRecentChanges(List<RecentChange> recentChanges) { * Checks if the change is reviewable or not.
String imageTitle; * - checks the type and revisionId of the change
Random r = new Random(); * - checks supported image extensions
int count = recentChanges.size(); * @param recentChange
// Build a range array * @return
int[] randomIndexes = new int[count]; */
for (int i = 0; i < count; i++) { private boolean isChangeReviewable(RecentChange recentChange) {
randomIndexes[i] = i; if (recentChange.getType().equals("log") && !(recentChange.getOldRevisionId() == 0)) {
return false;
} }
// Then shuffle it
for (int i = 0; i < count; i++) {
int swapIndex = r.nextInt(count);
int temp = randomIndexes[i];
randomIndexes[i] = randomIndexes[swapIndex];
randomIndexes[swapIndex] = temp;
}
for (int i = 0; i < count; i++) {
int randomIndex = randomIndexes[i];
RecentChange recentChange = recentChanges.get(randomIndex);
if (recentChange.getType().equals("log") && !(recentChange.getOldRevisionId() == 0)) {
// For log entries, we only want ones where old_revid is zero, indicating a new file
continue;
}
imageTitle = recentChange.getTitle();
for (String imageExtension : imageExtensions) { for (String extension : imageExtensions) {
if (imageTitle.toLowerCase().endsWith(imageExtension)) { if (recentChange.getTitle().endsWith(extension)) {
return imageTitle; return true;
}
} }
} }
return null;
return false;
} }
} }

View file

@ -0,0 +1,18 @@
package fr.free.nrw.commons.review;
import org.wikipedia.dataclient.mwapi.MwQueryResponse;
import io.reactivex.Observable;
import retrofit2.http.GET;
import retrofit2.http.Query;
/**
* Interface class for peer review calls
*/
public interface ReviewInterface {
@GET("w/api.php?action=query&format=json&formatversion=2&list=recentchanges&rcprop=title|ids&rctype=new|log&rctoponly=1&rcnamespace=6")
Observable<MwQueryResponse> getRecentChanges(@Query("rcstart") String rcStart);
@GET("w/api.php?action=query&format=json&formatversion=2&prop=revisions&rvprop=timestamp|ids|user&rvdir=newer&rvlimit=1")
Observable<MwQueryResponse> getFirstRevisionOfFile(@Query("titles") String titles);
}

View file

@ -5,17 +5,17 @@ import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.net.Uri; import android.net.Uri;
import android.provider.Settings; import android.provider.Settings;
import androidx.annotation.StringRes;
import androidx.core.content.ContextCompat;
import androidx.annotation.StringRes; import androidx.annotation.StringRes;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import com.karumi.dexter.Dexter; import com.karumi.dexter.Dexter;
import com.karumi.dexter.PermissionToken; import com.karumi.dexter.PermissionToken;
import com.karumi.dexter.listener.PermissionDeniedResponse; import com.karumi.dexter.listener.PermissionDeniedResponse;
import com.karumi.dexter.listener.PermissionGrantedResponse; import com.karumi.dexter.listener.PermissionGrantedResponse;
import com.karumi.dexter.listener.PermissionRequest; import com.karumi.dexter.listener.PermissionRequest;
import com.karumi.dexter.listener.single.BasePermissionListener; import com.karumi.dexter.listener.single.BasePermissionListener;
import fr.free.nrw.commons.CommonsApplication; import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
@ -27,7 +27,7 @@ public class PermissionUtils {
It open the app settings from where the user can manually give us the required permission. It open the app settings from where the user can manually give us the required permission.
* @param activity * @param activity
*/ */
public static void askUserToManuallyEnablePermissionFromSettings(Activity activity) { private static void askUserToManuallyEnablePermissionFromSettings(Activity activity) {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", activity.getPackageName(), null); Uri uri = Uri.fromParts("package", activity.getPackageName(), null);
intent.setData(uri); intent.setData(uri);
@ -50,6 +50,9 @@ public class PermissionUtils {
* Checks for a particular permission and runs the runnable to perform an action when the permission is granted * Checks for a particular permission and runs the runnable to perform an action when the permission is granted
* Also, it shows a rationale if needed * Also, it shows a rationale if needed
* *
* rationaleTitle and rationaleMessage can be invalid @StringRes. If the value is -1 then no permission rationale
* will be displayed and permission would be requested
*
* Sample usage: * Sample usage:
* *
* PermissionUtils.checkPermissionsAndPerformAction(activity, * PermissionUtils.checkPermissionsAndPerformAction(activity,
@ -58,12 +61,20 @@ public class PermissionUtils {
* R.string.storage_permission_title, * R.string.storage_permission_title,
* R.string.write_storage_permission_rationale); * R.string.write_storage_permission_rationale);
* *
* If you don't want the permission rationale to be shown then use:
*
* PermissionUtils.checkPermissionsAndPerformAction(activity,
* Manifest.permission.WRITE_EXTERNAL_STORAGE,
* () -> initiateCameraUpload(activity),
* - 1, -1);
*
*
* *
* @param activity activity requesting permissions * @param activity activity requesting permissions
* @param permission the permission being requests * @param permission the permission being requests
* @param onPermissionGranted the runnable to be executed when the permission is granted * @param onPermissionGranted the runnable to be executed when the permission is granted
* @param rationaleTitle rationale title to be displayed when permission was denied * @param rationaleTitle rationale title to be displayed when permission was denied. It can be an invalid @StringRes
* @param rationaleMessage rationale message to be displayed when permission was denied * @param rationaleMessage rationale message to be displayed when permission was denied. It can be an invalid @StringRes
*/ */
public static void checkPermissionsAndPerformAction(Activity activity, String permission, public static void checkPermissionsAndPerformAction(Activity activity, String permission,
Runnable onPermissionGranted, @StringRes int rationaleTitle, Runnable onPermissionGranted, @StringRes int rationaleTitle,
@ -93,7 +104,6 @@ public class PermissionUtils {
* @param rationaleTitle rationale title to be displayed when permission was denied * @param rationaleTitle rationale title to be displayed when permission was denied
* @param rationaleMessage rationale message to be displayed when permission was denied * @param rationaleMessage rationale message to be displayed when permission was denied
*/ */
public static void checkPermissionsAndPerformAction(Activity activity, String permission, public static void checkPermissionsAndPerformAction(Activity activity, String permission,
Runnable onPermissionGranted, Runnable onPermissionDenied, @StringRes int rationaleTitle, Runnable onPermissionGranted, Runnable onPermissionDenied, @StringRes int rationaleTitle,
@StringRes int rationaleMessage) { @StringRes int rationaleMessage) {
@ -120,6 +130,10 @@ public class PermissionUtils {
@Override @Override
public void onPermissionRationaleShouldBeShown(PermissionRequest permission, public void onPermissionRationaleShouldBeShown(PermissionRequest permission,
PermissionToken token) { PermissionToken token) {
if (rationaleTitle == -1 && rationaleMessage == -1) {
token.continuePermissionRequest();
return;
}
DialogUtil.showAlertDialog(activity, activity.getString(rationaleTitle), DialogUtil.showAlertDialog(activity, activity.getString(rationaleTitle),
activity.getString(rationaleMessage), activity.getString(rationaleMessage),
activity.getString(android.R.string.ok), activity.getString(android.R.string.ok),
@ -129,4 +143,5 @@ public class PermissionUtils {
}) })
.check(); .check();
} }
} }

View file

@ -6,6 +6,7 @@
* Azouz.anis * Azouz.anis
* ButterflyOfFire * ButterflyOfFire
* Claw eg * Claw eg
* Kassem7899
* Meno25 * Meno25
* Mido * Mido
* Monrokhoury * Monrokhoury
@ -22,6 +23,7 @@
<string name="preference_category_appearance">المظهر</string> <string name="preference_category_appearance">المظهر</string>
<string name="preference_category_general">عام</string> <string name="preference_category_general">عام</string>
<string name="preference_category_feedback">التعليقات</string> <string name="preference_category_feedback">التعليقات</string>
<string name="preference_category_privacy">الخصوصية</string>
<string name="preference_category_location">الموقع</string> <string name="preference_category_location">الموقع</string>
<string name="app_name">كومنز</string> <string name="app_name">كومنز</string>
<string name="bullet"></string> <string name="bullet"></string>
@ -516,6 +518,15 @@
<string name="welcome_dont_upload_content_description">أمثلة على صور لعدم رفعها</string> <string name="welcome_dont_upload_content_description">أمثلة على صور لعدم رفعها</string>
<string name="skip_image">تخطي هذه الصورة</string> <string name="skip_image">تخطي هذه الصورة</string>
<string name="download_failed_we_cannot_download_the_file_without_storage_permission">التنزيل فشل!!. لا يمكننا تنزيل الملف دون إذن تخزين خارجي.</string> <string name="download_failed_we_cannot_download_the_file_without_storage_permission">التنزيل فشل!!. لا يمكننا تنزيل الملف دون إذن تخزين خارجي.</string>
<string name="manage_exif_tags">إدارة وسوم EXIF</string>
<string name="manage_exif_tags_summary">حدد أية وسوم EXIF ​​لتحتفظ بها في المرفوعات</string>
<string name="exif_tag_name_author">المؤلف</string>
<string name="exif_tag_name_copyright">حقوق نشر</string>
<string name="exif_tag_name_location">الموقع</string>
<string name="exif_tag_name_cameraModel">طراز الكاميرا</string>
<string name="exif_tag_name_lensModel">طراز العدسة</string>
<string name="exif_tag_name_serialNumbers">الأرقام التسلسلية</string>
<string name="exif_tag_name_software">برمجية</string>
<string name="share_text">ارفع الصور لويكيميديا ​​كومنز على هاتفك قم بتنزيل تطبيق كومنز: %1$s</string> <string name="share_text">ارفع الصور لويكيميديا ​​كومنز على هاتفك قم بتنزيل تطبيق كومنز: %1$s</string>
<string name="share_via">مشاركة التطبيق عبر...</string> <string name="share_via">مشاركة التطبيق عبر...</string>
<string name="image_info">معلومات الصورة</string> <string name="image_info">معلومات الصورة</string>

View file

@ -322,4 +322,5 @@
<string name="yes_submit">হ্যাঁ, জমা দিন</string> <string name="yes_submit">হ্যাঁ, জমা দিন</string>
<string name="no_go_back">না, ফিরে যান</string> <string name="no_go_back">না, ফিরে যান</string>
<string name="please_wait">অনুগ্রহ করে অপেক্ষা করুন...</string> <string name="please_wait">অনুগ্রহ করে অপেক্ষা করুন...</string>
<string name="exif_tag_name_location">অবস্থান</string>
</resources> </resources>

View file

@ -9,6 +9,8 @@
* FarsiNevis * FarsiNevis
* Fatemi127 * Fatemi127
* Freshman404 * Freshman404
* Ladsgroup
* Mardetanha
* Mehdi * Mehdi
* Mjbmr * Mjbmr
* Omidh * Omidh
@ -18,6 +20,7 @@
* Vahedi * Vahedi
* Yoosef Pooranvary * Yoosef Pooranvary
* جواد * جواد
* درفش کاویانی
--> -->
<resources> <resources>
<string name="title_activity_explore">کاوش</string> <string name="title_activity_explore">کاوش</string>
@ -25,6 +28,7 @@
<string name="preference_category_appearance">نمایش صفحه</string> <string name="preference_category_appearance">نمایش صفحه</string>
<string name="preference_category_general">عمومی</string> <string name="preference_category_general">عمومی</string>
<string name="preference_category_feedback">بازخورد</string> <string name="preference_category_feedback">بازخورد</string>
<string name="preference_category_privacy">حریم خصوصی</string>
<string name="preference_category_location">مکان</string> <string name="preference_category_location">مکان</string>
<string name="app_name">ویکی‌انبار</string> <string name="app_name">ویکی‌انبار</string>
<string name="bullet"></string> <string name="bullet"></string>
@ -300,9 +304,12 @@
<string name="no_internet">اینترنت در دسترس نیست</string> <string name="no_internet">اینترنت در دسترس نیست</string>
<string name="internet_established">اینترنت در دسترس است</string> <string name="internet_established">اینترنت در دسترس است</string>
<string name="error_notifications">خطا در آوردن اطلاعیه</string> <string name="error_notifications">خطا در آوردن اطلاعیه</string>
<string name="error_review">در تلاش برای بدست‌آوردن تصویر جهت بازبینی خطایی پیش آمد. لطفا برای تلاش مجدد بر روی ریفرش کلیک کنید.</string>
<string name="error_review_categories">دریافت رده‌های تصویر برای بازبینی به مشکل برخورد کرد. لطفا برای تلاش مجدد صفحه را رفرش کنید.</string>
<string name="no_notifications">هشداری پیدا نشد</string> <string name="no_notifications">هشداری پیدا نشد</string>
<string name="about_translate">&lt;u&gt;ترجمه&lt;/u&gt;</string> <string name="about_translate">&lt;u&gt;ترجمه&lt;/u&gt;</string>
<string name="about_translate_title">زبان‌ها</string> <string name="about_translate_title">زبان‌ها</string>
<string name="about_translate_message">لطفا زبانی که مایلید در آنها ترجمه‌های خود را ثبت کنید انتخاب کنید.</string>
<string name="about_translate_proceed">ادامه</string> <string name="about_translate_proceed">ادامه</string>
<string name="about_translate_cancel">لغو</string> <string name="about_translate_cancel">لغو</string>
<string name="retry">سعى دوباره</string> <string name="retry">سعى دوباره</string>
@ -317,11 +324,15 @@
<string name="search_commons">جستجوی ویکی‌انبار</string> <string name="search_commons">جستجوی ویکی‌انبار</string>
<string name="title_activity_search">جستجو</string> <string name="title_activity_search">جستجو</string>
<string name="search_recent_header">جستجوهای اخیر:</string> <string name="search_recent_header">جستجوهای اخیر:</string>
<string name="provider_searches">کوئری‌های که اخیرا جستجو شده</string>
<string name="error_loading_categories">خطا هنگام بار کردن رده ها.</string> <string name="error_loading_categories">خطا هنگام بار کردن رده ها.</string>
<string name="error_loading_subcategories">در هنگام بارگیری زیررده‌ها خطایی رخ داد.</string>
<string name="search_tab_title_media">رسانه</string> <string name="search_tab_title_media">رسانه</string>
<string name="search_tab_title_categories">دسته بندی</string> <string name="search_tab_title_categories">دسته بندی</string>
<string name="explore_tab_title_featured">برگزیده</string> <string name="explore_tab_title_featured">برگزیده</string>
<string name="explore_tab_title_mobile">بارگذاری‌شده با تلفن همراه</string> <string name="explore_tab_title_mobile">بارگذاری‌شده با تلفن همراه</string>
<string name="successful_wikidata_edit">تصویر با موفقیت به %1$s در ویکی‌داده افزوده‌شد.</string>
<string name="wikidata_edit_failure">تلاش برای بروزرسانی موجودی ویکی‌دیتای مرتبط شکست خورد</string>
<string name="menu_set_wallpaper">انتخاب به عنوان پس‌زمینه</string> <string name="menu_set_wallpaper">انتخاب به عنوان پس‌زمینه</string>
<string name="wallpaper_set_successfully">تصویر پس زمینه به طور موفقیت آمیز تنظیم شد!</string> <string name="wallpaper_set_successfully">تصویر پس زمینه به طور موفقیت آمیز تنظیم شد!</string>
<string name="quiz">امتحان</string> <string name="quiz">امتحان</string>
@ -330,6 +341,7 @@
<string name="result">نتیجه</string> <string name="result">نتیجه</string>
<string name="warning_for_no_answer">یکی از دو گزینه را انتخاب کنید تا به سوال پاسخ دهید</string> <string name="warning_for_no_answer">یکی از دو گزینه را انتخاب کنید تا به سوال پاسخ دهید</string>
<string name="user_not_logged_in">جلسه ورود به سیستم منقضی شد، لطفا دوباره وارد سیستم شوید.</string> <string name="user_not_logged_in">جلسه ورود به سیستم منقضی شد، لطفا دوباره وارد سیستم شوید.</string>
<string name="quiz_result_share_message">کویز خود را با دوستان خود به اشتراک بگذارید.</string>
<string name="continue_message">ادامه</string> <string name="continue_message">ادامه</string>
<string name="correct">جواب درست</string> <string name="correct">جواب درست</string>
<string name="wrong">جواب نادرست</string> <string name="wrong">جواب نادرست</string>
@ -348,6 +360,7 @@
<string name="statistics">آمارها</string> <string name="statistics">آمارها</string>
<string name="statistics_thanks">تشکر دریافت‌شد</string> <string name="statistics_thanks">تشکر دریافت‌شد</string>
<string name="statistics_featured">تصاویر برگزیده</string> <string name="statistics_featured">تصاویر برگزیده</string>
<string name="statistics_wikidata_edits">تصاویر بر اساس «مکان‌های اطراف»</string>
<string name="level">سطح</string> <string name="level">سطح</string>
<string name="images_uploaded">تصاویر بارگذاری شده</string> <string name="images_uploaded">تصاویر بارگذاری شده</string>
<string name="image_reverts">تصاویر واگردانی نشده</string> <string name="image_reverts">تصاویر واگردانی نشده</string>
@ -361,6 +374,9 @@
<string name="contributions_fragment">مشارکت‌ها</string> <string name="contributions_fragment">مشارکت‌ها</string>
<string name="nearby_fragment">در نزدیکی</string> <string name="nearby_fragment">در نزدیکی</string>
<string name="notifications">آگاه‌سازی‌ها</string> <string name="notifications">آگاه‌سازی‌ها</string>
<string name="archived_notifications">اعلان‌ها (بایگانی‌شده)</string>
<string name="display_nearby_notification">نمایش اعلان اطراف</string>
<string name="no_close_nearby">هیچ‌ مکان نزدیکی به شما یافته نشد</string>
<string name="list_sheet">فهرست</string> <string name="list_sheet">فهرست</string>
<string name="storage_permission">اجازه ذخیره</string> <string name="storage_permission">اجازه ذخیره</string>
<string name="step_count">گام %1$d از %2$d</string> <string name="step_count">گام %1$d از %2$d</string>
@ -396,25 +412,55 @@
<string name="search_this_area">جستجوی این محدوده</string> <string name="search_this_area">جستجوی این محدوده</string>
<string name="nearby_card_permission_title">درخواست اجازه</string> <string name="nearby_card_permission_title">درخواست اجازه</string>
<string name="never_ask_again">این را دیگر نپرس</string> <string name="never_ask_again">این را دیگر نپرس</string>
<string name="display_campaigns">کمپین‌های نمایش</string>
<string name="error_processing_image">هنگام پردازش این تصویر خطایی رخ داد. لطفا دوباره سعی کنید!</string> <string name="error_processing_image">هنگام پردازش این تصویر خطایی رخ داد. لطفا دوباره سعی کنید!</string>
<string name="getting_edit_token">دریافت توکن برای ویرایش</string>
<string name="check_category_edit_summary">درخواست بررسی رده</string>
<string name="check_category_success_title">بررسی رده درخواست شده</string>
<string name="check_category_failure_title">درخواست بررسی رده کار نکرد</string>
<string name="nominate_for_deletion_edit_file_page">افزودن پیام حذف‌شده به فایل</string>
<string name="nominate_for_deletion_done">انجام شد</string> <string name="nominate_for_deletion_done">انجام شد</string>
<string name="nominate_for_deletion_notify_user">به کاربر در صفحه بحثش خبر بده</string>
<string name="notsure">مطمئن نیستم</string> <string name="notsure">مطمئن نیستم</string>
<string name="send_thank_success_title">ارسال تشکر: موفق</string> <string name="send_thank_success_title">ارسال تشکر: موفق</string>
<string name="send_thank_failure_message">تلاش برای فرستادن تشکر شکست خورد %1$s</string>
<string name="send_thank_failure_title">ارسال تشکر: ناموفق</string> <string name="send_thank_failure_title">ارسال تشکر: ناموفق</string>
<string name="send_thank_send">ارسال تشکر</string> <string name="send_thank_send">ارسال تشکر</string>
<string name="send_thank_notification_title">ارسال تشکر</string> <string name="send_thank_notification_title">ارسال تشکر</string>
<string name="send_thank_toast">در حال ارسال تشکر برای %1$s</string> <string name="send_thank_toast">در حال ارسال تشکر برای %1$s</string>
<string name="review_category">آیا این به درستی رده‌بندی شده‌است؟</string> <string name="review_category">آیا این به درستی رده‌بندی شده‌است؟</string>
<string name="review_spam">آیا این در محدوده قابل قبول است؟</string>
<string name="review_thanks">آیا مایلید که از مشارکت‌ کننده تشکر کنید؟</string>
<string name="review_no_category">عجب، این حتی رده‌بندی هم نشده!</string>
<string name="review_spam_report_question">این از محدوده خارج است زیرا که</string>
<string name="review_c_violation_report_question">این فایل ناقض حق تکثیر است به خاطر اینکه</string>
<string name="review_category_no_button_text">خوب به نظر می‌رسد</string> <string name="review_category_no_button_text">خوب به نظر می‌رسد</string>
<string name="review_spam_yes_button_text">نه، این از محدوده خارج است</string>
<string name="review_spam_no_button_text">خوب به نظر می‌رسد</string> <string name="review_spam_no_button_text">خوب به نظر می‌رسد</string>
<string name="review_copyright_no_button_text">خوب به نظر می‌رسد</string> <string name="review_copyright_no_button_text">خوب به نظر می‌رسد</string>
<string name="review_thanks_yes_button_text">بله، چرا که نه</string> <string name="review_thanks_yes_button_text">بله، چرا که نه</string>
<string name="review_thanks_no_button_text">تصویر بعدی</string> <string name="review_thanks_no_button_text">تصویر بعدی</string>
<string name="no_image">تصاویر استاده نشده</string>
<string name="no_image_reverted">تصویر برگردانده نشد</string> <string name="no_image_reverted">تصویر برگردانده نشد</string>
<string name="no_image_uploaded">هیچ تصویری بارگذاری نشد</string> <string name="no_image_uploaded">هیچ تصویری بارگذاری نشد</string>
<string name="no_notification">شما هیچ اعلان خوانده‌نشده‌ای ندارید</string>
<string name="no_archived_notification">شما هیچ پیغام بایگانی شده‌ای ندارید</string>
<string name="menu_option_archived">نمایش بایگانی‌شده</string> <string name="menu_option_archived">نمایش بایگانی‌شده</string>
<string name="menu_option_unread">مشاهده خوانده نشده ها</string> <string name="menu_option_unread">مشاهده خوانده نشده ها</string>
<string name="image_chooser_title">انتخاب تصویر برای بارگذاری</string> <string name="image_chooser_title">انتخاب تصویر برای بارگذاری</string>
<string name="please_wait">لطفاً صبر کنید...</string> <string name="please_wait">لطفاً صبر کنید...</string>
<string name="previous_image_title_description">از عنوان/توضیحات پیشین استفاده کنید</string>
<string name="welcome_dont_upload_content_description">نمونه تصاویری که برای بازگذاری مناسب نیستند</string>
<string name="skip_image">از این تصویر صرف نظر کن</string>
<string name="manage_exif_tags">مدیریت تگ‌های EXIF</string>
<string name="manage_exif_tags_summary">تگ‌های موردنظر خود در EXIF را برای آپلود انتخاب کنید</string>
<string name="exif_tag_name_author">پدیدآور</string>
<string name="exif_tag_name_copyright">حق تکثیر</string>
<string name="exif_tag_name_location">مکان</string>
<string name="exif_tag_name_cameraModel">مدل دوربین</string>
<string name="exif_tag_name_lensModel">مدل لنز</string>
<string name="exif_tag_name_serialNumbers">شماره سریال</string>
<string name="exif_tag_name_software">نرم‌افزار</string>
<string name="share_via">اشتراک از طریق...</string>
<string name="image_info">اطلاعات عکس</string> <string name="image_info">اطلاعات عکس</string>
</resources> </resources>

View file

@ -30,6 +30,7 @@
<string name="preference_category_appearance">Apparence</string> <string name="preference_category_appearance">Apparence</string>
<string name="preference_category_general">Général</string> <string name="preference_category_general">Général</string>
<string name="preference_category_feedback">Donner son avis</string> <string name="preference_category_feedback">Donner son avis</string>
<string name="preference_category_privacy">Confidentialité</string>
<string name="preference_category_location">Emplacement</string> <string name="preference_category_location">Emplacement</string>
<string name="app_name">Commons</string> <string name="app_name">Commons</string>
<string name="bullet"></string> <string name="bullet"></string>
@ -527,6 +528,15 @@
<string name="welcome_dont_upload_content_description">Exemples d\'images à ne pas téléverser</string> <string name="welcome_dont_upload_content_description">Exemples d\'images à ne pas téléverser</string>
<string name="skip_image">SAUTER CETTE IMAGE</string> <string name="skip_image">SAUTER CETTE IMAGE</string>
<string name="download_failed_we_cannot_download_the_file_without_storage_permission">Échec du téléchargement! Nous ne pouvons pas télécharger le fichier sans droit de stockage externe.</string> <string name="download_failed_we_cannot_download_the_file_without_storage_permission">Échec du téléchargement! Nous ne pouvons pas télécharger le fichier sans droit de stockage externe.</string>
<string name="manage_exif_tags">Gérer les balises EXIF</string>
<string name="manage_exif_tags_summary">Sélectionner quelles balises EXIF à conserver dans les téléchargements</string>
<string name="exif_tag_name_author">Auteur</string>
<string name="exif_tag_name_copyright">Droits dauteur</string>
<string name="exif_tag_name_location">Emplacement</string>
<string name="exif_tag_name_cameraModel">Modèle dappareil photo</string>
<string name="exif_tag_name_lensModel">Modèle de lentille</string>
<string name="exif_tag_name_serialNumbers">Numéros de série</string>
<string name="exif_tag_name_software">Logiciel</string>
<string name="share_text">Téléverser des photos vers Wikimédia Communs, sur votre téléphone Téléchargez lapplication Communs : %1$s</string> <string name="share_text">Téléverser des photos vers Wikimédia Communs, sur votre téléphone Téléchargez lapplication Communs : %1$s</string>
<string name="share_via">Partager lapplication via…</string> <string name="share_via">Partager lapplication via…</string>
<string name="image_info">Informations de limage</string> <string name="image_info">Informations de limage</string>

View file

@ -469,6 +469,7 @@
<string name="previous_image_title_description">Copia titolo/descrizione precedente</string> <string name="previous_image_title_description">Copia titolo/descrizione precedente</string>
<string name="previous_button_tooltip_message">Clicca per riusare il titolo e la descrizione dell\'immagine precedente e adattarli all\'immagine attuale.</string> <string name="previous_button_tooltip_message">Clicca per riusare il titolo e la descrizione dell\'immagine precedente e adattarli all\'immagine attuale.</string>
<string name="skip_image">SALTA QUESTA IMMAGINE</string> <string name="skip_image">SALTA QUESTA IMMAGINE</string>
<string name="exif_tag_name_author">Autore</string>
<string name="share_via">Condividi applicazione tramite...</string> <string name="share_via">Condividi applicazione tramite...</string>
<string name="image_info">Informazioni sull\'immagine</string> <string name="image_info">Informazioni sull\'immagine</string>
</resources> </resources>

View file

@ -19,6 +19,7 @@
<string name="preference_category_appearance">보이기</string> <string name="preference_category_appearance">보이기</string>
<string name="preference_category_general">일반</string> <string name="preference_category_general">일반</string>
<string name="preference_category_feedback">피드백</string> <string name="preference_category_feedback">피드백</string>
<string name="preference_category_privacy">개인정보</string>
<string name="preference_category_location">위치</string> <string name="preference_category_location">위치</string>
<string name="app_name">공용</string> <string name="app_name">공용</string>
<string name="bullet"></string> <string name="bullet"></string>
@ -452,6 +453,13 @@
<string name="previous_image_title_description">이전의 제목 및 설명 복사</string> <string name="previous_image_title_description">이전의 제목 및 설명 복사</string>
<string name="skip_image">이 이미지 건너뛰기</string> <string name="skip_image">이 이미지 건너뛰기</string>
<string name="download_failed_we_cannot_download_the_file_without_storage_permission">다운로드를 실패했습니다!! 외장 스토리지 권한 없이 파일을 다운로드할 수 없습니다.</string> <string name="download_failed_we_cannot_download_the_file_without_storage_permission">다운로드를 실패했습니다!! 외장 스토리지 권한 없이 파일을 다운로드할 수 없습니다.</string>
<string name="manage_exif_tags">EXIF 태그 관리</string>
<string name="exif_tag_name_author">만든이</string>
<string name="exif_tag_name_copyright">저작권</string>
<string name="exif_tag_name_location">위치</string>
<string name="exif_tag_name_cameraModel">카메라 모델</string>
<string name="exif_tag_name_serialNumbers">일련 번호</string>
<string name="exif_tag_name_software">소프트웨어</string>
<string name="share_via">앱 공유...</string> <string name="share_via">앱 공유...</string>
<string name="image_info">이미지 정보</string> <string name="image_info">이미지 정보</string>
</resources> </resources>

View file

@ -10,6 +10,7 @@
<string name="preference_category_appearance">Изглед</string> <string name="preference_category_appearance">Изглед</string>
<string name="preference_category_general">Општи</string> <string name="preference_category_general">Општи</string>
<string name="preference_category_feedback">Мислења</string> <string name="preference_category_feedback">Мислења</string>
<string name="preference_category_privacy">Лични податоци</string>
<string name="preference_category_location">Место</string> <string name="preference_category_location">Место</string>
<string name="app_name">Ризница</string> <string name="app_name">Ризница</string>
<string name="bullet"></string> <string name="bullet"></string>
@ -507,6 +508,15 @@
<string name="welcome_dont_upload_content_description">Примери — Слики што не се за подигање</string> <string name="welcome_dont_upload_content_description">Примери — Слики што не се за подигање</string>
<string name="skip_image">ПРЕСКОКНИ ЈА ПОРАКАВА</string> <string name="skip_image">ПРЕСКОКНИ ЈА ПОРАКАВА</string>
<string name="download_failed_we_cannot_download_the_file_without_storage_permission">Преземањето не успеа!!! Не можеме да ја преземеме податотеката без дозвола од надворешен склад.</string> <string name="download_failed_we_cannot_download_the_file_without_storage_permission">Преземањето не успеа!!! Не можеме да ја преземеме податотеката без дозвола од надворешен склад.</string>
<string name="manage_exif_tags">Раков. со EXIF-ознаки</string>
<string name="manage_exif_tags_summary">Изберете кои EXIF-ознаки да се задржат во подигањата</string>
<string name="exif_tag_name_author">Автор</string>
<string name="exif_tag_name_copyright">Авторски права</string>
<string name="exif_tag_name_location">Место</string>
<string name="exif_tag_name_cameraModel">Модел на камерата</string>
<string name="exif_tag_name_lensModel">Модел на објективот</string>
<string name="exif_tag_name_serialNumbers">Сериски броеви</string>
<string name="exif_tag_name_software">Програми</string>
<string name="share_text">Подигајте слики на Ризницата од телефон. Преземете го прилогот на Ризницата: %1$s</string> <string name="share_text">Подигајте слики на Ризницата од телефон. Преземете го прилогот на Ризницата: %1$s</string>
<string name="share_via">Сподели преку...</string> <string name="share_via">Сподели преку...</string>
<string name="image_info">Инфо за сликата</string> <string name="image_info">Инфо за сликата</string>

View file

@ -22,6 +22,7 @@
<string name="preference_category_appearance">Aparência</string> <string name="preference_category_appearance">Aparência</string>
<string name="preference_category_general">Geral</string> <string name="preference_category_general">Geral</string>
<string name="preference_category_feedback">Comentário</string> <string name="preference_category_feedback">Comentário</string>
<string name="preference_category_privacy">Privacidade</string>
<string name="preference_category_location">Localização</string> <string name="preference_category_location">Localização</string>
<string name="app_name">Commons</string> <string name="app_name">Commons</string>
<string name="bullet"></string> <string name="bullet"></string>
@ -519,6 +520,15 @@
<string name="welcome_dont_upload_content_description">Exemplos de imagens que não devem ser carregadas</string> <string name="welcome_dont_upload_content_description">Exemplos de imagens que não devem ser carregadas</string>
<string name="skip_image">PULAR ESTA IMAGEM</string> <string name="skip_image">PULAR ESTA IMAGEM</string>
<string name="download_failed_we_cannot_download_the_file_without_storage_permission">Falha no Download!!. Não podemos fazer o download do arquivo sem permissão de armazenamento externo.</string> <string name="download_failed_we_cannot_download_the_file_without_storage_permission">Falha no Download!!. Não podemos fazer o download do arquivo sem permissão de armazenamento externo.</string>
<string name="manage_exif_tags">Gerenciar etiquetas EXIF</string>
<string name="manage_exif_tags_summary">Selecione quais etiquetas EXIF para manter nos carregados</string>
<string name="exif_tag_name_author">Autor</string>
<string name="exif_tag_name_copyright">Direitos autorais</string>
<string name="exif_tag_name_location">Localização</string>
<string name="exif_tag_name_cameraModel">Modelo da câmera</string>
<string name="exif_tag_name_lensModel">Modelo de lente</string>
<string name="exif_tag_name_serialNumbers">Números de série</string>
<string name="exif_tag_name_software">Software</string>
<string name="share_text">Faça o carregamento de fotos para o Wikimedia Commons no seu telefone ou baixe o aplicativo Commons: %1$s</string> <string name="share_text">Faça o carregamento de fotos para o Wikimedia Commons no seu telefone ou baixe o aplicativo Commons: %1$s</string>
<string name="share_via">Compartilhar aplicativo via...</string> <string name="share_via">Compartilhar aplicativo via...</string>
<string name="image_info">Informação da imagem</string> <string name="image_info">Informação da imagem</string>

View file

@ -28,6 +28,7 @@
<string name="preference_category_appearance">Внешний вид</string> <string name="preference_category_appearance">Внешний вид</string>
<string name="preference_category_general">Общие</string> <string name="preference_category_general">Общие</string>
<string name="preference_category_feedback">Обратная связь</string> <string name="preference_category_feedback">Обратная связь</string>
<string name="preference_category_privacy">Конфиденциальность</string>
<string name="preference_category_location">Местоположение</string> <string name="preference_category_location">Местоположение</string>
<string name="app_name">Викисклад</string> <string name="app_name">Викисклад</string>
<string name="bullet"></string> <string name="bullet"></string>
@ -526,6 +527,15 @@
<string name="welcome_dont_upload_content_description">Примеры изображений, которые не следует загружать на Викисклад</string> <string name="welcome_dont_upload_content_description">Примеры изображений, которые не следует загружать на Викисклад</string>
<string name="skip_image">ПРОПУСТИТЬ ЭТО ИЗОБРАЖЕНИЕ</string> <string name="skip_image">ПРОПУСТИТЬ ЭТО ИЗОБРАЖЕНИЕ</string>
<string name="download_failed_we_cannot_download_the_file_without_storage_permission">Скачивание файла не удалось! Это невозможно выполнить без предоставленного разрешения по использованию внешнего носителя.</string> <string name="download_failed_we_cannot_download_the_file_without_storage_permission">Скачивание файла не удалось! Это невозможно выполнить без предоставленного разрешения по использованию внешнего носителя.</string>
<string name="manage_exif_tags">Работа с EXIF-тегами</string>
<string name="manage_exif_tags_summary">Укажите, какие EXIF-теги следует сохранить при загрузке файлов</string>
<string name="exif_tag_name_author">Автор</string>
<string name="exif_tag_name_copyright">Авторские права</string>
<string name="exif_tag_name_location">Местоположение</string>
<string name="exif_tag_name_cameraModel">Модель камеры</string>
<string name="exif_tag_name_lensModel">Модель объектива</string>
<string name="exif_tag_name_serialNumbers">Серийный номер</string>
<string name="exif_tag_name_software">Программное обеспечение</string>
<string name="share_text">Чтобы загружать фото на Викисклад (Wikimedia Commons), скачайте одноимённое приложение «Викисклад» (Commons): %1$s</string> <string name="share_text">Чтобы загружать фото на Викисклад (Wikimedia Commons), скачайте одноимённое приложение «Викисклад» (Commons): %1$s</string>
<string name="share_via">Поделиться приложением с помощью...</string> <string name="share_via">Поделиться приложением с помощью...</string>
<string name="image_info">Информация об изображении</string> <string name="image_info">Информация об изображении</string>

View file

@ -15,6 +15,7 @@
<string name="preference_category_appearance">Utseende</string> <string name="preference_category_appearance">Utseende</string>
<string name="preference_category_general">Allmänt</string> <string name="preference_category_general">Allmänt</string>
<string name="preference_category_feedback">Återkoppling</string> <string name="preference_category_feedback">Återkoppling</string>
<string name="preference_category_privacy">Integritet</string>
<string name="preference_category_location">Plats</string> <string name="preference_category_location">Plats</string>
<string name="app_name">Commons</string> <string name="app_name">Commons</string>
<string name="bullet"></string> <string name="bullet"></string>
@ -512,6 +513,15 @@
<string name="welcome_dont_upload_content_description">Exempel på bilder att inte ladda upp</string> <string name="welcome_dont_upload_content_description">Exempel på bilder att inte ladda upp</string>
<string name="skip_image">HOPPA ÖVER DENNA BILD</string> <string name="skip_image">HOPPA ÖVER DENNA BILD</string>
<string name="download_failed_we_cannot_download_the_file_without_storage_permission">Nedladdning misslyckades!! Vi kan inte ladda ned filen utan behörighet för extern lagring.</string> <string name="download_failed_we_cannot_download_the_file_without_storage_permission">Nedladdning misslyckades!! Vi kan inte ladda ned filen utan behörighet för extern lagring.</string>
<string name="manage_exif_tags">Hantera EXIF-taggar</string>
<string name="manage_exif_tags_summary">Välj vilka EXIF-taggar att behålla i uppladdningar</string>
<string name="exif_tag_name_author">Skapare</string>
<string name="exif_tag_name_copyright">Upphovsrätt</string>
<string name="exif_tag_name_location">Plats</string>
<string name="exif_tag_name_cameraModel">Kameramodell</string>
<string name="exif_tag_name_lensModel">Linsmodell</string>
<string name="exif_tag_name_serialNumbers">Serienummer</string>
<string name="exif_tag_name_software">Programvara</string>
<string name="share_text">Ladda upp foton till Wikimedia Commons på din telefon Ladda ned Commons-appen: %1$s</string> <string name="share_text">Ladda upp foton till Wikimedia Commons på din telefon Ladda ned Commons-appen: %1$s</string>
<string name="share_via">Dela appen via...</string> <string name="share_via">Dela appen via...</string>
<string name="image_info">Bildinfo</string> <string name="image_info">Bildinfo</string>

View file

@ -18,6 +18,7 @@
<string name="preference_category_appearance">Зовнішній вигляд</string> <string name="preference_category_appearance">Зовнішній вигляд</string>
<string name="preference_category_general">Загальні</string> <string name="preference_category_general">Загальні</string>
<string name="preference_category_feedback">Зворотний зв\'язок</string> <string name="preference_category_feedback">Зворотний зв\'язок</string>
<string name="preference_category_privacy">Конфіденційність</string>
<string name="preference_category_location">Розташування</string> <string name="preference_category_location">Розташування</string>
<string name="app_name">Вікісховище</string> <string name="app_name">Вікісховище</string>
<string name="bullet"></string> <string name="bullet"></string>
@ -526,6 +527,15 @@
<string name="welcome_dont_upload_content_description">Приклади зображень, які не слід завантажувати</string> <string name="welcome_dont_upload_content_description">Приклади зображень, які не слід завантажувати</string>
<string name="skip_image">ПРОПУСТИТИ ЦЕ ЗОБРАЖЕННЯ</string> <string name="skip_image">ПРОПУСТИТИ ЦЕ ЗОБРАЖЕННЯ</string>
<string name="download_failed_we_cannot_download_the_file_without_storage_permission">Завантаження не вдалося. Ми не змогли завантажити файл без доступу до зовнішнього носія.</string> <string name="download_failed_we_cannot_download_the_file_without_storage_permission">Завантаження не вдалося. Ми не змогли завантажити файл без доступу до зовнішнього носія.</string>
<string name="manage_exif_tags">Робота з EXIF-тегами</string>
<string name="manage_exif_tags_summary">Вкажіть, які EXIF-теги мають бути збережені при завантаженні файлів</string>
<string name="exif_tag_name_author">Автор</string>
<string name="exif_tag_name_copyright">Авторські права</string>
<string name="exif_tag_name_location">Місцезнаходження</string>
<string name="exif_tag_name_cameraModel">Модель камери</string>
<string name="exif_tag_name_lensModel">Модель об\'єктиву</string>
<string name="exif_tag_name_serialNumbers">Серійний номер</string>
<string name="exif_tag_name_software">Програмне забезпечення</string>
<string name="share_text">Вивантажуйте фото у Вікісховище зі свого телефона. Завантажте застосунок: %1$s</string> <string name="share_text">Вивантажуйте фото у Вікісховище зі свого телефона. Завантажте застосунок: %1$s</string>
<string name="share_via">Поділитися програмкою через…</string> <string name="share_via">Поділитися програмкою через…</string>
<string name="image_info">Інформація про зображення</string> <string name="image_info">Інформація про зображення</string>

View file

@ -21,6 +21,7 @@
<string name="preference_category_appearance">外觀</string> <string name="preference_category_appearance">外觀</string>
<string name="preference_category_general">一般</string> <string name="preference_category_general">一般</string>
<string name="preference_category_feedback">意見回饋</string> <string name="preference_category_feedback">意見回饋</string>
<string name="preference_category_privacy">隱私</string>
<string name="preference_category_location">位置</string> <string name="preference_category_location">位置</string>
<string name="app_name">維基共享資源</string> <string name="app_name">維基共享資源</string>
<string name="bullet"></string> <string name="bullet"></string>
@ -516,6 +517,15 @@
<string name="welcome_dont_upload_content_description">未上傳範例圖片</string> <string name="welcome_dont_upload_content_description">未上傳範例圖片</string>
<string name="skip_image">忽略此圖片</string> <string name="skip_image">忽略此圖片</string>
<string name="download_failed_we_cannot_download_the_file_without_storage_permission">下載失敗!因為缺少外部存儲裝置權限緣故,我們無法下載檔案。</string> <string name="download_failed_we_cannot_download_the_file_without_storage_permission">下載失敗!因為缺少外部存儲裝置權限緣故,我們無法下載檔案。</string>
<string name="manage_exif_tags">管理 EXIF 標籤</string>
<string name="manage_exif_tags_summary">選擇要保持上傳的 EXIF 標籤</string>
<string name="exif_tag_name_author">作者</string>
<string name="exif_tag_name_copyright">版權</string>
<string name="exif_tag_name_location">位置</string>
<string name="exif_tag_name_cameraModel">相機模型</string>
<string name="exif_tag_name_lensModel">透鏡模型</string>
<string name="exif_tag_name_serialNumbers">序號</string>
<string name="exif_tag_name_software">軟體</string>
<string name="share_text">在您的手機上更新照片到維基共享資源,下載共享資源應用程式:%1$s</string> <string name="share_text">在您的手機上更新照片到維基共享資源,下載共享資源應用程式:%1$s</string>
<string name="share_via">分享應用程式透過…</string> <string name="share_via">分享應用程式透過…</string>
<string name="image_info">圖片資訊</string> <string name="image_info">圖片資訊</string>

View file

@ -166,6 +166,7 @@
<string name="storage_permission_title">Requesting Storage Permission</string> <string name="storage_permission_title">Requesting Storage Permission</string>
<string name="read_storage_permission_rationale">Required permission: Read external storage. App cannot access your gallery without this.</string> <string name="read_storage_permission_rationale">Required permission: Read external storage. App cannot access your gallery without this.</string>
<string name="write_storage_permission_rationale">Required permission: Write external storage. App cannot access your camera/gallery without this.</string> <string name="write_storage_permission_rationale">Required permission: Write external storage. App cannot access your camera/gallery without this.</string>
<string name="location_permission_title">Requesting Location Permission</string>
<string name="location_permission_rationale">Optional permission: Get current location for category suggestions</string> <string name="location_permission_rationale">Optional permission: Get current location for category suggestions</string>
<string name="ok">OK</string> <string name="ok">OK</string>
<string name="title_activity_nearby">Nearby Places</string> <string name="title_activity_nearby">Nearby Places</string>

View file

@ -3,7 +3,10 @@ package fr.free.nrw.commons.review
import fr.free.nrw.commons.Media import fr.free.nrw.commons.Media
import fr.free.nrw.commons.mwapi.MediaWikiApi import fr.free.nrw.commons.mwapi.MediaWikiApi
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient
import io.reactivex.Observable
import io.reactivex.Single import io.reactivex.Single
import junit.framework.Assert.assertNotNull
import junit.framework.Assert.assertNull
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
@ -13,6 +16,8 @@ import org.mockito.Mock
import org.mockito.Mockito.* import org.mockito.Mockito.*
import org.mockito.MockitoAnnotations import org.mockito.MockitoAnnotations
import org.wikipedia.dataclient.mwapi.MwQueryPage import org.wikipedia.dataclient.mwapi.MwQueryPage
import org.wikipedia.dataclient.mwapi.MwQueryResponse
import org.wikipedia.dataclient.mwapi.MwQueryResult
import org.wikipedia.dataclient.mwapi.RecentChange import org.wikipedia.dataclient.mwapi.RecentChange
/** /**
@ -20,6 +25,8 @@ import org.wikipedia.dataclient.mwapi.RecentChange
*/ */
class ReviewHelperTest { class ReviewHelperTest {
@Mock
internal var reviewInterface: ReviewInterface? = null
@Mock @Mock
internal var okHttpJsonApiClient: OkHttpJsonApiClient? = null internal var okHttpJsonApiClient: OkHttpJsonApiClient? = null
@Mock @Mock
@ -35,6 +42,31 @@ class ReviewHelperTest {
@Throws(Exception::class) @Throws(Exception::class)
fun setUp() { fun setUp() {
MockitoAnnotations.initMocks(this) MockitoAnnotations.initMocks(this)
val mwQueryPage = mock(MwQueryPage::class.java)
val mockRevision = mock(MwQueryPage.Revision::class.java)
`when`(mockRevision.user).thenReturn("TestUser")
`when`(mwQueryPage.revisions()).thenReturn(listOf(mockRevision))
val recentChange = getMockRecentChange("test", "File:Test1.jpeg", 0)
val recentChange1 = getMockRecentChange("test", "File:Test2.png", 0)
val recentChange2 = getMockRecentChange("test", "File:Test3.jpg", 0)
val mwQueryResult = mock(MwQueryResult::class.java)
`when`(mwQueryResult.recentChanges).thenReturn(listOf(recentChange, recentChange1, recentChange2))
`when`(mwQueryResult.firstPage()).thenReturn(mwQueryPage)
`when`(mwQueryResult.pages()).thenReturn(listOf(mwQueryPage))
val mockResponse = mock(MwQueryResponse::class.java)
`when`(mockResponse.query()).thenReturn(mwQueryResult)
`when`(reviewInterface?.getRecentChanges(ArgumentMatchers.anyString()))
.thenReturn(Observable.just(mockResponse))
`when`(reviewInterface?.getFirstRevisionOfFile(ArgumentMatchers.anyString()))
.thenReturn(Observable.just(mockResponse))
val media = mock(Media::class.java)
`when`(media.filename).thenReturn("File:Test.jpg")
`when`(okHttpJsonApiClient?.getMedia(ArgumentMatchers.anyString(), ArgumentMatchers.anyBoolean()))
.thenReturn(Single.just(media))
} }
/** /**
@ -42,40 +74,48 @@ class ReviewHelperTest {
*/ */
@Test @Test
fun getRandomMedia() { fun getRandomMedia() {
val recentChange = getMockRecentChange("test", "File:Test1.jpeg", 0)
val recentChange1 = getMockRecentChange("test", "File:Test2.png", 0)
val recentChange2 = getMockRecentChange("test", "File:Test3.jpg", 0)
`when`(okHttpJsonApiClient?.recentFileChanges)
.thenReturn(Single.just(listOf(recentChange, recentChange1, recentChange2)))
`when`(mediaWikiApi?.pageExists(ArgumentMatchers.anyString())) `when`(mediaWikiApi?.pageExists(ArgumentMatchers.anyString()))
.thenReturn(Single.just(false)) .thenReturn(Single.just(false))
`when`(okHttpJsonApiClient?.getMedia(ArgumentMatchers.anyString(), ArgumentMatchers.anyBoolean()))
.thenReturn(Single.just(mock(Media::class.java)))
val randomMedia = reviewHelper?.randomMedia?.blockingGet() val randomMedia = reviewHelper?.randomMedia?.blockingGet()
assertNotNull(randomMedia)
assertTrue(randomMedia is Media) assertTrue(randomMedia is Media)
verify(reviewInterface, times(1))!!.getRecentChanges(ArgumentMatchers.anyString())
} }
/** /**
* Test scenario when all media is already nominated for deletion * Test scenario when all media is already nominated for deletion
*/ */
@Test(expected = Exception::class) @Test(expected = RuntimeException::class)
fun getRandomMediaWithWithAllMediaNominatedForDeletion() { fun getRandomMediaWithWithAllMediaNominatedForDeletion() {
val recentChange = getMockRecentChange("test", "File:Test1.jpeg", 0)
val recentChange1 = getMockRecentChange("test", "File:Test2.png", 0)
val recentChange2 = getMockRecentChange("test", "File:Test3.jpg", 0)
`when`(okHttpJsonApiClient?.recentFileChanges)
.thenReturn(Single.just(listOf(recentChange, recentChange1, recentChange2)))
`when`(mediaWikiApi?.pageExists(ArgumentMatchers.anyString())) `when`(mediaWikiApi?.pageExists(ArgumentMatchers.anyString()))
.thenReturn(Single.just(true)) .thenReturn(Single.just(true))
reviewHelper?.randomMedia?.blockingGet() val media = reviewHelper?.randomMedia?.blockingGet()
assertNull(media)
verify(reviewInterface, times(1))!!.getRecentChanges(ArgumentMatchers.anyString())
} }
fun getMockRecentChange(type: String, title: String, oldRevisionId: Long): RecentChange { /**
* Test scenario when first media is already nominated for deletion
*/
@Test
fun getRandomMediaWithWithOneMediaNominatedForDeletion() {
`when`(mediaWikiApi?.pageExists("Commons:Deletion_requests/File:Test1.jpeg"))
.thenReturn(Single.just(true))
`when`(mediaWikiApi?.pageExists("Commons:Deletion_requests/File:Test2.png"))
.thenReturn(Single.just(false))
`when`(mediaWikiApi?.pageExists("Commons:Deletion_requests/File:Test3.jpg"))
.thenReturn(Single.just(true))
val media = reviewHelper?.randomMedia?.blockingGet()
assertNotNull(media)
assertTrue(media is Media)
verify(reviewInterface, times(1))!!.getRecentChanges(ArgumentMatchers.anyString())
}
private fun getMockRecentChange(type: String, title: String, oldRevisionId: Long): RecentChange {
val recentChange = mock(RecentChange::class.java) val recentChange = mock(RecentChange::class.java)
`when`(recentChange!!.type).thenReturn(type) `when`(recentChange!!.type).thenReturn(type)
`when`(recentChange.title).thenReturn(title) `when`(recentChange.title).thenReturn(title)
@ -88,9 +128,7 @@ class ReviewHelperTest {
*/ */
@Test @Test
fun getFirstRevisionOfFile() { fun getFirstRevisionOfFile() {
`when`(okHttpJsonApiClient?.getFirstRevisionOfFile(ArgumentMatchers.anyString())) val firstRevisionOfFile = reviewHelper?.getFirstRevisionOfFile("Test.jpg")?.blockingFirst()
.thenReturn(Single.just(mock(MwQueryPage.Revision::class.java)))
val firstRevisionOfFile = reviewHelper?.getFirstRevisionOfFile("Test.jpg")?.blockingGet()
assertTrue(firstRevisionOfFile is MwQueryPage.Revision) assertTrue(firstRevisionOfFile is MwQueryPage.Revision)
} }