Update master with backend overhaul branch (#2829)

* Beginnings of integration with Wikipedia client library. (#2642)

* Remove remaining unnecessary API version check.

* Roll up sleeves.

* Add and integrate the beginnings of app adapter.

* Remove vestigial event logging logic.

Event logging is no longer used in this app.

* Beginnings: remove StringUtils and associated redundancies.

* Remove redundant capitalize() method.

* Remove redundant urlEncode() method.

* Remove redundant (and incomplete) language lists.

* Remove redundant usages of SimpleDateFormat.

* Remove redundant json type adapter.

* Remove redundant MW error model classes.

* Rip out redundant MW model classes.

* Pass SessionManager into AppAdapter instead of injecting.

* Wire up more of the AppAdapter.

* Remove redundant Gson initialization and type adapters.

* Rip out PageTitle.

This was being used in some slightly incorrect/unexpected ways.

* Don't need static WikiSite.

* Bump data client library version

* Bump library version and fix build

* Fix tests

* Fix build

* Fix media of the day

* With fixes in recently modified APIs
This commit is contained in:
Vivek Maskara 2019-04-02 08:35:22 +05:30 committed by GitHub
parent 76e5a30fb5
commit dcbf076965
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
76 changed files with 424 additions and 2122 deletions

View file

@ -28,6 +28,7 @@ dependencies {
implementation 'com.jakewharton.rxbinding2:rxbinding-design:2.1.1'
implementation 'com.facebook.fresco:fresco:1.13.0'
implementation 'com.drewnoakes:metadata-extractor:2.11.0'
implementation 'com.dmitrybrant:wikimedia-android-data-client:0.0.12'
// UI
implementation 'fr.avianey.com.viewpagerindicator:library:2.4.1.1@aar'
@ -191,8 +192,6 @@ android {
buildConfigField "String", "HOME_URL", "\"https://commons.wikimedia.org/wiki/\""
buildConfigField "String", "COMMONS_URL", "\"https://commons.wikimedia.org\""
buildConfigField "String", "MOBILE_HOME_URL", "\"https://commons.m.wikimedia.org/wiki/\""
buildConfigField "String", "EVENTLOG_URL", "\"https://www.wikimedia.org/beacon/event\""
buildConfigField "String", "EVENTLOG_WIKI", "\"commonswiki\""
buildConfigField "String", "SIGNUP_LANDING_URL", "\"https://commons.m.wikimedia.org/w/index.php?title=Special:CreateAccount&returnto=Main+Page&returntoquery=welcome%3Dyes\""
buildConfigField "String", "SIGNUP_SUCCESS_REDIRECTION_URL", "\"https://commons.m.wikimedia.org/w/index.php?title=Main_Page&welcome=yes\""
buildConfigField "String", "FORGOT_PASSWORD_URL", "\"https://commons.wikimedia.org/wiki/Special:PasswordReset\""
@ -224,8 +223,6 @@ android {
buildConfigField "String", "HOME_URL", "\"https://commons.wikimedia.beta.wmflabs.org/wiki/\""
buildConfigField "String", "COMMONS_URL", "\"https://commons.wikimedia.beta.wmflabs.org\""
buildConfigField "String", "MOBILE_HOME_URL", "\"https://commons.m.wikimedia.beta.wmflabs.org/wiki/\""
buildConfigField "String", "EVENTLOG_URL", "\"https://commons.wikimedia.beta.wmflabs.org/beacon/event\""
buildConfigField "String", "EVENTLOG_WIKI", "\"commonswiki\""
buildConfigField "String", "SIGNUP_LANDING_URL", "\"https://commons.m.wikimedia.beta.wmflabs.org/w/index.php?title=Special:CreateAccount&returnto=Main+Page&returntoquery=welcome%3Dyes\""
buildConfigField "String", "SIGNUP_SUCCESS_REDIRECTION_URL", "\"https://commons.m.wikimedia.beta.wmflabs.org/w/index.php?title=Main_Page&welcome=yes\""
buildConfigField "String", "FORGOT_PASSWORD_URL", "\"https://commons.wikimedia.beta.wmflabs.org/wiki/Special:PasswordReset\""

View file

@ -5,7 +5,6 @@ import android.app.AlertDialog;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.text.Html;
import android.text.SpannableString;
import android.text.style.UnderlineSpan;
import android.view.Menu;
@ -17,6 +16,8 @@ import android.widget.LinearLayout;
import android.widget.Spinner;
import android.widget.TextView;
import org.wikipedia.util.StringUtil;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
@ -32,16 +33,6 @@ public class AboutActivity extends NavigationBaseActivity {
@BindView(R.id.about_license) HtmlTextView aboutLicenseText;
@BindView(R.id.about_faq) TextView faqText;
String language[] = { "Kazakh", "Afrikaans", "Arabic", "Bengali", "Asturianu", "azərbaycanca", "Bikol Central",
"Bulgarain", "বাংলা", "Bosanski", "Brezhoneg","català","کوردی", " čeština", " kaszëbsczi", "Cymraeg", "dansk", "Deutsch"
,"Zazaki", "डोटेली","Ελληνικά","euskara","español","فارسی","suomi", "français" ,"Nordfriisk", "galego", "Hawaiʻi"
,"हिन्दी","Hunsrik","עברית","hornjoserbsce","magyar","interlingua","Bahasa Indonesia", "íslenska","Italian","japanese",
"Basa Jawa", "ქართული", " ភាសាខ្មែរ","ಕನ್ನಡ", "한국어","къарачай-малкъар","Кыргызча", "latina", "Lëtzebuergesch", "lietuvių",
"latviešu", "Malagasy", "македонски"," മലയാളം","монгол","मराठी","Bahasa Melayu","Malti", "नेपाली", "norsk bokmål",
" Nederlands","occitan","ଓଡ଼ିଆ","ਪੰਜਾਬੀ","polsk","Piemontèis","پښتو","português","română","русский"," سنڌي", " සිංහල",
"slovenčina"," سرائیکی", "svenska", "தமிழ்", "ತುಳು"," తెలుగు"," ไทย", "Türkçe","українська", "اردو", "Tiếng Việt",
" მარგალური","ייִדיש",};
/**
* This method helps in the creation About screen
*
@ -66,11 +57,11 @@ public class AboutActivity extends NavigationBaseActivity {
TextView credits = findViewById(R.id.about_credits);
TextView faq = findViewById(R.id.about_faq);
rate_us.setText(Html.fromHtml(getString(R.string.about_rate_us)));
privacy_policy.setText(Html.fromHtml(getString(R.string.about_privacy_policy)));
translate.setText(Html.fromHtml(getString(R.string.about_translate)));
credits.setText(Html.fromHtml(getString(R.string.about_credits)));
faq.setText(Html.fromHtml(getString(R.string.about_faq)));
rate_us.setText(StringUtil.fromHtml(getString(R.string.about_rate_us)));
privacy_policy.setText(StringUtil.fromHtml(getString(R.string.about_privacy_policy)));
translate.setText(StringUtil.fromHtml(getString(R.string.about_translate)));
credits.setText(StringUtil.fromHtml(getString(R.string.about_credits)));
faq.setText(StringUtil.fromHtml(getString(R.string.about_faq)));
initDrawer();
}
@ -144,7 +135,7 @@ public class AboutActivity extends NavigationBaseActivity {
@OnClick(R.id.about_translate)
public void launchTranslate(View view) {
final ArrayAdapter<String> languageAdapter = new ArrayAdapter<>(AboutActivity.this,
android.R.layout.simple_spinner_dropdown_item, language);
android.R.layout.simple_spinner_dropdown_item, CommonsApplication.getInstance().getLanguageLookUpTable().getLocalizedNames());
final Spinner spinner = new Spinner(AboutActivity.this);
spinner.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT));
spinner.setAdapter(languageAdapter);
@ -156,11 +147,9 @@ public class AboutActivity extends NavigationBaseActivity {
builder.setTitle(R.string.about_translate_title)
.setMessage(R.string.about_translate_message)
.setPositiveButton(R.string.about_translate_proceed, (dialog, which) -> {
String languageSelected = spinner.getSelectedItem().toString();
TokensTranslations tokensTranslations = new TokensTranslations();
tokensTranslations.initailize();
String token = tokensTranslations.getTranslationToken(languageSelected);
Utils.handleWebUrl(AboutActivity.this,Uri.parse("https://translatewiki.net/w/i.php?title=Special:Translate&language="+token+"&group=commons-android-strings&filter=%21translated&action=translate ?"));
String langCode = CommonsApplication.getInstance().getLanguageLookUpTable().getCodes().get(spinner.getSelectedItemPosition());
Utils.handleWebUrl(AboutActivity.this,Uri.parse("https://translatewiki.net/w/i.php?title=Special:Translate&language="
+ langCode + "&group=commons-android-strings&filter=%21translated&action=translate ?"));
});
builder.setNegativeButton(R.string.about_translate_cancel, (dialog, which) -> finish());
builder.create().show();

View file

@ -0,0 +1,84 @@
package fr.free.nrw.commons;
import org.wikipedia.AppAdapter;
import org.wikipedia.dataclient.SharedPreferenceCookieManager;
import org.wikipedia.dataclient.WikiSite;
import org.wikipedia.json.GsonMarshaller;
import org.wikipedia.json.GsonUnmarshaller;
import org.wikipedia.login.LoginResult;
import androidx.annotation.NonNull;
import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.kvstore.JsonKvStore;
import okhttp3.OkHttpClient;
public class CommonsAppAdapter extends AppAdapter {
private final int DEFAULT_THUMB_SIZE = 640;
private final String COOKIE_STORE_NAME = "cookie_store";
private final SessionManager sessionManager;
private final JsonKvStore preferences;
CommonsAppAdapter(@NonNull SessionManager sessionManager, @NonNull JsonKvStore preferences) {
this.sessionManager = sessionManager;
this.preferences = preferences;
}
@Override
public String getMediaWikiBaseUrl() {
return BuildConfig.COMMONS_URL;
}
@Override
public String getRestbaseUriFormat() {
return BuildConfig.COMMONS_URL;
}
@Override
public OkHttpClient getOkHttpClient(@NonNull WikiSite wikiSite) {
return OkHttpConnectionFactory.getClient();
}
@Override
public int getDesiredLeadImageDp() {
return DEFAULT_THUMB_SIZE;
}
@Override
public boolean isLoggedIn() {
return sessionManager.isUserLoggedIn();
}
@Override
public String getUserName() {
return sessionManager.getUserName();
}
@Override
public String getPassword() {
return sessionManager.getPassword();
}
@Override
public void updateAccount(@NonNull LoginResult result) {
// TODO: sessionManager.updateAccount(result);
}
@Override
public SharedPreferenceCookieManager getCookies() {
if (!preferences.contains(COOKIE_STORE_NAME)) {
return null;
}
return GsonUnmarshaller.unmarshal(SharedPreferenceCookieManager.class, preferences.getString(COOKIE_STORE_NAME, null));
}
@Override
public void setCookies(@NonNull SharedPreferenceCookieManager cookies) {
preferences.putString(COOKIE_STORE_NAME, GsonMarshaller.marshal(cookies));
}
@Override
public boolean logErrorsInsteadOfCrashing() {
return false;
}
}

View file

@ -21,6 +21,8 @@ import org.acra.annotation.AcraCore;
import org.acra.annotation.AcraDialog;
import org.acra.annotation.AcraMailSender;
import org.acra.data.StringFormat;
import org.wikipedia.AppAdapter;
import org.wikipedia.language.AppLanguageLookUpTable;
import java.io.File;
@ -49,7 +51,12 @@ import io.reactivex.plugins.RxJavaPlugins;
import io.reactivex.schedulers.Schedulers;
import timber.log.Timber;
import static org.acra.ReportField.*;
import static org.acra.ReportField.ANDROID_VERSION;
import static org.acra.ReportField.APP_VERSION_CODE;
import static org.acra.ReportField.APP_VERSION_NAME;
import static org.acra.ReportField.PHONE_MODEL;
import static org.acra.ReportField.STACK_TRACE;
import static org.acra.ReportField.USER_COMMENT;
@AcraCore(
buildConfigClass = BuildConfig.class,
@ -97,6 +104,15 @@ public class CommonsApplication extends Application {
private RefWatcher refWatcher;
private static CommonsApplication INSTANCE;
public static CommonsApplication getInstance() {
return INSTANCE;
}
private AppLanguageLookUpTable languageLookUpTable;
public AppLanguageLookUpTable getLanguageLookUpTable() {
return languageLookUpTable;
}
/**
* Used to declare and initialize various components and dependencies
@ -104,6 +120,7 @@ public class CommonsApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
INSTANCE = this;
ACRA.init(this);
ApplicationlessInjection
@ -111,6 +128,8 @@ public class CommonsApplication extends Application {
.getCommonsApplicationComponent()
.inject(this);
AppAdapter.set(new CommonsAppAdapter(sessionManager, defaultPrefs));
initTimber();
// Set DownsampleEnabled to True to downsample the image in case it's heavy
@ -130,6 +149,8 @@ public class CommonsApplication extends Application {
createNotificationChannel(this);
languageLookUpTable = new AppLanguageLookUpTable(this);
// This handler will catch exceptions thrown from Observables after they are disposed,
// or from Observables that are (deliberately or not) missing an onError handler.
RxJavaPlugins.setErrorHandler(Functions.emptyConsumer());
@ -185,6 +206,10 @@ public class CommonsApplication extends Application {
}
}
public String getUserAgent() {
return "Commons/" + ConfigUtils.getVersionNameWithSha(this) + " (https://mediawiki.org/wiki/Apps/Commons) Android/" + Build.VERSION.RELEASE;
}
/**
* Helps in setting up LeakCanary library
* @return instance of LeakCanary

View file

@ -4,6 +4,15 @@ import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import org.apache.commons.lang3.StringUtils;
import org.wikipedia.dataclient.mwapi.MwQueryPage;
import org.wikipedia.gallery.ExtMetadata;
import org.wikipedia.gallery.ImageInfo;
import org.wikipedia.page.PageTitle;
import org.wikipedia.util.DateUtil;
import org.wikipedia.util.StringUtil;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
@ -11,17 +20,11 @@ import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.media.model.ExtMetadata;
import fr.free.nrw.commons.media.model.ImageInfo;
import fr.free.nrw.commons.media.model.MwQueryPage;
import fr.free.nrw.commons.utils.DateUtils;
import fr.free.nrw.commons.utils.MediaDataExtractorUtil;
import fr.free.nrw.commons.utils.StringUtils;
public class Media implements Parcelable {
@ -37,7 +40,6 @@ public class Media implements Parcelable {
}
};
private static Pattern displayTitlePattern = Pattern.compile("(.*)(\\.\\w+)", Pattern.CASE_INSENSITIVE);
// Primary metadata fields
protected Uri localUri;
protected String imageUrl;
@ -88,7 +90,7 @@ public class Media implements Parcelable {
* @param creator Media creator
*/
public Media(Uri localUri, String imageUrl, String filename, String description,
long dataLength, Date dateCreated, @Nullable Date dateUploaded, String creator) {
long dataLength, Date dateCreated, Date dateUploaded, String creator) {
this();
this.localUri = localUri;
this.imageUrl = imageUrl;
@ -144,26 +146,16 @@ public class Media implements Parcelable {
* Gets media display title
* @return Media title
*/
public String getDisplayTitle() {
if (filename == null) {
return "";
}
// FIXME: Gross hack because my regex skills suck maybe or I am too lazy who knows
String title = getFilePageTitle().getDisplayText().replaceFirst("^File:", "");
Matcher matcher = displayTitlePattern.matcher(title);
if (matcher.matches()) {
return matcher.group(1);
} else {
return title;
}
@NonNull public String getDisplayTitle() {
return filename != null ? getPageTitle().getDisplayTextWithoutNamespace().replaceFirst("[.][^.]+$", "") : "";
}
/**
* Gets file page title
* @return New media page title
*/
public PageTitle getFilePageTitle() {
return new PageTitle("File:" + getFilename().replaceFirst("^File:", ""));
@NonNull public PageTitle getPageTitle() {
return Utils.getPageTitle(getFilename());
}
/**
@ -344,7 +336,7 @@ public class Media implements Parcelable {
@Nullable
public static Media from(MwQueryPage page) {
ImageInfo imageInfo = page.imageInfo();
if(imageInfo == null) {
if (imageInfo == null) {
return null;
}
ExtMetadata metadata = imageInfo.getMetadata();
@ -358,13 +350,13 @@ public class Media implements Parcelable {
page.title(),
"",
0,
DateUtils.getDateFromString(metadata.dateTimeOriginal().value()),
DateUtils.getDateFromString(metadata.dateTime().value()),
StringUtils.getParsedStringFromHtml(metadata.artist().value())
safeParseDate(metadata.dateTimeOriginal().value()),
safeParseDate(metadata.dateTime().value()),
StringUtil.fromHtml(metadata.artist().value()).toString()
);
String language = Locale.getDefault().getLanguage();
if (StringUtils.isNullOrWhiteSpace(language)) {
if (StringUtils.isBlank(language)) {
language = "default";
}
@ -373,7 +365,7 @@ public class Media implements Parcelable {
String latitude = metadata.gpsLatitude().value();
String longitude = metadata.gpsLongitude().value();
if(!StringUtils.isNullOrWhiteSpace(latitude) && !StringUtils.isNullOrWhiteSpace(longitude)) {
if (!StringUtils.isBlank(latitude) && !StringUtils.isBlank(longitude)) {
LatLng latLng = new LatLng(Double.parseDouble(latitude), Double.parseDouble(longitude), 0);
media.setCoordinates(latLng);
}
@ -467,6 +459,14 @@ public class Media implements Parcelable {
}
}
@Nullable private static Date safeParseDate(String dateStr) {
try {
return DateUtil.getIso8601DateFormatShort().parse(dateStr);
} catch (ParseException e) {
return null;
}
}
/**
* Method of Parcelable interface
* @return zero

View file

@ -7,6 +7,8 @@ import android.util.AttributeSet;
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
import com.facebook.drawee.view.SimpleDraweeView;
import org.apache.commons.lang3.StringUtils;
import javax.inject.Inject;
import androidx.annotation.Nullable;
@ -14,7 +16,6 @@ import androidx.collection.LruCache;
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
import fr.free.nrw.commons.di.ApplicationlessInjection;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
import fr.free.nrw.commons.utils.StringUtils;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
@ -56,7 +57,7 @@ public class MediaWikiImageView extends SimpleDraweeView {
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(thumbnail -> {
if (!StringUtils.isNullOrWhiteSpace(thumbnail)) {
if (!StringUtils.isBlank(thumbnail)) {
setImageUrl(thumbnail);
}
}, throwable -> Timber.e(throwable, "Error occurred while fetching thumbnail"));
@ -89,8 +90,10 @@ public class MediaWikiImageView extends SimpleDraweeView {
}
//TODO: refactor the logic for thumbnails. ImageInfo API can be used to fetch thumbnail upfront
/**
* Fetches media thumbnail from the server
*
* @param media
* @return
*/

View file

@ -0,0 +1,61 @@
package fr.free.nrw.commons;
import org.wikipedia.dataclient.SharedPreferenceCookieManager;
import org.wikipedia.dataclient.okhttp.HttpStatusException;
import java.io.File;
import java.io.IOException;
import androidx.annotation.NonNull;
import okhttp3.Cache;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.logging.HttpLoggingInterceptor;
public final class OkHttpConnectionFactory {
private static final String CACHE_DIR_NAME = "okhttp-cache";
private static final long NET_CACHE_SIZE = 64 * 1024 * 1024;
@NonNull private static final Cache NET_CACHE = new Cache(new File(CommonsApplication.getInstance().getCacheDir(),
CACHE_DIR_NAME), NET_CACHE_SIZE);
@NonNull private static OkHttpClient CLIENT = createClient();
@NonNull public static OkHttpClient getClient() {
return CLIENT;
}
@NonNull
private static OkHttpClient createClient() {
return new OkHttpClient.Builder()
.cookieJar(SharedPreferenceCookieManager.getInstance())
.cache(NET_CACHE)
.addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BASIC))
.addInterceptor(new UnsuccessfulResponseInterceptor())
.addInterceptor(new CommonHeaderRequestInterceptor())
.build();
}
private static class CommonHeaderRequestInterceptor implements Interceptor {
@Override @NonNull public Response intercept(@NonNull Chain chain) throws IOException {
Request request = chain.request().newBuilder()
.header("User-Agent", CommonsApplication.getInstance().getUserAgent())
.build();
return chain.proceed(request);
}
}
public static class UnsuccessfulResponseInterceptor implements Interceptor {
@Override @NonNull public Response intercept(@NonNull Chain chain) throws IOException {
Response rsp = chain.proceed(chain.request());
if (rsp.isSuccessful()) {
return rsp;
}
throw new HttpStatusException(rsp);
}
}
private OkHttpConnectionFactory() {
}
}

View file

@ -1,95 +0,0 @@
package fr.free.nrw.commons;
import android.net.Uri;
import androidx.annotation.NonNull;
public class PageTitle {
private final String namespace;
private final String titleKey;
/**
* Construct from a namespace-prefixed page name.
* @param prefixedText namespace-prefixed page name
*/
public PageTitle(@NonNull String prefixedText) {
String[] segments = prefixedText.trim().replace(" ", "_").split(":", 2);
// Canonicalize and capitalize page title and namespace (if present)
if (segments.length == 2) {
namespace = Utils.capitalize(segments[0]);
titleKey = Utils.capitalize(segments[1]);
} else {
namespace = "";
titleKey = Utils.capitalize(segments[0]);
}
}
/**
* Get the canonicalized title for displaying (such as "File:My example.jpg").
*
* @return canonical title
*/
@NonNull
public String getPrefixedText() {
if (namespace.isEmpty()) {
return titleKey;
} else {
return namespace + ":" + titleKey;
}
}
/**
* Get the canonical title for DB and URLs (such as "File:My_example.jpg").
*
* @return canonical title
*/
@NonNull
public String getDisplayText() {
return getPrefixedText().replace("_", " ");
}
/**
* Convert to a URI
* (such as "https://commons.wikimedia.org/wiki/File:My_example.jpg").
*
* @return URI
*/
@NonNull
public Uri getCanonicalUri() {
String uriStr = BuildConfig.HOME_URL + Uri.encode(getPrefixedText(), ":/");
return Uri.parse(uriStr);
}
/**
* Convert to a mobile URI
* (such as "https://commons.m.wikimedia.org/wiki/File:My_example.jpg").
*
* @return URI
*/
@NonNull
public Uri getMobileUri() {
String uriStr = BuildConfig.MOBILE_HOME_URL + Uri.encode(getPrefixedText(), ":/");
return Uri.parse(uriStr);
}
/**
* Get the canonical title without namespace.
* @return title
*/
@NonNull
public String getText() {
return titleKey;
}
/**
* Gets the canonicalized title for displaying (such as "File:My example.jpg").
* </p>
* Essentially equivalent to getPrefixedText
* @return canonical title as a String
*/
@Override
public String toString() {
return getPrefixedText();
}
}

View file

@ -1,105 +0,0 @@
package fr.free.nrw.commons;
import java.util.HashMap;
/**
* Created by Dell on 3/16/2018.
*/
public class TokensTranslations {
HashMap<String,String> translationToken = new HashMap<>();
public void initailize() {
translationToken.put("Kazakh", "ab");
translationToken.put("Afrikaans", "af");
translationToken.put("Arabic", "ar");
translationToken.put("Bengali", "as");
translationToken.put("Asturianu", "ast");
translationToken.put("azərbaycanca", "az");
translationToken.put("Bikol Central", "bcl");
translationToken.put("Bulgarain","bg");
translationToken.put("বাংলা", "bn");
translationToken.put("Brezhoneg", "br");
translationToken.put("Bosanski", "bs");
translationToken.put("català", "ca");
translationToken.put("کوردی","ckb");
translationToken.put("čeština", "cs");
translationToken.put("kaszëbsczi", "csb");
translationToken.put("Cymraeg", "cy");
translationToken.put("dansk", "da");
translationToken.put("Deutsch", "de");
translationToken.put("Zazaki", "diq");
translationToken.put("डोटेली","diq");
translationToken.put("Ελληνικά","el");
translationToken.put("euskara","eu");
translationToken.put("español", "es");
translationToken.put("فارسی","fa");
translationToken.put("suomi", "fi");
translationToken.put("føroyskt", "fo");
translationToken.put("français", "fr");
translationToken.put("Nordfriisk", "frr");
translationToken.put("galego", "gr");
translationToken.put("Hawaiʻi", "haw");
translationToken.put("עברית","he");
translationToken.put("हिन्दी","hi");
translationToken.put("Hunsrik", "hrx");
translationToken.put("hornjoserbsce", "hsb");
translationToken.put("magyar","hu");
translationToken.put("interlingua","ia");
translationToken.put("Bahasa Indonesia", "id");
translationToken.put("íslenska","is");
translationToken.put("Italian","it");
translationToken.put("japanese","ja");
translationToken.put("Basa Jawa","jv");
translationToken.put("ქართული", "ka");
translationToken.put("Taqbaylit","kab");
translationToken.put(" ភាសាខ្មែរ","km");
translationToken.put("ಕನ್ನಡ", "kn");
translationToken.put("한국어", "ko");
translationToken.put("къарачай-малкъар","krc");
translationToken.put("Кыргызча","ky");
translationToken.put("latina","la");
translationToken.put("Lëtzebuergesch","lb");
translationToken.put("lietuvių", "lt");
translationToken.put("latviešu","lv");
translationToken.put("Malagasy","mg");
translationToken.put("македонски", "mk");
translationToken.put("മലയാളം","ml");
translationToken.put("монгол","mn");
translationToken.put("मराठी","mr");
translationToken.put("Bahasa Melayu","ms");
translationToken.put("Malti","mt");
translationToken.put("norsk bokmål", "nb");
translationToken.put("नेपाली","ne");
translationToken.put("Nederlands","nl");
translationToken.put("occitan","oc");
translationToken.put("ଓଡ଼ିଆ","or");
translationToken.put("ਪੰਜਾਬੀ","pa");
translationToken.put("polsk", "pl");
translationToken.put("Piemontèis","pms");
translationToken.put("پښتو","ps");
translationToken.put("português","pt");
translationToken.put("română","ro");
translationToken.put("русский","ru");
translationToken.put(" سنڌي","sd");
translationToken.put(" සිංහල","si");
translationToken.put("slovenčina","sk");
translationToken.put(" سرائیکی","skr");
translationToken.put("Basa Sunda","su");
translationToken.put("svenska","sv");
translationToken.put("தமிழ்", "ta");
translationToken.put("ತುಳು", "tcy");
translationToken.put(" తెలుగు","te");
translationToken.put(" ไทย","th");
translationToken.put("Türkçe","tr");
translationToken.put("українська","uk");
translationToken.put("اردو","ur");
translationToken.put("Tiếng Việt","vi");
translationToken.put(" მარგალური", "xmf");
translationToken.put("ייִדיש","yi");
}
public String getTranslationToken ( String language){
return translationToken.get(language);
}
}

View file

@ -11,13 +11,11 @@ import android.widget.Toast;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.digest.DigestUtils;
import org.wikipedia.dataclient.WikiSite;
import org.wikipedia.page.PageTitle;
import org.wikipedia.util.UriUtil;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import androidx.annotation.NonNull;
@ -32,20 +30,8 @@ import static android.widget.Toast.LENGTH_SHORT;
public class Utils {
/**
* Strips localization symbols from a string.
* Removes the suffix after "@" and quotes.
*
* @param s string possibly containing localization symbols
* @return stripped string
*/
public static String stripLocalizedString(String s) {
Matcher matcher = Pattern.compile("\\\"(.*)\\\"(@\\w+)?").matcher(s);
if (matcher.find()) {
return matcher.group(1);
} else {
return s;
}
public static PageTitle getPageTitle(@NonNull String title) {
return new PageTitle(title, new WikiSite(BuildConfig.COMMONS_URL));
}
/**
@ -55,36 +41,9 @@ public class Utils {
* @return URL of thumbnail
*/
public static String makeThumbBaseUrl(@NonNull String filename) {
String name = new PageTitle(filename).getPrefixedText();
String name = getPageTitle(filename).getPrefixedText();
String sha = new String(Hex.encodeHex(DigestUtils.md5(name)));
return String.format("%s/%s/%s/%s", BuildConfig.IMAGE_URL_BASE, sha.substring(0, 1), sha.substring(0, 2), urlEncode(name));
}
/**
* URL Encode an URL in UTF-8 format
* @param url Unformatted URL
* @return Encoded URL
*/
public static String urlEncode(String url) {
try {
return URLEncoder.encode(url, "utf-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
/**
* Capitalizes the first character of a string.
*
* @param string String to alter
* @return string with capitalized first character
*/
public static String capitalize(String string) {
if (string.length() > 0) {
return string.substring(0, 1).toUpperCase(Locale.getDefault()) + string.substring(1);
} else {
return string;
}
return String.format("%s/%s/%s/%s", BuildConfig.IMAGE_URL_BASE, sha.substring(0, 1), sha.substring(0, 2), UriUtil.encodeURL(name));
}
/**
@ -234,15 +193,6 @@ public class Utils {
return bitmap;
}
public static <K,V> Map<K,V> arraysToMap(K[] kArray, V[] vArray){
if(kArray.length!=vArray.length)
throw new RuntimeException("arraysToMap array sizes don't match");
Map<K,V> map=new LinkedHashMap<>();
for (int i=0;i<vArray.length;i++){
map.put(kArray[i], vArray[i]);
}
return map;
}
/*
*Copies the content to the clipboard
*

View file

@ -3,12 +3,13 @@ package fr.free.nrw.commons;
import android.net.Uri;
import androidx.annotation.Nullable;
import androidx.viewpager.widget.PagerAdapter;
import android.text.Html;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import org.wikipedia.util.StringUtil;
public class WelcomePagerAdapter extends PagerAdapter {
private static final int[] PAGE_LAYOUTS = new int[]{
R.layout.welcome_wikipedia,
@ -61,7 +62,7 @@ public class WelcomePagerAdapter extends PagerAdapter {
if (position == PAGE_FINAL) {
// Add link to more information
TextView moreInfo = layout.findViewById(R.id.welcomeInfo);
moreInfo.setText(Html.fromHtml(WelcomeActivity.moreInformation));
moreInfo.setText(StringUtil.fromHtml(WelcomeActivity.moreInformation));
moreInfo.setOnClickListener(view -> {
try {
Utils.handleWebUrl(

View file

@ -26,6 +26,8 @@ import android.widget.TextView;
import com.dinuscxj.progressbar.CircleProgressBar;
import org.apache.commons.lang3.StringUtils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
@ -42,7 +44,6 @@ import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient;
import fr.free.nrw.commons.theme.NavigationBaseActivity;
import fr.free.nrw.commons.utils.StringUtils;
import fr.free.nrw.commons.utils.ViewUtil;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
@ -243,7 +244,7 @@ public class AchievementsActivity extends NavigationBaseActivity {
@SuppressLint("CheckResult")
private void setWikidataEditCount() {
String userName = sessionManager.getUserName();
if (StringUtils.isNullOrWhiteSpace(userName)) {
if (StringUtils.isBlank(userName)) {
return;
}
compositeDisposable.add(okHttpJsonApiClient.getWikidataEdits(userName)

View file

@ -41,7 +41,6 @@ import butterknife.OnClick;
import butterknife.OnEditorAction;
import butterknife.OnFocusChange;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.PageTitle;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.WelcomeActivity;
@ -192,22 +191,6 @@ public class LoginActivity extends AccountAuthenticatorActivity {
Utils.handleWebUrl(this, Uri.parse(BuildConfig.PRIVACY_POLICY_URL));
}
@OnClick(R.id.login_button)
void performLogin() {
loginCurrentlyInProgress = true;
Timber.d("Login to start!");
final String username = canonicializeUsername(usernameEdit.getText().toString());
final String rawUsername = Utils.capitalize(usernameEdit.getText().toString().trim());
final String password = passwordEdit.getText().toString();
String twoFactorCode = twoFactorEdit.getText().toString();
showLoggingProgressBar();
compositeDisposable.add(Observable.fromCallable(() -> login(username, password, twoFactorCode))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(result -> handleLogin(username, rawUsername, password, result)));
}
@OnClick(R.id.sign_up_button)
void signUp() {
Intent intent = new Intent(this, SignupActivity.class);
@ -259,6 +242,22 @@ public class LoginActivity extends AccountAuthenticatorActivity {
super.onDestroy();
}
@OnClick(R.id.login_button)
public void performLogin() {
loginCurrentlyInProgress = true;
Timber.d("Login to start!");
final String username = usernameEdit.getText().toString();
final String rawUsername = usernameEdit.getText().toString().trim();
final String password = passwordEdit.getText().toString();
String twoFactorCode = twoFactorEdit.getText().toString();
showLoggingProgressBar();
compositeDisposable.add(Observable.fromCallable(() -> login(username, password, twoFactorCode))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(result -> handleLogin(username, rawUsername, password, result)));
}
private String login(String username, String password, String twoFactorCode) {
try {
if (twoFactorCode.isEmpty()) {
@ -364,16 +363,6 @@ public class LoginActivity extends AccountAuthenticatorActivity {
}
}
/**
* Because Mediawiki is upercase-first-char-then-case-sensitive :)
*
* @param username String
* @return String canonicial username
*/
private String canonicializeUsername(String username) {
return new PageTitle(username).getText();
}
@Override
protected void onStart() {
super.onStart();

View file

@ -14,7 +14,6 @@ import javax.inject.Singleton;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
import io.reactivex.Completable;
import io.reactivex.Observable;

View file

@ -6,8 +6,9 @@ import android.util.AttributeSet;
import android.view.View;
import android.widget.TextView;
import org.wikipedia.util.DateUtil;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import androidx.annotation.NonNull;
@ -85,13 +86,11 @@ public class CampaignView extends SwipableCardView {
if (campaign != null) {
tvTitle.setText(campaign.getTitle());
tvDescription.setText(campaign.getDescription());
SimpleDateFormat inputDateFormat = new SimpleDateFormat("yyyy-MM-dd");
SimpleDateFormat outputDateFormat = new SimpleDateFormat("dd MMM");
try {
Date startDate = inputDateFormat.parse(campaign.getStartDate());
Date endDate = inputDateFormat.parse(campaign.getEndDate());
tvDates.setText(String.format("%1s - %2s", outputDateFormat.format(startDate),
outputDateFormat.format(endDate)));
Date startDate = DateUtil.getIso8601DateFormatShort().parse(campaign.getStartDate());
Date endDate = DateUtil.getIso8601DateFormatShort().parse(campaign.getEndDate());
tvDates.setText(String.format("%1s - %2s", DateUtil.getExtraShortDateString(startDate),
DateUtil.getExtraShortDateString(endDate)));
} catch (ParseException e) {
e.printStackTrace();
}

View file

@ -2,8 +2,9 @@ package fr.free.nrw.commons.campaigns;
import android.annotation.SuppressLint;
import org.wikipedia.util.DateUtil;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.List;
@ -71,7 +72,6 @@ public class CampaignsPresenter implements BasePresenter {
@Override public void onSuccess(CampaignResponseDTO campaignResponseDTO) {
List<Campaign> campaigns = campaignResponseDTO.getCampaigns();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
if (campaigns == null || campaigns.isEmpty()) {
Timber.e("The campaigns list is empty");
view.showCampaigns(null);
@ -79,8 +79,8 @@ public class CampaignsPresenter implements BasePresenter {
Collections.sort(campaigns, (campaign, t1) -> {
Date date1, date2;
try {
date1 = dateFormat.parse(campaign.getStartDate());
date2 = dateFormat.parse(t1.getStartDate());
date1 = DateUtil.getIso8601DateFormatShort().parse(campaign.getStartDate());
date2 = DateUtil.getIso8601DateFormatShort().parse(t1.getStartDate());
} catch (ParseException e) {
e.printStackTrace();
return -1;
@ -91,9 +91,8 @@ public class CampaignsPresenter implements BasePresenter {
Date currentDate = new Date();
try {
for (Campaign aCampaign : campaigns) {
campaignEndDate = dateFormat.parse(aCampaign.getEndDate());
campaignStartDate =
dateFormat.parse(aCampaign.getStartDate());
campaignEndDate = DateUtil.getIso8601DateFormatShort().parse(aCampaign.getEndDate());
campaignStartDate = DateUtil.getIso8601DateFormatShort().parse(aCampaign.getStartDate());
if (campaignEndDate.compareTo(currentDate) >= 0
&& campaignStartDate.compareTo(currentDate) <= 0) {
campaign = aCampaign;

View file

@ -3,6 +3,7 @@ package fr.free.nrw.commons.category;
import android.content.Context;
import android.content.Intent;
import android.database.DataSetObserver;
import android.net.Uri;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
@ -22,7 +23,6 @@ import androidx.viewpager.widget.ViewPager;
import butterknife.BindView;
import butterknife.ButterKnife;
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.PageTitle;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.explore.ViewPagerAdapter;
@ -219,7 +219,7 @@ public class CategoryDetailsActivity extends NavigationBaseActivity
// Handle item selection
switch (item.getItemId()) {
case R.id.menu_browser_current_category:
Utils.handleWebUrl(this, new PageTitle(categoryName).getCanonicalUri());
Utils.handleWebUrl(this, Uri.parse(Utils.getPageTitle(categoryName).getCanonicalUri()));
return true;
default:
return super.onOptionsItemSelected(item);

View file

@ -1,13 +1,22 @@
package fr.free.nrw.commons.category;
import androidx.annotation.NonNull;
import org.apache.commons.lang3.StringUtils;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.wikipedia.util.StringUtil;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.annotation.Nullable;
import fr.free.nrw.commons.Media;
import timber.log.Timber;
public class CategoryImageUtils {
/**

View file

@ -3,11 +3,14 @@ package fr.free.nrw.commons.contributions;
import android.content.Context;
import android.net.Uri;
import android.os.Parcel;
import org.apache.commons.lang3.StringUtils;
import org.wikipedia.util.DateUtil;
import androidx.annotation.NonNull;
import androidx.annotation.StringDef;
import java.lang.annotation.Retention;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
@ -18,7 +21,6 @@ import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.filepicker.UploadableFile;
import fr.free.nrw.commons.settings.Prefs;
import fr.free.nrw.commons.utils.ConfigUtils;
import fr.free.nrw.commons.utils.StringUtils;
import static java.lang.annotation.RetentionPolicy.SOURCE;
@ -168,7 +170,7 @@ public class Contribution extends Media {
.append("|author=[[User:").append(creator).append("|").append(creator).append("]]\n");
String templatizedCreatedDate = getTemplatizedCreatedDate();
if (!StringUtils.isNullOrWhiteSpace(templatizedCreatedDate)) {
if (!StringUtils.isBlank(templatizedCreatedDate)) {
buffer.append("|date=").append(templatizedCreatedDate);
}
@ -201,8 +203,7 @@ public class Contribution extends Media {
private String getTemplatizedCreatedDate() {
if (dateCreated != null) {
if (UploadableFile.DateTimeWithSource.EXIF_SOURCE.equals(dateCreatedSource)) {
SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH);
return String.format(Locale.ENGLISH, TEMPLATE_DATE_ACC_TO_EXIF, isoFormat.format(dateCreated)) + "\n";
return String.format(Locale.ENGLISH, TEMPLATE_DATE_ACC_TO_EXIF, DateUtil.getIso8601DateFormatShort().format(dateCreated)) + "\n";
} else {
Calendar calendar = Calendar.getInstance();
calendar.setTime(dateCreated);

View file

@ -9,8 +9,10 @@ import android.net.Uri;
import android.os.RemoteException;
import androidx.annotation.Nullable;
import android.text.TextUtils;
import org.apache.commons.lang3.StringUtils;
import fr.free.nrw.commons.settings.Prefs;
import fr.free.nrw.commons.utils.StringUtils;
import java.util.Date;
import javax.inject.Inject;
import javax.inject.Named;
@ -144,7 +146,7 @@ public class ContributionDao {
);
String wikidataEntityId = cursor.getString(cursor.getColumnIndex(COLUMN_WIKI_DATA_ENTITY_ID));
if (!StringUtils.isNullOrWhiteSpace(wikidataEntityId)) {
if (!StringUtils.isBlank(wikidataEntityId)) {
contribution.setWikiDataEntityId(wikidataEntityId);
}

View file

@ -11,13 +11,12 @@ import android.os.Bundle;
import android.os.RemoteException;
import android.text.TextUtils;
import org.wikipedia.util.DateUtil;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import javax.inject.Inject;
import javax.inject.Named;
@ -25,7 +24,6 @@ import javax.inject.Named;
import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.di.ApplicationlessInjection;
import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.mwapi.LogEventResult;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
import timber.log.Timber;
@ -151,13 +149,7 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
done = true;
}
}
defaultKvStore.putString("lastSyncTimestamp", toMWDate(curTime));
defaultKvStore.putString("lastSyncTimestamp", DateUtil.getIso8601DateFormat().format(curTime));
Timber.d("Oh hai, everyone! Look, a kitty!");
}
private String toMWDate(Date date) {
SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH); // Assuming MW always gives me UTC
isoFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
return isoFormat.format(date);
}
}

View file

@ -3,7 +3,8 @@ package fr.free.nrw.commons.delete;
import android.accounts.Account;
import android.content.Context;
import java.text.SimpleDateFormat;
import org.wikipedia.util.DateUtil;
import java.util.Date;
import java.util.Locale;
@ -40,8 +41,7 @@ public class ReasonBuilder {
if (date == null || date.toString() == null || date.toString().isEmpty()) {
return "Uploaded date not available";
}
SimpleDateFormat formatter = new SimpleDateFormat("dd MMM yyyy", Locale.getDefault());
return formatter.format(date);
return DateUtil.getDateStringWithSkeletonPattern(date,"dd MMM yyyy");
}
private Single<String> fetchArticleNumber(Media media, String reason) {

View file

@ -6,6 +6,8 @@ import android.net.Uri;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.wikipedia.json.GsonUtil;
import java.io.File;
import java.util.concurrent.TimeUnit;
@ -20,8 +22,6 @@ import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.mwapi.ApacheHttpClientMediaWikiApi;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient;
import fr.free.nrw.commons.utils.UriDeserializer;
import fr.free.nrw.commons.utils.UriSerializer;
import okhttp3.Cache;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
@ -107,10 +107,7 @@ public class NetworkingModule {
@Provides
@Singleton
public Gson provideGson() {
return new GsonBuilder()
.registerTypeAdapter(Uri.class, new UriSerializer())
.registerTypeAdapter(Uri.class, new UriDeserializer())
.create();
return GsonUtil.getDefaultGson();
}
}

View file

@ -9,7 +9,6 @@ import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Parcelable;
import android.preference.PreferenceManager;
import android.provider.MediaStore;
@ -65,11 +64,8 @@ public class FilePicker implements Constants {
private static Intent createGalleryIntent(@NonNull Context context, int type) {
storeType(context, type);
Intent intent = plainGalleryPickerIntent();
if (Build.VERSION.SDK_INT >= 18) {
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, configuration(context).allowsMultiplePickingInGallery());
}
return intent;
return plainGalleryPickerIntent()
.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, configuration(context).allowsMultiplePickingInGallery());
}
private static Intent createCameraForImageIntent(@NonNull Context context, int type) {

View file

@ -1,34 +0,0 @@
package fr.free.nrw.commons.json;
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
public class PostProcessingTypeAdapter implements TypeAdapterFactory {
public interface PostProcessable {
void postProcess();
}
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
return new TypeAdapter<T>() {
public void write(JsonWriter out, T value) throws IOException {
delegate.write(out, value);
}
public T read(JsonReader in) throws IOException {
T obj = delegate.read(in);
if (obj instanceof PostProcessable) {
((PostProcessable)obj).postProcess();
}
return obj;
}
};
}
}

View file

@ -7,7 +7,6 @@ import android.database.DataSetObserver;
import android.net.Uri;
import android.os.Bundle;
import android.text.Editable;
import android.text.Html;
import android.text.TextWatcher;
import android.util.TypedValue;
import android.view.LayoutInflater;
@ -23,6 +22,10 @@ import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import org.apache.commons.lang3.StringUtils;
import org.wikipedia.util.DateUtil;
import org.wikipedia.util.StringUtil;
import java.util.ArrayList;
import java.util.Date;
import java.util.Locale;
@ -44,9 +47,6 @@ import fr.free.nrw.commons.delete.ReasonBuilder;
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
import fr.free.nrw.commons.ui.widget.CompatTextView;
import fr.free.nrw.commons.ui.widget.HtmlTextView;
import fr.free.nrw.commons.utils.DateUtils;
import fr.free.nrw.commons.utils.StringUtils;
import fr.free.nrw.commons.utils.ViewUtil;
import fr.free.nrw.commons.utils.ViewUtilWrapper;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
@ -183,7 +183,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
final View view = inflater.inflate(R.layout.fragment_media_detail, container, false);
ButterKnife.bind(this,view);
seeMore.setText(Html.fromHtml(getString(R.string.nominated_see_more)));
seeMore.setText(StringUtil.fromHtml(getString(R.string.nominated_see_more)));
if (isCategoryImage){
authorLayout.setVisibility(VISIBLE);
@ -319,7 +319,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
@OnClick(R.id.mediaDetailLicense)
public void onMediaDetailLicenceClicked(){
String url = media.getLicenseUrl();
if (!StringUtils.isNullOrWhiteSpace(url) && getActivity() != null) {
if (!StringUtils.isBlank(url) && getActivity() != null) {
Utils.handleWebUrl(getActivity(), Uri.parse(url));
} else {
viewUtil.showShortToast(getActivity(), getString(R.string.null_url));
@ -422,7 +422,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
@OnClick(R.id.seeMore)
public void onSeeMoreClicked(){
if (nominatedForDeletion.getVisibility() == VISIBLE && getActivity() != null) {
Utils.handleWebUrl(getActivity(), media.getFilePageTitle().getMobileUri());
Utils.handleWebUrl(getActivity(), Uri.parse(media.getPageTitle().getMobileUri()));
}
}
@ -516,7 +516,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
if (date == null || date.toString() == null || date.toString().isEmpty()) {
return "Uploaded date not available";
}
return DateUtils.dateInLocaleFormat(date);
return DateUtil.getDateStringWithSkeletonPattern(date, "dd MMM yyyy");
}
/**

View file

@ -159,12 +159,12 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
case R.id.menu_share_current_image:
Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType("text/plain");
shareIntent.putExtra(Intent.EXTRA_TEXT, m.getDisplayTitle() + " \n" + m.getFilePageTitle().getCanonicalUri());
shareIntent.putExtra(Intent.EXTRA_TEXT, m.getDisplayTitle() + " \n" + m.getPageTitle().getCanonicalUri());
startActivity(Intent.createChooser(shareIntent, "Share image via..."));
return true;
case R.id.menu_browser_current_image:
// View in browser
handleWebUrl(requireContext(), m.getFilePageTitle().getMobileUri());
handleWebUrl(requireContext(), Uri.parse(m.getPageTitle().getMobileUri()));
return true;
case R.id.menu_download_current_image:
// Download

View file

@ -1,99 +0,0 @@
package fr.free.nrw.commons.media.model;
import com.google.gson.annotations.SerializedName;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import fr.free.nrw.commons.utils.StringUtils;
public class ExtMetadata {
@SuppressWarnings("unused") @SerializedName("DateTime") @Nullable
private Values dateTime;
@SuppressWarnings("unused") @SerializedName("ObjectName") @Nullable private Values objectName;
@SuppressWarnings("unused") @SerializedName("CommonsMetadataExtension") @Nullable private Values commonsMetadataExtension;
@SuppressWarnings("unused") @SerializedName("Categories") @Nullable private Values categories;
@SuppressWarnings("unused") @SerializedName("Assessments") @Nullable private Values assessments;
@SuppressWarnings("unused")
@SerializedName("ImageDescription")
@Nullable
private Values imageDescription;
@SuppressWarnings("unused") @SerializedName("GPSLatitude") @Nullable private Values gpsLatitude;
@SuppressWarnings("unused") @SerializedName("GPSLongitude") @Nullable private Values gpsLongitude;
@SuppressWarnings("unused") @SerializedName("DateTimeOriginal") @Nullable private Values dateTimeOriginal;
@SuppressWarnings("unused") @SerializedName("Artist") @Nullable private Values artist;
@SuppressWarnings("unused") @SerializedName("Credit") @Nullable private Values credit;
@SuppressWarnings("unused") @SerializedName("Permission") @Nullable private Values permission;
@SuppressWarnings("unused") @SerializedName("AuthorCount") @Nullable private Values authorCount;
@SuppressWarnings("unused") @SerializedName("LicenseShortName") @Nullable private Values licenseShortName;
@SuppressWarnings("unused") @SerializedName("UsageTerms") @Nullable private Values usageTerms;
@SuppressWarnings("unused") @SerializedName("LicenseUrl") @Nullable private Values licenseUrl;
@SuppressWarnings("unused") @SerializedName("AttributionRequired") @Nullable private Values attributionRequired;
@SuppressWarnings("unused") @SerializedName("Copyrighted") @Nullable private Values copyrighted;
@SuppressWarnings("unused") @SerializedName("Restrictions") @Nullable private Values restrictions;
@SuppressWarnings("unused") @SerializedName("License") @Nullable private Values license;
@NonNull public Values dateTime() {
return dateTime != null ? dateTime : new Values();
}
@NonNull public Values dateTimeOriginal() {
return dateTimeOriginal != null ? dateTimeOriginal : new Values();
}
@NonNull public Values licenseShortName() {
return licenseShortName != null ? licenseShortName : new Values();
}
@NonNull public Values licenseUrl() {
return licenseUrl != null ? licenseUrl : new Values();
}
@NonNull public Values license() {
return license != null ? license : new Values();
}
@NonNull
public Values imageDescription() {
return imageDescription != null ? imageDescription : new Values();
}
@NonNull
public Values categories() {
return categories != null ? categories : new Values();
}
@NonNull
public Values gpsLatitude() {
return gpsLatitude != null ? gpsLatitude : new Values();
}
@NonNull
public Values gpsLongitude() {
return gpsLongitude != null ? gpsLongitude : new Values();
}
@NonNull public Values objectName() {
return objectName != null ? objectName : new Values();
}
@NonNull public Values usageTerms() {
return usageTerms != null ? usageTerms : new Values();
}
@NonNull public Values artist() {
return artist != null ? artist : new Values();
}
public class Values {
@SuppressWarnings("unused,NullableProblems") @Nullable private String value;
@SuppressWarnings("unused,NullableProblems") @Nullable private String source;
@NonNull public String value() {
return StringUtils.defaultString(value);
}
@NonNull public String source() {
return StringUtils.defaultString(source);
}
}
}

View file

@ -1,63 +0,0 @@
package fr.free.nrw.commons.media.model;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.gson.annotations.SerializedName;
import java.io.Serializable;
import fr.free.nrw.commons.utils.StringUtils;
/**
* Gson POJO for a standard image info object as returned by the API ImageInfo module
*/
public class ImageInfo implements Serializable {
@SuppressWarnings("unused") private int size;
@SuppressWarnings("unused") private int width;
@SuppressWarnings("unused") private int height;
@SuppressWarnings("unused,NullableProblems") @Nullable
private String source;
@SuppressWarnings("unused") @SerializedName("thumburl") @Nullable private String thumbUrl;
@SuppressWarnings("unused") @SerializedName("thumbwidth") private int thumbWidth;
@SuppressWarnings("unused") @SerializedName("thumbheight") private int thumbHeight;
@SuppressWarnings("unused") @SerializedName("url") @Nullable private String originalUrl;
@SuppressWarnings("unused") @SerializedName("descriptionurl") @Nullable private String descriptionUrl;
@SuppressWarnings("unused") @SerializedName("descriptionshorturl") @Nullable private String descriptionShortUrl;
@SuppressWarnings("unused,NullableProblems") @SerializedName("mime") @NonNull
private String mimeType = "*/*";
@SuppressWarnings("unused") @SerializedName("extmetadata")@Nullable private ExtMetadata metadata;
@NonNull
public String getSource() {
return StringUtils.defaultString(source);
}
public void setSource(@Nullable String source) {
this.source = source;
}
public int getSize() {
return size;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
@NonNull public String getThumbUrl() {
return StringUtils.defaultString(thumbUrl);
}
@NonNull public String getOriginalUrl() {
return StringUtils.defaultString(originalUrl);
}
@Nullable public ExtMetadata getMetadata() {
return metadata;
}
}

View file

@ -1,305 +0,0 @@
package fr.free.nrw.commons.media.model;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.gson.annotations.SerializedName;
import java.util.Collections;
import java.util.List;
import fr.free.nrw.commons.utils.StringUtils;
/**
* A class representing a standard page object as returned by the MediaWiki API.
*/
public class MwQueryPage {
@SuppressWarnings("unused")
private int pageid;
@SuppressWarnings("unused")
private int ns;
@SuppressWarnings("unused")
private int index;
@SuppressWarnings("unused,NullableProblems")
@NonNull
private String title;
@SuppressWarnings("unused")
@Nullable
private List<LangLink> langlinks;
@SuppressWarnings("unused")
@Nullable
private List<Revision> revisions;
@SuppressWarnings("unused")
@Nullable
private List<Coordinates> coordinates;
@SuppressWarnings("unused")
@Nullable
private List<Category> categories;
@SuppressWarnings("unused")
@Nullable
private PageProps pageprops;
@SuppressWarnings("unused")
@Nullable
private String extract;
@SuppressWarnings("unused")
@Nullable
private Thumbnail thumbnail;
@SuppressWarnings("unused")
@Nullable
private String description;
@SuppressWarnings("unused")
@SerializedName("descriptionsource")
@Nullable
private String descriptionSource;
@SuppressWarnings("unused")
@SerializedName("imageinfo")
@Nullable
private List<ImageInfo> imageInfo;
@Nullable
private String redirectFrom;
@Nullable
private String convertedFrom;
@Nullable
private String convertedTo;
@NonNull
public String title() {
return title;
}
public int index() {
return index;
}
@Nullable
public List<LangLink> langLinks() {
return langlinks;
}
@Nullable
public List<Revision> revisions() {
return revisions;
}
@Nullable
public List<Category> categories() {
return categories;
}
@Nullable
public List<Coordinates> coordinates() {
// TODO: Handle null values in lists during deserialization, perhaps with a new
// @RequiredElements annotation and corresponding TypeAdapter
if (coordinates != null) {
coordinates.removeAll(Collections.singleton(null));
}
return coordinates;
}
public int pageId() {
return pageid;
}
@Nullable
public PageProps pageProps() {
return pageprops;
}
@Nullable
public String extract() {
return extract;
}
@Nullable
public String thumbUrl() {
return thumbnail != null ? thumbnail.source() : null;
}
@Nullable
public String description() {
return description;
}
@Nullable
public String descriptionSource() {
return descriptionSource;
}
@Nullable
public ImageInfo imageInfo() {
return imageInfo != null ? imageInfo.get(0) : null;
}
@Nullable
public String redirectFrom() {
return redirectFrom;
}
public void redirectFrom(@Nullable String from) {
redirectFrom = from;
}
@Nullable
public String convertedFrom() {
return convertedFrom;
}
public void convertedFrom(@Nullable String from) {
convertedFrom = from;
}
@Nullable
public String convertedTo() {
return convertedTo;
}
public void convertedTo(@Nullable String to) {
convertedTo = to;
}
public void appendTitleFragment(@Nullable String fragment) {
title += "#" + fragment;
}
public static class Revision {
private String revid;
private String user;
@SuppressWarnings("unused,NullableProblems")
@SerializedName("contentformat")
@NonNull
private String contentFormat;
@SuppressWarnings("unused,NullableProblems")
@SerializedName("contentmodel")
@NonNull
private String contentModel;
@SuppressWarnings("unused,NullableProblems")
@SerializedName("timestamp")
@NonNull
private String timeStamp;
@SuppressWarnings("unused,NullableProblems")
@NonNull
private String content;
@NonNull
public String content() {
return content;
}
@NonNull
public String timeStamp() {
return StringUtils.defaultString(timeStamp);
}
public String getRevid() {
return revid;
}
public String getUser() {
return user;
}
}
public static class LangLink {
@SuppressWarnings("unused,NullableProblems")
@NonNull
private String lang;
@SuppressWarnings("unused,NullableProblems")
@NonNull
private String title;
@NonNull
public String lang() {
return lang;
}
@NonNull
public String title() {
return title;
}
}
public static class Coordinates {
@SuppressWarnings("unused")
@Nullable
private Double lat;
@SuppressWarnings("unused")
@Nullable
private Double lon;
@Nullable
public Double lat() {
return lat;
}
@Nullable
public Double lon() {
return lon;
}
}
static class Thumbnail {
@SuppressWarnings("unused")
private String source;
@SuppressWarnings("unused")
private int width;
@SuppressWarnings("unused")
private int height;
String source() {
return source;
}
}
public static class PageProps {
@SuppressWarnings("unused")
@SerializedName("wikibase_item")
@Nullable
private String wikiBaseItem;
@SuppressWarnings("unused")
@Nullable
private String displaytitle;
@SuppressWarnings("unused")
@Nullable
private String disambiguation;
@Nullable
public String getDisplayTitle() {
return displaytitle;
}
@NonNull
public String getWikiBaseItem() {
return StringUtils.defaultString(wikiBaseItem);
}
public boolean isDisambiguation() {
return disambiguation != null;
}
}
public static class Category {
@SuppressWarnings("unused")
private int ns;
@SuppressWarnings("unused,NullableProblems")
@Nullable
private String title;
@SuppressWarnings("unused")
private boolean hidden;
public int ns() {
return ns;
}
@NonNull
public String title() {
return StringUtils.defaultString(title);
}
public boolean hidden() {
return hidden;
}
}
}

View file

@ -2,12 +2,11 @@ package fr.free.nrw.commons.mwapi;
import android.content.Context;
import android.net.Uri;
import android.os.Build;
import android.text.TextUtils;
import com.google.gson.Gson;
import org.apache.http.HttpResponse;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
@ -21,23 +20,22 @@ import org.apache.http.params.CoreProtocolPNames;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.wikipedia.util.DateUtil;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import java.util.concurrent.Callable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.auth.AccountUtil;
import fr.free.nrw.commons.category.CategoryImageUtils;
@ -45,10 +43,7 @@ import fr.free.nrw.commons.category.QueryContinue;
import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.notification.Notification;
import fr.free.nrw.commons.notification.NotificationUtils;
import fr.free.nrw.commons.utils.ConfigUtils;
import fr.free.nrw.commons.utils.StringUtils;
import fr.free.nrw.commons.utils.ViewUtil;
import in.yuvi.http.fluent.Http;
import io.reactivex.Observable;
import io.reactivex.Single;
import timber.log.Timber;
@ -79,7 +74,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
final SSLSocketFactory sslSocketFactory = SSLSocketFactory.getSocketFactory();
schemeRegistry.register(new Scheme("https", sslSocketFactory, 443));
ClientConnectionManager cm = new ThreadSafeClientConnManager(params, schemeRegistry);
params.setParameter(CoreProtocolPNames.USER_AGENT, getUserAgent());
params.setParameter(CoreProtocolPNames.USER_AGENT, CommonsApplication.getInstance().getUserAgent());
httpClient = new DefaultHttpClient(cm, params);
if (BuildConfig.DEBUG) {
httpClient.addRequestInterceptor(NetworkInterceptors.getHttpRequestInterceptor());
@ -90,12 +85,6 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
this.gson = gson;
}
@Override
@NonNull
public String getUserAgent() {
return "Commons/" + ConfigUtils.getVersionNameWithSha(context) + " (https://mediawiki.org/wiki/Apps/Commons) Android/" + Build.VERSION.RELEASE;
}
/**
* @param username String
* @param password String
@ -254,11 +243,11 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
}
@Override
public boolean thank(String editToken, String revision) throws IOException {
public boolean thank(String editToken, long revision) throws IOException {
CustomApiResult res = api.action("thank")
.param("rev", revision)
.param("token", editToken)
.param("source", getUserAgent())
.param("source", CommonsApplication.getInstance().getUserAgent())
.post();
String r = res.getString("/api/result/@success");
// Does this correctly check the success/failure?
@ -587,7 +576,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
notfilter = "!read";
}
String language=Locale.getDefault().getLanguage();
if(StringUtils.isNullOrWhiteSpace(language)){
if(StringUtils.isBlank(language)){
//if no language is set we use the default user language defined on wikipedia
language="user";
}
@ -624,7 +613,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
.post()
.getString("/api/query/echomarkread/@result");
if (StringUtils.isNullOrWhiteSpace(result)) {
if (StringUtils.isBlank(result)) {
return false;
}
@ -783,29 +772,6 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
.getNodes("/api/query/allimages/img").size() > 0;
}
@Override
public boolean logEvents(LogBuilder[] logBuilders) {
boolean allSuccess = true;
// Not using the default URL connection, since that seems to have different behavior than the rest of the code
for (LogBuilder logBuilder : logBuilders) {
try {
URL url = logBuilder.toUrl();
HttpResponse response = Http.get(url.toString()).use(httpClient).asResponse();
if (response.getStatusLine().getStatusCode() != 204) {
allSuccess = false;
}
Timber.d("EventLog hit %s", url);
} catch (IOException e) {
// Probably just ignore for now. Can be much more robust with a service, etc later on.
Timber.d("IO Error, EventLog hit skipped");
}
}
return allSuccess;
}
@Override
@NonNull
public Single<UploadStash> uploadFile(
@ -903,10 +869,8 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
}
private Date parseMWDate(String mwDate) {
SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH); // Assuming MW always gives me UTC
isoFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
try {
return isoFormat.parse(mwDate);
return DateUtil.getIso8601DateFormat().parse(mwDate);
} catch (ParseException e) {
throw new RuntimeException(e);
}

View file

@ -2,6 +2,9 @@ package fr.free.nrw.commons.mwapi;
import com.google.gson.Gson;
import org.wikipedia.dataclient.mwapi.MwQueryPage;
import org.wikipedia.dataclient.mwapi.MwQueryResponse;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
@ -11,9 +14,6 @@ import java.util.Set;
import javax.inject.Inject;
import javax.inject.Named;
import fr.free.nrw.commons.mwapi.model.ApiResponse;
import fr.free.nrw.commons.mwapi.model.Page;
import fr.free.nrw.commons.mwapi.model.PageCategory;
import io.reactivex.Single;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
@ -54,12 +54,14 @@ public class CategoryApi {
return Collections.emptyList();
}
ApiResponse apiResponse = gson.fromJson(body.charStream(), ApiResponse.class);
MwQueryResponse apiResponse = gson.fromJson(body.charStream(), MwQueryResponse.class);
Set<String> categories = new LinkedHashSet<>();
if (apiResponse != null && apiResponse.hasPages()) {
for (Page page : apiResponse.query.pages) {
for (PageCategory category : page.getCategories()) {
categories.add(category.withoutPrefix());
if (apiResponse != null && apiResponse.query() != null && apiResponse.query().pages() != null) {
for (MwQueryPage page : apiResponse.query().pages()) {
if (page.categories() != null) {
for (MwQueryPage.Category category : page.categories()) {
categories.add(category.title().replace("Category:", ""));
}
}
}
}

View file

@ -1,30 +0,0 @@
package fr.free.nrw.commons.mwapi;
import android.content.Context;
import android.os.Build;
import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.kvstore.JsonKvStore;
public class EventLog {
static final String DEVICE;
static {
if (Build.MODEL.startsWith(Build.MANUFACTURER)) {
DEVICE = Utils.capitalize(Build.MODEL);
} else {
DEVICE = Utils.capitalize(Build.MANUFACTURER) + " " + Build.MODEL;
}
}
private static LogBuilder schema(String schema, long revision, MediaWikiApi mwApi, JsonKvStore prefs, Context context) {
return new LogBuilder(schema, revision, mwApi, prefs, context);
}
public static LogBuilder schema(Object[] scid, MediaWikiApi mwApi, JsonKvStore prefs, Context context) {
if (scid.length != 2) {
throw new IllegalArgumentException("Needs an object array with schema as first param and revision as second");
}
return schema((String) scid[0], (Long) scid[1], mwApi, prefs, context);
}
}

View file

@ -1,99 +0,0 @@
package fr.free.nrw.commons.mwapi;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Build;
import org.json.JSONException;
import org.json.JSONObject;
import java.net.MalformedURLException;
import java.net.URL;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.settings.Prefs;
import fr.free.nrw.commons.utils.ConfigUtils;
@SuppressWarnings("WeakerAccess")
public class LogBuilder {
private final MediaWikiApi mwApi;
private final JSONObject data;
private final long rev;
private final String schema;
private final JsonKvStore prefs;
private final Context context;
/**
* Main constructor of LogBuilder
*
* @param schema Log schema
* @param revision Log revision
* @param mwApi Wiki media API instance
* @param prefs Instance of SharedPreferences
*/
LogBuilder(String schema,
long revision,
MediaWikiApi mwApi,
JsonKvStore prefs,
Context context) {
this.prefs = prefs;
this.data = new JSONObject();
this.schema = schema;
this.rev = revision;
this.mwApi = mwApi;
this.context = context;
}
/**
* Adds data to preferences
* @param key Log key
* @param value Log object value
* @return LogBuilder
*/
public LogBuilder param(String key, Object value) {
try {
data.put(key, value);
} catch (JSONException e) {
throw new RuntimeException(e);
}
return this;
}
/**
* Encodes JSON object to URL
* @return URL to JSON object
*/
URL toUrl() {
JSONObject fullData = new JSONObject();
try {
fullData.put("schema", schema);
fullData.put("revision", rev);
fullData.put("wiki", BuildConfig.EVENTLOG_WIKI);
data.put("device", EventLog.DEVICE);
data.put("platform", "Android/" + Build.VERSION.RELEASE);
data.put("appversion", "Android/" + ConfigUtils.getVersionNameWithSha(context));
fullData.put("event", data);
return new URL(BuildConfig.EVENTLOG_URL + "?" + Utils.urlEncode(fullData.toString()) + ";");
} catch (MalformedURLException | JSONException e) {
throw new RuntimeException(e);
}
}
// force param disregards user preference
// Use *only* for tracking the user preference change for EventLogging
// Attempting to use anywhere else will cause kitten explosions
public void log(boolean force) {
if (!prefs.getBoolean(Prefs.TRACKING_ENABLED, true) && !force) {
return; // User has disabled tracking
}
LogTask logTask = new LogTask(mwApi);
logTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, this);
}
public void log() {
log(false);
}
}

View file

@ -1,27 +0,0 @@
package fr.free.nrw.commons.mwapi;
import android.os.AsyncTask;
class LogTask extends AsyncTask<LogBuilder, Void, Boolean> {
private final MediaWikiApi mwApi;
/**
* Main constructor of LogTask
*
* @param mwApi Media wiki API instance
*/
public LogTask(MediaWikiApi mwApi) {
this.mwApi = mwApi;
}
/**
* Logs events in background
* @param logBuilders LogBuilder instance
* @return Background success state ( TRUE or FALSE )
*/
@Override
protected Boolean doInBackground(LogBuilder... logBuilders) {
return mwApi.logEvents(logBuilders);
}
}

View file

@ -13,7 +13,6 @@ import io.reactivex.Observable;
import io.reactivex.Single;
public interface MediaWikiApi {
String getUserAgent();
String getAuthCookie();
@ -37,8 +36,6 @@ public interface MediaWikiApi {
Single<String> findThumbnailByFilename(String filename);
boolean logEvents(LogBuilder[] logBuilders);
List<String> getSubCategoryList(String categoryName);
List<String> getParentCategoryList(String categoryName);
@ -103,7 +100,7 @@ public interface MediaWikiApi {
// Single<CampaignResponseDTO> getCampaigns();
boolean thank(String editToken, String revision) throws IOException;
boolean thank(String editToken, long revision) throws IOException;
interface ProgressListener {
void onProgress(long transferred, long total);

View file

@ -3,6 +3,12 @@ package fr.free.nrw.commons.mwapi;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import org.apache.commons.lang3.StringUtils;
import org.wikipedia.dataclient.mwapi.MwQueryPage;
import org.wikipedia.dataclient.mwapi.MwQueryResponse;
import org.wikipedia.dataclient.mwapi.RecentChange;
import org.wikipedia.util.DateUtil;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
@ -18,21 +24,15 @@ import javax.inject.Singleton;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.PageTitle;
import fr.free.nrw.commons.achievements.FeaturedImages;
import fr.free.nrw.commons.achievements.FeedbackResponse;
import fr.free.nrw.commons.campaigns.CampaignResponseDTO;
import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.media.model.MwQueryPage;
import fr.free.nrw.commons.mwapi.model.MwQueryResponse;
import fr.free.nrw.commons.mwapi.model.RecentChange;
import fr.free.nrw.commons.nearby.Place;
import fr.free.nrw.commons.nearby.model.NearbyResponse;
import fr.free.nrw.commons.nearby.model.NearbyResultItem;
import fr.free.nrw.commons.upload.FileUtils;
import fr.free.nrw.commons.utils.DateUtils;
import fr.free.nrw.commons.utils.StringUtils;
import fr.free.nrw.commons.wikidata.model.GetWikidataEditCountResponse;
import io.reactivex.Observable;
import io.reactivex.Single;
@ -135,7 +135,7 @@ public class OkHttpJsonApiClient {
String url = String.format(
Locale.ENGLISH,
fetchAchievementUrlTemplate,
new PageTitle(userName).getText());
userName);
HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder();
urlBuilder.addQueryParameter("user", userName);
Timber.i("Url %s", urlBuilder.toString());
@ -221,14 +221,16 @@ public class OkHttpJsonApiClient {
*/
@Nullable
public Single<Media> getPictureOfTheDay() {
String template = "Template:Potd/" + DateUtils.getCurrentDate();
String date = DateUtil.getIso8601DateFormatShort().format(new Date());
Timber.d("Current date is %s", date);
String template = "Template:Potd/" + date;
return getMedia(template, true);
}
/**
* Fetches Media object from the imageInfo API
*
* @param titles the tiles to be searched for. Can be filename or template name
* @param titles the tiles to be searched for. Can be filename or template name
* @param useGenerator specifies if a image generator parameter needs to be passed or not
* @return
*/
@ -265,6 +267,7 @@ public class OkHttpJsonApiClient {
/**
* Whenever imageInfo is fetched, these common properties can be specified for the API call
* https://www.mediawiki.org/wiki/API:Imageinfo
*
* @param builder
* @return
*/
@ -274,7 +277,7 @@ public class OkHttpJsonApiClient {
.addQueryParameter("iiextmetadatafilter", "DateTime|Categories|GPSLatitude|GPSLongitude|ImageDescription|DateTimeOriginal|Artist|LicenseShortName|LicenseUrl");
String language = Locale.getDefault().getLanguage();
if (!StringUtils.isNullOrWhiteSpace(language)) {
if (!StringUtils.isBlank(language)) {
builder.addQueryParameter("iiextmetadatalanguage", language);
}
@ -334,7 +337,8 @@ public class OkHttpJsonApiClient {
/**
* Append params for search query.
* @param query the search query to be sent to the API
*
* @param query the search query to be sent to the API
* @param urlBuilder builder for HttpUrl
*/
private void appendSearchParam(String query, HttpUrl.Builder urlBuilder) {
@ -347,6 +351,7 @@ public class OkHttpJsonApiClient {
/**
* It takes a urlBuilder and appends all the continue values as query parameters
*
* @param query
* @param urlBuilder
*/
@ -361,8 +366,9 @@ public class OkHttpJsonApiClient {
/**
* Append parameters for category image generator
*
* @param categoryName name of the category
* @param urlBuilder HttpUrl builder
* @param urlBuilder HttpUrl builder
*/
private void appendCategoryParams(String categoryName, HttpUrl.Builder urlBuilder) {
urlBuilder.addQueryParameter("generator", "categorymembers")
@ -376,6 +382,7 @@ public class OkHttpJsonApiClient {
/**
* Stores the continue values for action=query
* These values are sent to the server in the subsequent call to fetch results after this point
*
* @param keyword
* @param values
*/
@ -386,6 +393,7 @@ public class OkHttpJsonApiClient {
/**
* Retrieves a map of continue values from shared preferences.
* These values are appended to the next API call
*
* @param keyword
* @return
*/
@ -395,6 +403,7 @@ public class OkHttpJsonApiClient {
/**
* Returns recent changes on commons
*
* @return list of recent changes made
*/
@Nullable
@ -405,6 +414,7 @@ public class OkHttpJsonApiClient {
Date now = new Date();
Date startDate = new Date(now.getTime() - r.nextInt(RANDOM_SECONDS) * 1000L);
String rcStart = DateUtil.getIso8601DateFormat().format(startDate);
HttpUrl.Builder urlBuilder = HttpUrl
.parse(commonsBaseUrl)
.newBuilder()
@ -412,7 +422,7 @@ public class OkHttpJsonApiClient {
.addQueryParameter("format", "json")
.addQueryParameter("formatversion", "2")
.addQueryParameter("list", "recentchanges")
.addQueryParameter("rcstart", DateUtils.formatMWDate(startDate))
.addQueryParameter("rcstart", rcStart)
.addQueryParameter("rcnamespace", FILE_NAMESPACE)
.addQueryParameter("rcprop", "title|ids")
.addQueryParameter("rctype", "new|log")
@ -427,7 +437,7 @@ public class OkHttpJsonApiClient {
if (response.body() != null && response.isSuccessful()) {
String json = response.body().string();
MwQueryResponse mwQueryPage = gson.fromJson(json, MwQueryResponse.class);
return mwQueryPage.query().getRecentchanges();
return mwQueryPage.query().getRecentChanges();
}
return new ArrayList<>();
});

View file

@ -1,12 +0,0 @@
package fr.free.nrw.commons.mwapi.model;
public class ApiResponse {
public Query query;
public ApiResponse() {
}
public boolean hasPages() {
return query != null && query.pages != null;
}
}

View file

@ -1,30 +0,0 @@
package fr.free.nrw.commons.mwapi.model;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class MwException extends RuntimeException {
@SuppressWarnings("unused")
@NonNull
private final MwServiceError error;
public MwException(@NonNull MwServiceError error) {
this.error = error;
}
@NonNull
public MwServiceError getError() {
return error;
}
@Nullable
public String getTitle() {
return error.getTitle();
}
@Override
@Nullable
public String getMessage() {
return error.getDetails();
}
}

View file

@ -1,39 +0,0 @@
package fr.free.nrw.commons.mwapi.model;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.google.gson.annotations.SerializedName;
import java.util.Map;
public class MwQueryResponse extends MwResponse {
@SuppressWarnings("unused") @SerializedName("batchcomplete") private boolean batchComplete;
@SuppressWarnings("unused") @SerializedName("continue") @Nullable
private Map<String, String> continuation;
@Nullable private MwQueryResult query;
public boolean batchComplete() {
return batchComplete;
}
@Nullable public Map<String, String> continuation() {
return continuation;
}
@Nullable public MwQueryResult query() {
return query;
}
public boolean success() {
return query != null;
}
@VisibleForTesting
protected void setQuery(@Nullable MwQueryResult query) {
this.query = query;
}
}

View file

@ -1,48 +0,0 @@
package fr.free.nrw.commons.mwapi.model;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import fr.free.nrw.commons.media.model.ImageInfo;
import fr.free.nrw.commons.media.model.MwQueryPage;
public class MwQueryResult {
@SuppressWarnings("unused")
@Nullable
private List<MwQueryPage> pages;
private List<RecentChange> recentchanges;
@NonNull
public List<MwQueryPage> pages() {
if (pages == null) {
return new ArrayList<>();
}
return pages;
}
public List<RecentChange> getRecentchanges() {
return recentchanges;
}
@Nullable
public MwQueryPage firstPage() {
return pages().get(0);
}
@NonNull
public Map<String, ImageInfo> images() {
Map<String, ImageInfo> result = new HashMap<>();
if (pages != null) {
for (MwQueryPage page : pages()) {
if (page.imageInfo() != null) {
result.put(page.title(), page.imageInfo());
}
}
}
return result;
}
}

View file

@ -1,38 +0,0 @@
package fr.free.nrw.commons.mwapi.model;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.gson.annotations.SerializedName;
import java.util.Map;
import fr.free.nrw.commons.json.PostProcessingTypeAdapter;
public abstract class MwResponse implements PostProcessingTypeAdapter.PostProcessable {
@SuppressWarnings("unused")
@Nullable
private MwServiceError error;
@SuppressWarnings("unused")
@Nullable
private Map<String, Warning> warnings;
@SuppressWarnings("unused,NullableProblems")
@SerializedName("servedby")
@NonNull
private String servedBy;
@Override
public void postProcess() {
if (error != null) {
throw new MwException(error);
}
}
private class Warning {
@SuppressWarnings("unused,NullableProblems")
@NonNull
private String warnings;
}
}

View file

@ -1,85 +0,0 @@
package fr.free.nrw.commons.mwapi.model;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.Collections;
import java.util.List;
import fr.free.nrw.commons.utils.StringUtils;
/**
* Gson POJO for a MediaWiki API error.
*/
public class MwServiceError implements ServiceError {
@SuppressWarnings("unused")
@Nullable
private String code;
@SuppressWarnings("unused")
@Nullable
private String info;
@SuppressWarnings("unused")
@Nullable
private String docref;
@SuppressWarnings("unused")
@NonNull
private List<Message> messages = Collections.emptyList();
@Override
@NonNull
public String getTitle() {
return StringUtils.defaultString(code);
}
@Override
@NonNull
public String getDetails() {
return StringUtils.defaultString(info);
}
@Nullable
public String getDocRef() {
return docref;
}
public boolean badToken() {
return "badtoken".equals(code);
}
public boolean badLoginState() {
return "assertuserfailed".equals(code);
}
public boolean hasMessageName(@NonNull String messageName) {
for (Message msg : messages) {
if (messageName.equals(msg.name)) {
return true;
}
}
return false;
}
@Nullable
public String getMessageHtml(@NonNull String messageName) {
for (Message msg : messages) {
if (messageName.equals(msg.name)) {
return msg.html();
}
}
return null;
}
private static final class Message {
@SuppressWarnings("unused")
@Nullable
private String name;
@SuppressWarnings("unused")
@Nullable
private String html;
@NonNull
private String html() {
return StringUtils.defaultString(html);
}
}
}

View file

@ -1,17 +0,0 @@
package fr.free.nrw.commons.mwapi.model;
import androidx.annotation.NonNull;
public class Page {
public String title;
public PageCategory[] categories;
public PageCategory category;
public Page() {
}
@NonNull
public PageCategory[] getCategories() {
return categories != null ? categories : new PageCategory[0];
}
}

View file

@ -1,12 +0,0 @@
package fr.free.nrw.commons.mwapi.model;
public class PageCategory {
public String title;
public PageCategory() {
}
public String withoutPrefix() {
return title != null ? title.replace("Category:", "") : "";
}
}

View file

@ -1,10 +0,0 @@
package fr.free.nrw.commons.mwapi.model;
public class Query {
public Page[] pages;
public Query() {
pages = new Page[0];
}
}

View file

@ -1,28 +0,0 @@
package fr.free.nrw.commons.mwapi.model;
import com.google.gson.annotations.SerializedName;
public class RecentChange {
private final String type;
private final String title;
@SerializedName("old_revid")
private final String oldRevisionId;
public RecentChange(String type, String title, String oldRevisionId) {
this.type = type;
this.title = title;
this.oldRevisionId = oldRevisionId;
}
public String getType() {
return type;
}
public String getTitle() {
return title;
}
public String getOldRevisionId() {
return oldRevisionId;
}
}

View file

@ -1,13 +0,0 @@
package fr.free.nrw.commons.mwapi.model;
import androidx.annotation.NonNull;
/**
* The API reported an error in the payload.
*/
public interface ServiceError {
@NonNull
String getTitle();
@NonNull String getDetails();
}

View file

@ -3,12 +3,14 @@ package fr.free.nrw.commons.nearby;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import org.apache.commons.lang3.StringUtils;
import androidx.annotation.Nullable;
import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.nearby.model.NearbyResultItem;
import fr.free.nrw.commons.utils.PlaceUtils;
import fr.free.nrw.commons.utils.StringUtils;
import timber.log.Timber;
/**
@ -47,7 +49,7 @@ public class Place implements Parcelable {
public static Place from(NearbyResultItem item) {
String itemClass = item.getClassName().getValue();
String classEntityId = "";
if(!StringUtils.isNullOrWhiteSpace(itemClass)) {
if(!StringUtils.isBlank(itemClass)) {
classEntityId = itemClass.replace("http://www.wikidata.org/entity/", "");
}
return new Place(

View file

@ -1,7 +1,6 @@
package fr.free.nrw.commons.notification;
import android.graphics.Color;
import android.text.Html;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -14,6 +13,8 @@ import com.daimajia.swipe.SwipeLayout;
import com.google.android.material.animation.ArgbEvaluatorCompat;
import com.pedrogomez.renderers.Renderer;
import org.wikipedia.util.StringUtil;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
@ -106,7 +107,7 @@ public class NotificationRenderer extends Renderer<Notification> {
*/
private void setTitle(String notificationText) {
notificationText = notificationText.trim().replaceAll("(^\\s*)|(\\s*$)", "");
notificationText = Html.fromHtml(notificationText).toString();
notificationText = StringUtil.fromHtml(notificationText).toString();
if (notificationText.length() > 280) {
notificationText = notificationText.substring(0, 279);
notificationText = notificationText.concat("...");

View file

@ -144,7 +144,7 @@ public class ReviewActivity extends AuthenticatedActivity {
.subscribe(revision -> {
reviewController.firstRevision = revision;
reviewPagerAdapter.updateFileInformation(fileName);
((TextView) imageCaption).setText(fileName + " is uploaded by: " + revision.getUser());
((TextView) imageCaption).setText(fileName + " is uploaded by: " + revision.content());
progressBar.setVisibility(View.GONE);
}));
reviewPager.setCurrentItem(0);

View file

@ -7,6 +7,8 @@ import android.content.Context;
import android.view.Gravity;
import android.widget.Toast;
import org.wikipedia.dataclient.mwapi.MwQueryPage;
import java.util.ArrayList;
import javax.inject.Inject;
@ -21,7 +23,6 @@ import fr.free.nrw.commons.R;
import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.delete.DeleteHelper;
import fr.free.nrw.commons.di.ApplicationlessInjection;
import fr.free.nrw.commons.media.model.MwQueryPage;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
@ -31,21 +32,19 @@ import timber.log.Timber;
@Singleton
public class ReviewController {
private String fileName;
public static final int NOTIFICATION_SEND_THANK = 0x102;
protected static ArrayList<String> categories;
public static final int NOTIFICATION_CHECK_CATEGORY = 0x101;
private final DeleteHelper deleteHelper;
@Nullable
public MwQueryPage.Revision firstRevision; // TODO: maybe we can expand this class to include fileName
protected static ArrayList<String> categories;
public static final int NOTIFICATION_SEND_THANK = 0x102;
public static final int NOTIFICATION_CHECK_CATEGORY = 0x101;
private NotificationManager notificationManager;
private NotificationCompat.Builder notificationBuilder;
private Media media;
@Inject
MediaWikiApi mwApi;
@Inject
SessionManager sessionManager;
private final DeleteHelper deleteHelper;
private NotificationManager notificationManager;
private NotificationCompat.Builder notificationBuilder;
private Media media;
private ViewPager viewPager;
private ReviewActivity reviewActivity;
@ -201,7 +200,7 @@ public class ReviewController {
}
publishProgress(context, 1);
assert firstRevision != null;
mwApi.thank(editToken, firstRevision.getRevid());
mwApi.thank(editToken, firstRevision.getRevisionId());
publishProgress(context, 2);
} catch (Exception e) {
Timber.d(e);

View file

@ -1,5 +1,8 @@
package fr.free.nrw.commons.review;
import org.wikipedia.dataclient.mwapi.MwQueryPage;
import org.wikipedia.dataclient.mwapi.RecentChange;
import java.util.List;
import java.util.Random;
@ -9,10 +12,8 @@ import javax.inject.Singleton;
import androidx.core.util.Pair;
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.media.model.MwQueryPage;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient;
import fr.free.nrw.commons.mwapi.model.RecentChange;
import io.reactivex.Single;
@Singleton
@ -36,6 +37,7 @@ public class ReviewHelper {
* - Picks a random file from those changes
* - Checks if the file is nominated for deletion
* - Retries upto 5 times for getting a file which is not nominated for deletion
*
* @return
*/
Single<Media> getRandomMedia() {
@ -75,7 +77,7 @@ public class ReviewHelper {
for (int i = 0; i < count; i++) {
int randomIndex = randomIndexes[i];
RecentChange recentChange = recentChanges.get(randomIndex);
if (recentChange.getType().equals("log") && !recentChange.getOldRevisionId().equals("0")) {
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;
}

View file

@ -11,9 +11,10 @@ import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import org.wikipedia.dataclient.mwapi.MwQueryPage;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
import fr.free.nrw.commons.media.model.MwQueryPage;
public class ReviewImageFragment extends CommonsDaggerSupportFragment {
@ -130,6 +131,7 @@ public class ReviewImageFragment extends CommonsDaggerSupportFragment {
return layoutView;
}
private ReviewActivity getReviewActivity() {
return (ReviewActivity) requireActivity();
}

View file

@ -5,7 +5,6 @@ import android.os.Bundle;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentStatePagerAdapter;
import fr.free.nrw.commons.media.model.MwQueryPage;
public class ReviewPagerAdapter extends FragmentStatePagerAdapter {
ReviewImageFragment[] reviewImageFragments;

View file

@ -1,13 +1,12 @@
package fr.free.nrw.commons.ui.widget;
import android.content.Context;
import android.os.Build;
import androidx.appcompat.widget.AppCompatTextView;
import android.text.Html;
import android.text.Spanned;
import android.text.method.LinkMovementMethod;
import android.util.AttributeSet;
import org.wikipedia.util.StringUtil;
/**
* An {@link AppCompatTextView} which formats the text to HTML displayable text and makes any
* links clickable.
@ -23,7 +22,7 @@ public class HtmlTextView extends AppCompatTextView {
super(context, attrs);
setMovementMethod(LinkMovementMethod.getInstance());
setText(fromHtml(getText().toString()));
setText(StringUtil.fromHtml(getText().toString()));
}
/**
@ -31,21 +30,6 @@ public class HtmlTextView extends AppCompatTextView {
* @param newText the text to be displayed
*/
public void setHtmlText(String newText) {
setText(fromHtml(newText));
}
/**
* Fix Html.fromHtml is deprecated problem
*
* @param source provided Html string
* @return returned Spanned of appropriate method according to version check
*/
private static Spanned fromHtml(String source) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return Html.fromHtml(source, Html.FROM_HTML_MODE_LEGACY);
} else {
//noinspection deprecation
return Html.fromHtml(source);
}
setText(StringUtil.fromHtml(newText));
}
}

View file

@ -3,6 +3,8 @@ package fr.free.nrw.commons.upload;
import android.content.Context;
import android.net.Uri;
import org.apache.commons.lang3.StringUtils;
import java.io.IOException;
import javax.inject.Inject;
@ -12,7 +14,6 @@ import fr.free.nrw.commons.mwapi.MediaWikiApi;
import fr.free.nrw.commons.nearby.Place;
import fr.free.nrw.commons.utils.ImageUtils;
import fr.free.nrw.commons.utils.ImageUtilsWrapper;
import fr.free.nrw.commons.utils.StringUtils;
import io.reactivex.Single;
import timber.log.Timber;
@ -172,13 +173,13 @@ public class ImageProcessingService {
*/
private Single<Integer> checkImageGeoLocation(Place place, String filePath) {
Timber.d("Checking for image geolocation %s", filePath);
if (place == null || StringUtils.isNullOrWhiteSpace(place.getWikiDataEntityId())) {
if (place == null || StringUtils.isBlank(place.getWikiDataEntityId())) {
return Single.just(ImageUtils.IMAGE_OK);
}
return Single.fromCallable(() -> filePath)
.map(fileUtilsWrapper::getGeolocationOfFile)
.flatMap(geoLocation -> {
if (StringUtils.isNullOrWhiteSpace(geoLocation)) {
if (StringUtils.isBlank(geoLocation)) {
return Single.just(ImageUtils.IMAGE_OK);
}
return imageUtilsWrapper.checkImageGeolocationIsDifferent(geoLocation, place.getLocation());

View file

@ -11,12 +11,7 @@ import androidx.appcompat.app.AlertDialog;
import androidx.cardview.widget.CardView;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.text.Html;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.text.style.ClickableSpan;
import android.text.style.URLSpan;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
@ -61,6 +56,7 @@ import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
import fr.free.nrw.commons.nearby.Place;
import fr.free.nrw.commons.theme.BaseActivity;
import fr.free.nrw.commons.ui.widget.HtmlTextView;
import fr.free.nrw.commons.utils.DialogUtil;
import fr.free.nrw.commons.utils.NetworkUtils;
import fr.free.nrw.commons.utils.PermissionUtils;
@ -124,8 +120,7 @@ public class UploadActivity extends BaseActivity implements UploadView, SimilarI
// Final Submission
@BindView(R.id.license_title) TextView licenseTitle;
@BindView(R.id.share_license_summary) TextView licenseSummary;
@BindView(R.id.media_upload_policy) TextView licensePolicy;
@BindView(R.id.share_license_summary) HtmlTextView licenseSummary;
@BindView(R.id.license_list) Spinner licenseSpinner;
@BindView(R.id.submit) Button submit;
@BindView(R.id.license_previous) Button licensePrevious;
@ -152,7 +147,6 @@ public class UploadActivity extends BaseActivity implements UploadView, SimilarI
configureNavigationButtons();
configureCategories();
configureLicenses();
configurePolicy();
presenter.init();
@ -271,8 +265,7 @@ public class UploadActivity extends BaseActivity implements UploadView, SimilarI
public void updateLicenseSummary(String selectedLicense, int imageCount) {
String licenseHyperLink = "<a href='" + Utils.licenseUrlFor(selectedLicense) + "'>" +
getString(Utils.licenseNameFor(selectedLicense)) + "</a><br>";
setTextViewHTML(licenseSummary, getResources().getQuantityString(R.plurals.share_license_summary, imageCount, licenseHyperLink));
licenseSummary.setHtmlText(getResources().getQuantityString(R.plurals.share_license_summary, imageCount, licenseHyperLink));
}
@Override
@ -438,47 +431,6 @@ public class UploadActivity extends BaseActivity implements UploadView, SimilarI
}
}
/**
* Parses links from HTML string, and makes the links clickable in the specified TextView.<br>
* Uses {@link #makeLinkClickable(SpannableStringBuilder, URLSpan)}.
* @see <a href="https://stackoverflow.com/questions/12418279/android-textview-with-clickable-links-how-to-capture-clicks">Source</a>
*/
private void setTextViewHTML(TextView text, String html)
{
CharSequence sequence = Html.fromHtml(html);
SpannableStringBuilder strBuilder = new SpannableStringBuilder(sequence);
URLSpan[] urls = strBuilder.getSpans(0, sequence.length(), URLSpan.class);
for (URLSpan span : urls) {
makeLinkClickable(strBuilder, span);
}
text.setText(strBuilder);
text.setMovementMethod(LinkMovementMethod.getInstance());
}
/**
* Sets onClick handler to launch browser for the specified URLSpan.
* @see <a href="https://stackoverflow.com/questions/12418279/android-textview-with-clickable-links-how-to-capture-clicks">Source</a>
*/
private void makeLinkClickable(SpannableStringBuilder strBuilder, final URLSpan span)
{
int start = strBuilder.getSpanStart(span);
int end = strBuilder.getSpanEnd(span);
int flags = strBuilder.getSpanFlags(span);
ClickableSpan clickable = new ClickableSpan() {
public void onClick(View view) {
// Handle hyperlink click
String hyperLink = span.getURL();
launchBrowser(hyperLink);
}
};
strBuilder.setSpan(clickable, start, end, flags);
strBuilder.removeSpan(span);
}
private void launchBrowser(String hyperLink) {
Utils.handleWebUrl(this, Uri.parse(hyperLink));
}
private void configureLicenses() {
licenseSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
@ -559,10 +511,6 @@ public class UploadActivity extends BaseActivity implements UploadView, SimilarI
categoriesList.setAdapter(categoriesAdapter);
}
private void configurePolicy() {
setTextViewHTML(licensePolicy, getString(R.string.media_upload_policy));
}
@SuppressLint("CheckResult")
private void updateCategoryList(String filter) {
List<String> imageTitleList = presenter.getImageTitleList();

View file

@ -2,6 +2,17 @@ package fr.free.nrw.commons.upload;
import android.annotation.SuppressLint;
import android.content.Context;
import org.apache.commons.lang3.StringUtils;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.category.CategoriesModel;
import fr.free.nrw.commons.contributions.Contribution;
@ -11,7 +22,6 @@ import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.nearby.Place;
import fr.free.nrw.commons.settings.Prefs;
import fr.free.nrw.commons.utils.CustomProxy;
import fr.free.nrw.commons.utils.StringUtils;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
@ -166,7 +176,7 @@ public class UploadPresenter {
break;
default:
String errorMessageForResult = getErrorMessageForResult(context, errorCode);
if (StringUtils.isNullOrWhiteSpace(errorMessageForResult)) {
if (StringUtils.isBlank(errorMessageForResult)) {
return;
}
view.showBadPicturePopup(errorMessageForResult);

View file

@ -1,67 +0,0 @@
package fr.free.nrw.commons.utils;
import android.text.format.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
public class DateUtils {
private static final SimpleDateFormat isoFormat =
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH);
public static String getTimeAgo(Date currDate, Date itemDate) {
Calendar c = Calendar.getInstance();
c.setTime(currDate);
int yearNow = c.get(Calendar.YEAR);
int monthNow = c.get(Calendar.MONTH);
int dayNow = c.get(Calendar.DAY_OF_MONTH);
int hourNow = c.get(Calendar.HOUR_OF_DAY);
int minuteNow = c.get(Calendar.MINUTE);
c.setTime(itemDate);
int videoYear = c.get(Calendar.YEAR);
int videoMonth = c.get(Calendar.MONTH);
int videoDays = c.get(Calendar.DAY_OF_MONTH);
int videoHour = c.get(Calendar.HOUR_OF_DAY);
int videoMinute = c.get(Calendar.MINUTE);
if (yearNow != videoYear) {
return (String.valueOf(yearNow - videoYear) + "-" + "years");
} else if (monthNow != videoMonth) {
return (String.valueOf(monthNow - videoMonth) + "-" + "months");
} else if (dayNow != videoDays) {
return (String.valueOf(dayNow - videoDays) + "-" + "days");
} else if (hourNow != videoHour) {
return (String.valueOf(hourNow - videoHour) + "-" + "hours");
} else if (minuteNow != videoMinute) {
return (String.valueOf(minuteNow - videoMinute) + "-" + "minutes");
} else {
return "0-seconds";
}
}
public static Date getDateFromString(String dateString) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
try {
return dateFormat.parse(dateString);
} catch (ParseException e) {
return null;
}
}
public static String getCurrentDate() {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
Date date = new Date();
return dateFormat.format(date);
}
public static String dateInLocaleFormat(Date date){
return new SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "dd MMM yyyy"), Locale.getDefault()).format(date);
}
public static String formatMWDate(Date date) {
return isoFormat.format(date);
}
}

View file

@ -6,6 +6,8 @@ import android.app.Dialog;
import android.content.DialogInterface;
import android.view.View;
import org.apache.commons.lang3.StringUtils;
import fr.free.nrw.commons.R;
import timber.log.Timber;
@ -59,7 +61,7 @@ public class DialogUtil {
builder.setTitle(title);
builder.setMessage(message);
if (!StringUtils.isNullOrWhiteSpace(positiveButtonText)) {
if (!StringUtils.isBlank(positiveButtonText)) {
builder.setPositiveButton(positiveButtonText, (dialogInterface, i) -> {
dialogInterface.dismiss();
if (onPositiveBtnClick != null) {
@ -68,7 +70,7 @@ public class DialogUtil {
});
}
if (!StringUtils.isNullOrWhiteSpace(negativeButtonText)) {
if (!StringUtils.isBlank(negativeButtonText)) {
builder.setNegativeButton(negativeButtonText, (DialogInterface dialogInterface, int i) -> {
dialogInterface.dismiss();
if (onNegativeBtnClick != null) {

View file

@ -1,5 +1,7 @@
package fr.free.nrw.commons.utils;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
@ -33,13 +35,13 @@ public class MediaDataExtractorUtil {
* @return
*/
public static List<String> extractCategoriesFromList(String source) {
if (StringUtils.isNullOrWhiteSpace(source)) {
if (StringUtils.isBlank(source)) {
return new ArrayList<>();
}
String[] cats = source.split("\\|");
List<String> categories = new ArrayList<>();
for (String category : cats) {
if (!StringUtils.isNullOrWhiteSpace(category.trim())) {
if (!StringUtils.isBlank(category.trim())) {
categories.add(category);
}
}

View file

@ -1,62 +0,0 @@
package fr.free.nrw.commons.utils;
import android.os.Build;
import android.text.Html;
public class StringUtils {
public static final String EMPTY = "";
public static String getParsedStringFromHtml(String source) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return Html.fromHtml(source, Html.FROM_HTML_MODE_LEGACY).toString();
} else {
//noinspection deprecation
return Html.fromHtml(source).toString();
}
}
public static boolean isNullOrWhiteSpace(String value) {
return value == null || value.trim().isEmpty();
}
// Defaults
//-----------------------------------------------------------------------
/**
* <p>Returns either the passed in String,
* or if the String is {@code null}, an empty String ("").</p>
*
* <pre>
* StringUtils.defaultString(null) = ""
* StringUtils.defaultString("") = ""
* StringUtils.defaultString("bat") = "bat"
* </pre>
*
* @see String#valueOf(Object)
* @param str the String to check, may be null
* @return the passed in String, or the empty String if it
* was {@code null}
*/
public static String defaultString(final String str) {
return defaultString(str, EMPTY);
}
/**
* <p>Returns either the passed in String, or if the String is
* {@code null}, the value of {@code defaultStr}.</p>
*
* <pre>
* StringUtils.defaultString(null, "NULL") = "NULL"
* StringUtils.defaultString("", "NULL") = ""
* StringUtils.defaultString("bat", "NULL") = "bat"
* </pre>
*
* @see String#valueOf(Object)
* @param str the String to check, may be null
* @param defaultStr the default String to return
* if the input is {@code null}, may be null
* @return the passed in String, or the default if it was {@code null}
*/
public static String defaultString(final String str, final String defaultStr) {
return str == null ? defaultStr : str;
}
}

View file

@ -1,18 +0,0 @@
package fr.free.nrw.commons.utils;
import android.net.Uri;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import java.lang.reflect.Type;
public class UriDeserializer implements JsonDeserializer<Uri> {
@Override
public Uri deserialize(final JsonElement src, final Type srcType,
final JsonDeserializationContext context) throws JsonParseException {
return Uri.parse(src.getAsString());
}
}

View file

@ -1,17 +0,0 @@
package fr.free.nrw.commons.utils;
import android.net.Uri;
import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import java.lang.reflect.Type;
public class UriSerializer implements JsonSerializer<Uri> {
public JsonElement serialize(Uri src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(src.toString());
}
}

View file

@ -70,7 +70,7 @@ public class PicOfDayAppWidget extends AppWidgetProvider {
// View in browser
Intent viewIntent = new Intent();
viewIntent.setAction(ACTION_VIEW);
viewIntent.setData(response.getFilePageTitle().getMobileUri());
viewIntent.setData(Uri.parse(response.getPageTitle().getMobileUri()));
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, viewIntent, 0);
views.setOnClickPendingIntent(R.id.appwidget_image, pendingIntent);

View file

@ -52,7 +52,7 @@
android:layout_below="@id/license_subtitle"
tools:visibility="gone"/>
<TextView
<fr.free.nrw.commons.ui.widget.HtmlTextView
android:id="@+id/share_license_summary"
android:layout_width="match_parent"
android:layout_height="wrap_content"

View file

@ -1,96 +0,0 @@
package fr.free.nrw.commons
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import java.net.URLEncoder
@RunWith(RobolectricTestRunner::class)
@Config(constants = BuildConfig::class, sdk = [21], application = TestCommonsApplication::class)
class PageTitleTest {
@Test
fun displayTextShouldNotBeUnderscored() {
assertEquals("Ex 1", PageTitle("Ex_1").displayText)
assertEquals("Ex 1", PageTitle("Ex_1 ").displayText)
assertEquals("Ex 1", PageTitle("Ex 1").displayText)
assertEquals("Ex 1", PageTitle("Ex 1 ").displayText)
assertEquals("File:Ex 1 2", PageTitle("File:Ex_1_2 ").displayText)
}
@Test
fun prefixedTextShouldBeUnderscored() {
assertEquals("Ex_1", PageTitle("Ex_1").prefixedText)
assertEquals("Ex_1", PageTitle("Ex_1 ").prefixedText)
assertEquals("Ex_1", PageTitle("Ex 1").prefixedText)
assertEquals("Ex_1", PageTitle("Ex 1 ").prefixedText)
assertEquals("File:Ex_1_2", PageTitle("File:Ex 1 2 ").prefixedText)
}
@Test
fun fileNameWithOneColon() {
val pageTitle = PageTitle("File:sample:a.jpg")
assertEquals("File:Sample:a.jpg", pageTitle.prefixedText)
assertEquals("File:Sample:a.jpg", pageTitle.displayText)
assertEquals("Sample:a.jpg", pageTitle.text)
}
@Test
fun fileNameWithMoreThanOneColon() {
var pageTitle = PageTitle("File:sample:a:b.jpg")
assertEquals("File:Sample:a:b.jpg", pageTitle.prefixedText)
assertEquals("File:Sample:a:b.jpg", pageTitle.displayText)
assertEquals("Sample:a:b.jpg", pageTitle.text)
pageTitle = PageTitle("File:sample:a:b:c.jpg")
assertEquals("File:Sample:a:b:c.jpg", pageTitle.prefixedText)
assertEquals("File:Sample:a:b:c.jpg", pageTitle.displayText)
assertEquals("Sample:a:b:c.jpg", pageTitle.text)
}
@Test
fun keyShouldNotIncludeNamespace() {
val pageTitle = PageTitle("File:Sample.jpg")
assertEquals("Sample.jpg", pageTitle.text)
}
@Test
fun capitalizeNamespace() {
val pageTitle = PageTitle("file:Sample.jpg")
assertEquals("File:Sample.jpg", pageTitle.prefixedText)
assertEquals("File:Sample.jpg", pageTitle.displayText)
}
@Test
fun capitalizeKey() {
val pageTitle = PageTitle("File:sample.jpg")
assertEquals("File:Sample.jpg", pageTitle.prefixedText)
assertEquals("File:Sample.jpg", pageTitle.displayText)
assertEquals("Sample.jpg", pageTitle.text)
}
@Test
fun getMobileUriForTest() {
val pageTitle = PageTitle("Test")
assertEquals(BuildConfig.MOBILE_HOME_URL + "Test", pageTitle.mobileUri.toString())
}
@Test
fun spaceBecomesUnderscoreInUri() {
val pageTitle = PageTitle("File:Ex 1.jpg")
assertEquals(BuildConfig.HOME_URL + "File:Ex_1.jpg", pageTitle.canonicalUri.toString())
}
@Test
fun leaveSubpageNamesUncapitalizedInUri() {
val pageTitle = PageTitle("User:Ex/subpage")
assertEquals(BuildConfig.HOME_URL + "User:Ex/subpage", pageTitle.canonicalUri.toString())
}
@Test
fun unicodeUri() {
val pageTitle = PageTitle("User:例")
assertEquals(BuildConfig.HOME_URL + "User:" + URLEncoder.encode("", "utf-8"), pageTitle.canonicalUri.toString())
}
}

View file

@ -19,8 +19,8 @@ import org.mockito.Mockito.mock
import org.robolectric.RobolectricTestRunner
import org.robolectric.RuntimeEnvironment
import org.robolectric.annotation.Config
import org.wikipedia.util.DateUtil
import java.net.URLDecoder
import java.text.SimpleDateFormat
import java.util.*
@RunWith(RobolectricTestRunner::class)
@ -262,9 +262,7 @@ class ApacheHttpClientMediaWikiApiTest {
fun isUserBlockedFromCommonsForTimeBlockedUser() {
val currentDate = Date()
val expiredDate = Date(currentDate.time + 10000)
val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'")
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"))
server.enqueue(MockResponse().setBody("<?xml version=\"1.0\"?><api><query><userinfo id=\"1000\" name=\"testusername\" blockid=\"3000\" blockedby=\"blockerusername\" blockedbyid=\"1001\" blockreason=\"testing\" blockedtimestamp=\"2018-05-24T15:32:09Z\" blockexpiry=\"" + dateFormat.format(expiredDate) + "\"></userinfo></query></api>"))
server.enqueue(MockResponse().setBody("<?xml version=\"1.0\"?><api><query><userinfo id=\"1000\" name=\"testusername\" blockid=\"3000\" blockedby=\"blockerusername\" blockedbyid=\"1001\" blockreason=\"testing\" blockedtimestamp=\"2018-05-24T15:32:09Z\" blockexpiry=\"" + DateUtil.getIso8601DateFormat().format(expiredDate) + "\"></userinfo></query></api>"))
val result = testObject.isUserBlockedFromCommons()
@ -284,9 +282,7 @@ class ApacheHttpClientMediaWikiApiTest {
fun isUserBlockedFromCommonsForExpiredBlockedUser() {
val currentDate = Date()
val expiredDate = Date(currentDate.time - 10000)
val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'")
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"))
server.enqueue(MockResponse().setBody("<?xml version=\"1.0\"?><api><query><userinfo id=\"1000\" name=\"testusername\" blockid=\"3000\" blockedby=\"blockerusername\" blockedbyid=\"1001\" blockreason=\"testing\" blockedtimestamp=\"2018-05-24T15:32:09Z\" blockexpiry=\"" + dateFormat.format(expiredDate) + "\"></userinfo></query></api>"))
server.enqueue(MockResponse().setBody("<?xml version=\"1.0\"?><api><query><userinfo id=\"1000\" name=\"testusername\" blockid=\"3000\" blockedby=\"blockerusername\" blockedbyid=\"1001\" blockreason=\"testing\" blockedtimestamp=\"2018-05-24T15:32:09Z\" blockexpiry=\"" + DateUtil.getIso8601DateFormat().format(expiredDate) + "\"></userinfo></query></api>"))
val result = testObject.isUserBlockedFromCommons()

View file

@ -1,178 +0,0 @@
package fr.free.nrw.commons.mwapi
import com.google.gson.Gson
import fr.free.nrw.commons.mwapi.model.Page
import fr.free.nrw.commons.mwapi.model.PageCategory
import okhttp3.HttpUrl
import okhttp3.OkHttpClient
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
class CategoryApiTest {
private lateinit var server: MockWebServer
private lateinit var url: String
private lateinit var categoryApi: CategoryApi
@Before
fun setUp() {
server = MockWebServer()
url = "http://${server.hostName}:${server.port}/"
categoryApi = CategoryApi(OkHttpClient.Builder().build(), Gson(), HttpUrl.parse(url))
}
@After
fun teardown() {
server.shutdown()
}
@Test
fun apiReturnsEmptyListWhenError() {
server.enqueue(MockResponse().setResponseCode(400).setBody(""))
assertTrue(categoryApi.request("foo").blockingGet().isEmpty())
}
@Test
fun apiReturnsEmptyWhenTheresNoQuery() {
server.success(emptyMap())
assertTrue(categoryApi.request("foo").blockingGet().isEmpty())
}
@Test
fun apiReturnsEmptyWhenQueryHasNoPages() {
server.success(mapOf("query" to emptyMap<String, Any>()))
assertTrue(categoryApi.request("foo").blockingGet().isEmpty())
}
@Test
fun apiReturnsEmptyWhenQueryHasPagesButTheyreEmpty() {
server.success(mapOf("query" to
mapOf("pages" to emptyList<String>())))
assertTrue(categoryApi.request("foo").blockingGet().isEmpty())
}
@Test
fun singlePageSingleCategory() {
server.success(mapOf("query" to
mapOf("pages" to listOf(
page(listOf("one"))
))))
val response = categoryApi.request("foo").blockingGet()
assertEquals(1, response.size)
assertEquals("one", response[0])
}
@Test
fun multiplePagesSingleCategory() {
server.success(mapOf("query" to
mapOf("pages" to listOf(
page(listOf("one")),
page(listOf("two"))
))))
val response = categoryApi.request("foo").blockingGet()
assertEquals(2, response.size)
assertEquals("one", response[0])
assertEquals("two", response[1])
}
@Test
fun singlePageMultipleCategories() {
server.success(mapOf("query" to
mapOf("pages" to listOf(
page(listOf("one", "two"))
))))
val response = categoryApi.request("foo").blockingGet()
assertEquals(2, response.size)
assertEquals("one", response[0])
assertEquals("two", response[1])
}
@Test
fun multiplePagesMultipleCategories() {
server.success(mapOf("query" to
mapOf("pages" to listOf(
page(listOf("one", "two")),
page(listOf("three", "four"))
))))
val response = categoryApi.request("foo").blockingGet()
assertEquals(4, response.size)
assertEquals("one", response[0])
assertEquals("two", response[1])
assertEquals("three", response[2])
assertEquals("four", response[3])
}
@Test
fun multiplePagesMultipleCategories_duplicatesRemoved() {
server.success(mapOf("query" to
mapOf("pages" to listOf(
page(listOf("one", "two", "three")),
page(listOf("three", "four", "one"))
))))
val response = categoryApi.request("foo").blockingGet()
assertEquals(4, response.size)
assertEquals("one", response[0])
assertEquals("two", response[1])
assertEquals("three", response[2])
assertEquals("four", response[3])
}
@Test
fun requestSendsWhatWeExpect() {
server.success(mapOf("query" to mapOf("pages" to emptyList<String>())))
val coords = "foo,bar"
categoryApi.request(coords).blockingGet()
server.takeRequest().let { request ->
assertEquals("GET", request.method)
assertEquals("/w/api.php", request.requestUrl.encodedPath())
request.requestUrl.let { url ->
assertEquals("query", url.queryParameter("action"))
assertEquals("categories|coordinates|pageprops", url.queryParameter("prop"))
assertEquals("json", url.queryParameter("format"))
assertEquals("!hidden", url.queryParameter("clshow"))
assertEquals("type|name|dim|country|region|globe", url.queryParameter("coprop"))
assertEquals(coords, url.queryParameter("codistancefrompoint"))
assertEquals("geosearch", url.queryParameter("generator"))
assertEquals(coords, url.queryParameter("ggscoord"))
assertEquals("10000", url.queryParameter("ggsradius"))
assertEquals("10", url.queryParameter("ggslimit"))
assertEquals("6", url.queryParameter("ggsnamespace"))
assertEquals("type|name|dim|country|region|globe", url.queryParameter("ggsprop"))
assertEquals("all", url.queryParameter("ggsprimary"))
assertEquals("2", url.queryParameter("formatversion"))
}
}
}
private fun page(catList: List<String>) = Page().apply {
categories = catList.map {
PageCategory().apply {
title = "Category:$it"
}
}.toTypedArray()
}
}
fun MockWebServer.success(response: Map<String, Any>) {
enqueue(MockResponse().setResponseCode(200).setBody(Gson().toJson(response)))
}

View file

@ -6,7 +6,6 @@ import fr.free.nrw.commons.Media
import fr.free.nrw.commons.TestCommonsApplication
import fr.free.nrw.commons.kvstore.JsonKvStore
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient.mapType
import fr.free.nrw.commons.utils.DateUtils
import junit.framework.Assert.assertEquals
import okhttp3.HttpUrl
import okhttp3.OkHttpClient
@ -22,6 +21,8 @@ import org.mockito.Mockito
import org.mockito.Mockito.`when`
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import org.wikipedia.util.DateUtil
import java.util.*
import kotlin.random.Random
/**
@ -189,7 +190,7 @@ class OkHttpJsonApiClientTest {
*/
@Test
fun getImageWithGenerator() {
val template = "Template:Potd/" + DateUtils.getCurrentDate()
val template = "Template:Potd/" + DateUtil.getIso8601DateFormatShort().format(Date())
server.enqueue(getMediaList("", "", "", 1))
val media = testObject.getMedia(template, true)!!.blockingGet()
@ -215,7 +216,7 @@ class OkHttpJsonApiClientTest {
*/
@Test
fun getPictureOfTheDay() {
val template = "Template:Potd/" + DateUtils.getCurrentDate()
val template = "Template:Potd/" + DateUtil.getIso8601DateFormatShort().format(Date())
server.enqueue(getMediaList("", "", "", 1))
val media = testObject.pictureOfTheDay?.blockingGet()

View file

@ -1,28 +0,0 @@
package fr.free.nrw.commons.mwapi.model
import org.junit.Assert.*
import org.junit.Test
class ApiResponseTest {
@Test
fun hasPages_whenQueryIsNull() {
val response = ApiResponse()
assertFalse(response.hasPages())
}
@Test
fun hasPages_whenPagesIsNull() {
val response = ApiResponse()
response.query = Query()
response.query.pages = null
assertFalse(response.hasPages())
}
@Test
fun hasPages_defaultsToSafeValue() {
val response = ApiResponse()
response.query = Query()
assertNotNull(response.query.pages)
assertTrue(response.hasPages())
}
}

View file

@ -1,20 +0,0 @@
package fr.free.nrw.commons.mwapi.model
import org.junit.Assert.assertEquals
import org.junit.Test
class PageCategoryTest {
@Test
fun stripPrefix_whenPresent() {
val testObject = PageCategory()
testObject.title = "Category:Foo"
assertEquals("Foo", testObject.withoutPrefix())
}
@Test
fun stripPrefix_prefixAbsent() {
val testObject = PageCategory()
testObject.title = "Foo_Bar"
assertEquals("Foo_Bar", testObject.withoutPrefix())
}
}

View file

@ -1,12 +0,0 @@
package fr.free.nrw.commons.mwapi.model
import org.junit.Assert.assertNotNull
import org.junit.Test
class PageTest {
@Test
fun categoriesDefaultToSafeValue() {
val page = Page()
assertNotNull(page.getCategories())
}
}

View file

@ -1,10 +1,8 @@
package fr.free.nrw.commons.review
import fr.free.nrw.commons.Media
import fr.free.nrw.commons.media.model.MwQueryPage
import fr.free.nrw.commons.mwapi.MediaWikiApi
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient
import fr.free.nrw.commons.mwapi.model.RecentChange
import io.reactivex.Single
import junit.framework.Assert.assertTrue
import org.junit.Before
@ -15,6 +13,8 @@ import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.mockito.Mockito.mock
import org.mockito.MockitoAnnotations
import org.wikipedia.dataclient.mwapi.MwQueryPage
import org.wikipedia.dataclient.mwapi.RecentChange
/**
* Test class for ReviewHelper
@ -43,10 +43,11 @@ class ReviewHelperTest {
*/
@Test
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("test", "File:Test1.jpeg", "0"),
RecentChange("test", "File:Test2.png", "0"),
RecentChange("test", "File:Test3.jpg", "0"))))
.thenReturn(Single.just(listOf(recentChange, recentChange1, recentChange2)))
`when`(mediaWikiApi?.pageExists(ArgumentMatchers.anyString()))
.thenReturn(Single.just(true))
@ -55,6 +56,14 @@ class ReviewHelperTest {
assertTrue(randomMedia is Media)
}
fun getMockRecentChange(type: String, title: String, oldRevisionId: Long): RecentChange {
val recentChange = mock(RecentChange::class.java)
`when`(recentChange!!.type).thenReturn(type)
`when`(recentChange.title).thenReturn(title)
`when`(recentChange.oldRevisionId).thenReturn(oldRevisionId)
return recentChange
}
/**
* Test for getting first revision of file
*/

View file

@ -1,36 +0,0 @@
package fr.free.nrw.commons.utils
import fr.free.nrw.commons.Utils
import org.junit.Assert
import org.junit.Test
import org.hamcrest.CoreMatchers.`is` as _is
class UtilsTest {
@Test fun `strip nothing from non-localized string`() {
Assert.assertThat(Utils.stripLocalizedString("Hello"), _is("Hello"))
}
@Test fun `strip tag from Japanese string`() {
Assert.assertThat(Utils.stripLocalizedString("\"こんにちは\"@ja"), _is("こんにちは"))
}
@Test fun `capitalize first letter`() {
Assert.assertThat(Utils.capitalize("hello"), _is("Hello"))
}
@Test fun `capitalize - pass all-capital string as it is`() {
Assert.assertThat(Utils.capitalize("HELLO"), _is("HELLO"))
}
@Test fun `capitalize - pass numbers`() {
Assert.assertThat(Utils.capitalize("12x"), _is("12x"))
}
@Test fun `capitalize - pass Japanese characters`() {
Assert.assertThat(Utils.capitalize("こんにちは"), _is("こんにちは"))
}
@Test fun `capitalize does not fail on empty string`() {
Assert.assertThat(Utils.capitalize(""), _is(""))
}
}