diff --git a/app/build.gradle b/app/build.gradle index d4758f332..638a4048a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -76,7 +76,7 @@ dependencies { testImplementation "org.powermock:powermock-api-mockito2:2.0.0-beta.5" // Unit testing - testImplementation 'junit:junit:4.12' + testImplementation 'junit:junit:4.13' testImplementation 'org.robolectric:robolectric:4.3' testImplementation 'androidx.test:core:1.2.0' testImplementation 'com.squareup.okhttp3:mockwebserver:3.12.1' diff --git a/app/src/androidTest/java/fr/free/nrw/commons/AboutActivityTest.kt b/app/src/androidTest/java/fr/free/nrw/commons/AboutActivityTest.kt index 1ee2a5ace..5b20590b7 100644 --- a/app/src/androidTest/java/fr/free/nrw/commons/AboutActivityTest.kt +++ b/app/src/androidTest/java/fr/free/nrw/commons/AboutActivityTest.kt @@ -14,7 +14,7 @@ import androidx.test.espresso.matcher.ViewMatchers import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.rule.ActivityTestRule import androidx.test.runner.AndroidJUnit4 -import fr.free.nrw.commons.utils.ConfigUtils +import fr.free.nrw.commons.utils.ConfigUtils.getVersionNameWithSha import org.hamcrest.CoreMatchers import org.junit.Before import org.junit.Rule @@ -36,7 +36,9 @@ class AboutActivityTest { @Test fun testBuildNumber() { Espresso.onView(ViewMatchers.withId(R.id.about_version)) - .check(ViewAssertions.matches(withText(ConfigUtils.getVersionNameWithSha(getApplicationContext())))) + .check(ViewAssertions.matches( + withText(getApplicationContext().getVersionNameWithSha()) + )) } @Test diff --git a/app/src/androidTest/java/fr/free/nrw/commons/UploadTest.kt b/app/src/androidTest/java/fr/free/nrw/commons/UploadTest.kt index e52acb4d0..6a94d12ac 100644 --- a/app/src/androidTest/java/fr/free/nrw/commons/UploadTest.kt +++ b/app/src/androidTest/java/fr/free/nrw/commons/UploadTest.kt @@ -26,7 +26,7 @@ import androidx.test.rule.ActivityTestRule import androidx.test.rule.GrantPermissionRule import androidx.test.runner.AndroidJUnit4 import fr.free.nrw.commons.auth.LoginActivity -import fr.free.nrw.commons.upload.DescriptionsAdapter +import fr.free.nrw.commons.upload.UploadMediaDetailAdapter import fr.free.nrw.commons.util.MyViewAction import fr.free.nrw.commons.utils.ConfigUtils import org.hamcrest.core.AllOf.allOf @@ -78,7 +78,7 @@ class UploadTest { @Test fun testUploadWithDescription() { - if (!ConfigUtils.isBetaFlavour()) { + if (!ConfigUtils.isBetaFlavour) { throw Error("This test should only be run in Beta!") } @@ -96,7 +96,7 @@ class UploadTest { // Try to dismiss the error, if there is one (probably about duplicate files on Commons) dismissWarning("Yes") - onView(allOf(isDisplayed(), withId(R.id.et_title))) + onView(allOf(isDisplayed(), withId(R.id.tv_title))) .perform(replaceText(commonsFileName)) onView(allOf(isDisplayed(), withId(R.id.description_item_edit_text))) @@ -150,7 +150,7 @@ class UploadTest { @Test fun testUploadWithoutDescription() { - if (!ConfigUtils.isBetaFlavour()) { + if (!ConfigUtils.isBetaFlavour) { throw Error("This test should only be run in Beta!") } @@ -168,7 +168,7 @@ class UploadTest { // Try to dismiss the error, if there is one (probably about duplicate files on Commons) dismissWarning("Yes") - onView(allOf(isDisplayed(), withId(R.id.et_title))) + onView(allOf(isDisplayed(), withId(R.id.tv_title))) .perform(replaceText(commonsFileName)) onView(allOf(isDisplayed(), withId(R.id.btn_next))) @@ -209,7 +209,7 @@ class UploadTest { @Test fun testUploadWithMultilingualDescription() { - if (!ConfigUtils.isBetaFlavour()) { + if (!ConfigUtils.isBetaFlavour) { throw Error("This test should only be run in Beta!") } @@ -227,12 +227,12 @@ class UploadTest { // Try to dismiss the error, if there is one (probably about duplicate files on Commons) dismissWarningDialog() - onView(allOf(isDisplayed(), withId(R.id.et_title))) + onView(allOf(isDisplayed(), withId(R.id.tv_title))) .perform(replaceText(commonsFileName)) onView(withId(R.id.rv_descriptions)).perform( RecyclerViewActions - .actionOnItemAtPosition(0, + .actionOnItemAtPosition(0, MyViewAction.typeTextInChildViewWithId(R.id.description_item_edit_text, "Test description"))) onView(withId(R.id.btn_add_description)) @@ -240,12 +240,12 @@ class UploadTest { onView(withId(R.id.rv_descriptions)).perform( RecyclerViewActions - .actionOnItemAtPosition(1, + .actionOnItemAtPosition(1, MyViewAction.selectSpinnerItemInChildViewWithId(R.id.spinner_description_languages, 2))) onView(withId(R.id.rv_descriptions)).perform( RecyclerViewActions - .actionOnItemAtPosition(1, + .actionOnItemAtPosition(1, MyViewAction.typeTextInChildViewWithId(R.id.description_item_edit_text, "Description"))) onView(allOf(isDisplayed(), withId(R.id.btn_next))) diff --git a/app/src/androidTest/java/fr/free/nrw/commons/WelcomeActivityTest.kt b/app/src/androidTest/java/fr/free/nrw/commons/WelcomeActivityTest.kt index 3716a86e5..b48600b6b 100644 --- a/app/src/androidTest/java/fr/free/nrw/commons/WelcomeActivityTest.kt +++ b/app/src/androidTest/java/fr/free/nrw/commons/WelcomeActivityTest.kt @@ -23,7 +23,7 @@ class WelcomeActivityTest { @Test fun ifBetaShowsSkipButton() { - if (ConfigUtils.isBetaFlavour()) { + if (ConfigUtils.isBetaFlavour) { onView(withId(R.id.finishTutorialButton)) .check(matches(isDisplayed())) } @@ -31,7 +31,7 @@ class WelcomeActivityTest { @Test fun ifProdHidesSkipButton() { - if (!ConfigUtils.isBetaFlavour()) { + if (!ConfigUtils.isBetaFlavour) { onView(withId(R.id.finishTutorialButton)) .check(matches(not(isDisplayed()))) } @@ -39,7 +39,7 @@ class WelcomeActivityTest { @Test fun testBetaSkipButton() { - if (ConfigUtils.isBetaFlavour()) { + if (ConfigUtils.isBetaFlavour) { onView(withId(R.id.finishTutorialButton)) .perform(ViewActions.click()) assert(activityRule.activity.isDestroyed) diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java index b1d86b9b0..5631acee5 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java @@ -31,6 +31,7 @@ import fr.free.nrw.commons.Utils; import fr.free.nrw.commons.di.CommonsDaggerSupportFragment; import fr.free.nrw.commons.media.MediaClient; import fr.free.nrw.commons.utils.DialogUtil; +import fr.free.nrw.commons.wikidata.WikidataEditService; import java.util.Locale; import javax.inject.Inject; import javax.inject.Named; @@ -41,7 +42,8 @@ import org.wikipedia.dataclient.WikiSite; */ public class ContributionsListFragment extends CommonsDaggerSupportFragment implements - ContributionsListContract.View, ContributionsListAdapter.Callback, WikipediaInstructionsDialogFragment.Callback { + ContributionsListContract.View, ContributionsListAdapter.Callback, + WikipediaInstructionsDialogFragment.Callback { private static final String RV_STATE = "rv_scroll_state"; @@ -275,7 +277,6 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl } - public Media getMediaAtPosition(final int i) { return adapter.getContributionForPosition(i).getMedia(); } @@ -291,13 +292,14 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl */ @Override public void onConfirmClicked(@Nullable Contribution contribution, boolean copyWikicode) { - if(copyWikicode) { - String wikicode = contribution.getMedia().getWikiCode(); + if (copyWikicode) { + String wikicode = contribution.getWikiCode(); Utils.copy("wikicode", wikicode, getContext()); } - final String url = languageWikipediaSite.mobileUrl() + "/wiki/" + contribution.getWikidataPlace() - .getWikipediaPageTitle(); + final String url = + languageWikipediaSite.mobileUrl() + "/wiki/" + contribution.getWikidataPlace() + .getWikipediaPageTitle(); Utils.handleWebUrl(getContext(), Uri.parse(url)); } diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.java b/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.java index 9606abee4..a42897978 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.java @@ -127,7 +127,8 @@ public class MainActivity extends NavigationBaseActivity implements FragmentMana tabLayout.getTabAt(1).setCustomView(nearbyTabLinearLayout); nearbyInfo.setOnClickListener(view -> - new AlertDialog.Builder(MainActivity.this).setTitle(R.string.title_activity_nearby).setMessage(R.string.showcase_view_whole_nearby_activity) + new AlertDialog.Builder(MainActivity.this).setTitle(R.string.title_activity_nearby) + .setView(getLayoutInflater().inflate(R.layout.dialog_nearby, null)) .setCancelable(true) .setPositiveButton(android.R.string.ok, (dialog, id) -> dialog.cancel()) .create() diff --git a/app/src/main/java/fr/free/nrw/commons/upload/CountingRequestBody.kt b/app/src/main/java/fr/free/nrw/commons/upload/CountingRequestBody.kt index 7119594f2..62c3b3d5c 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/CountingRequestBody.kt +++ b/app/src/main/java/fr/free/nrw/commons/upload/CountingRequestBody.kt @@ -12,7 +12,12 @@ import java.io.IOException * * @author Ashish Kumar */ -class CountingRequestBody(protected var delegate: RequestBody, protected var listener: Listener) : RequestBody() { +class CountingRequestBody( + protected var delegate: RequestBody, + protected var listener: Listener, + var offset: Long, + var totalContentLength: Long +) : RequestBody() { protected var countingSink: CountingSink? = null override fun contentType(): MediaType? { return delegate.contentType() @@ -37,11 +42,12 @@ class CountingRequestBody(protected var delegate: RequestBody, protected var lis protected inner class CountingSink(delegate: Sink?) : ForwardingSink(delegate!!) { private var bytesWritten: Long = 0 + @Throws(IOException::class) override fun write(source: Buffer, byteCount: Long) { super.write(source, byteCount) bytesWritten += byteCount - listener.onRequestProgress(bytesWritten, contentLength()) + listener.onRequestProgress(offset + bytesWritten, totalContentLength) } } diff --git a/app/src/main/java/fr/free/nrw/commons/upload/FileUtilsWrapper.java b/app/src/main/java/fr/free/nrw/commons/upload/FileUtilsWrapper.java index 95b157121..de8429136 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/FileUtilsWrapper.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/FileUtilsWrapper.java @@ -1,33 +1,84 @@ package fr.free.nrw.commons.upload; +import android.content.Context; +import io.reactivex.Observable; +import java.io.BufferedInputStream; +import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import javax.inject.Inject; import javax.inject.Singleton; +import timber.log.Timber; @Singleton public class FileUtilsWrapper { - @Inject - public FileUtilsWrapper() { + @Inject + public FileUtilsWrapper() { - } + } - public String getFileExt(String fileName) { - return FileUtils.getFileExt(fileName); - } + public String getFileExt(String fileName) { + return FileUtils.getFileExt(fileName); + } - public String getSHA1(InputStream is) { - return FileUtils.getSHA1(is); - } + public String getSHA1(InputStream is) { + return FileUtils.getSHA1(is); + } - public FileInputStream getFileInputStream(String filePath) throws FileNotFoundException { - return FileUtils.getFileInputStream(filePath); - } + public FileInputStream getFileInputStream(String filePath) throws FileNotFoundException { + return FileUtils.getFileInputStream(filePath); + } - public String getGeolocationOfFile(String filePath) { - return FileUtils.getGeolocationOfFile(filePath); + public String getGeolocationOfFile(String filePath) { + return FileUtils.getGeolocationOfFile(filePath); + } + + + /** + * Takes a file as input and returns an Observable of files with the specified chunk size + */ + public Observable getFileChunks(Context context, File file, final int chunkSize) + throws IOException { + final byte[] buffer = new byte[chunkSize]; + + //try-with-resources to ensure closing stream + try (final FileInputStream fis = new FileInputStream(file); + final BufferedInputStream bis = new BufferedInputStream(fis)) { + final List buffers = new ArrayList<>(); + int size; + while ((size = bis.read(buffer)) > 0) { + buffers.add(writeToFile(context, Arrays.copyOf(buffer, size), file.getName(), + getFileExt(file.getName()))); + } + return Observable.fromIterable(buffers); } + } + + /** + * Create a temp file containing the passed byte data. + */ + private File writeToFile(Context context, final byte[] data, final String fileName, + String fileExtension) + throws IOException { + final File file = File.createTempFile(fileName, fileExtension, context.getCacheDir()); + try { + if (!file.exists()) { + file.createNewFile(); + } + final FileOutputStream fos = new FileOutputStream(file); + fos.write(data); + fos.close(); + } catch (final Exception throwable) { + Timber.e(throwable, "Failed to create file"); + } + return file; + } } diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadClient.java b/app/src/main/java/fr/free/nrw/commons/upload/UploadClient.java index 39c42cc1d..37948b987 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/UploadClient.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadClient.java @@ -4,11 +4,15 @@ import static fr.free.nrw.commons.di.NetworkingModule.NAMED_COMMONS_CSRF; import android.content.Context; import android.net.Uri; +import androidx.annotation.Nullable; import fr.free.nrw.commons.CommonsApplication; import fr.free.nrw.commons.contributions.Contribution; import fr.free.nrw.commons.upload.UploadService.NotificationUpdateProgressListener; import io.reactivex.Observable; import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.concurrent.atomic.AtomicReference; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; @@ -16,59 +20,112 @@ import okhttp3.MediaType; import okhttp3.MultipartBody; import okhttp3.RequestBody; import org.wikipedia.csrf.CsrfTokenClient; +import timber.log.Timber; @Singleton public class UploadClient { - private final UploadInterface uploadInterface; - private final CsrfTokenClient csrfTokenClient; - private final PageContentsCreator pageContentsCreator; + private final int CHUNK_SIZE = 256 * 1024; // 256 KB - @Inject - public UploadClient(UploadInterface uploadInterface, - @Named(NAMED_COMMONS_CSRF) CsrfTokenClient csrfTokenClient, - PageContentsCreator pageContentsCreator) { - this.uploadInterface = uploadInterface; - this.csrfTokenClient = csrfTokenClient; - this.pageContentsCreator = pageContentsCreator; + private final UploadInterface uploadInterface; + private final CsrfTokenClient csrfTokenClient; + private final PageContentsCreator pageContentsCreator; + private final FileUtilsWrapper fileUtilsWrapper; + + @Inject + public UploadClient(final UploadInterface uploadInterface, + @Named(NAMED_COMMONS_CSRF) final CsrfTokenClient csrfTokenClient, + final PageContentsCreator pageContentsCreator, + final FileUtilsWrapper fileUtilsWrapper) { + this.uploadInterface = uploadInterface; + this.csrfTokenClient = csrfTokenClient; + this.pageContentsCreator = pageContentsCreator; + this.fileUtilsWrapper = fileUtilsWrapper; + } + + /** + * Upload file to stash in chunks of specified size. Uploading files in chunks will make handling + * of large files easier. Also, it will be useful in supporting pause/resume of uploads + */ + Observable uploadFileToStash( + final Context context, final String filename, final File file, + final NotificationUpdateProgressListener notificationUpdater) throws IOException { + final Observable fileChunks = fileUtilsWrapper.getFileChunks(context, file, CHUNK_SIZE); + final MediaType mediaType = MediaType + .parse(FileUtils.getMimeType(context, Uri.parse(file.getPath()))); + + final long[] offset = {0}; + final String[] fileKey = {null}; + final AtomicReference result = new AtomicReference<>(); + fileChunks.blockingForEach(chunkFile -> { + final RequestBody requestBody = RequestBody + .create(mediaType, chunkFile); + final CountingRequestBody countingRequestBody = new CountingRequestBody(requestBody, + notificationUpdater::onProgress, offset[0], file.length()); + uploadChunkToStash(filename, + file.length(), + offset[0], + fileKey[0], + countingRequestBody).blockingSubscribe(uploadResult -> { + result.set(uploadResult); + offset[0] = uploadResult.getOffset(); + fileKey[0] = uploadResult.getFilekey(); + }); + }); + return Observable.just(result.get()); + } + + /** + * Uploads a file chunk to stash + * + * @param filename The name of the file being uploaded + * @param fileSize The total size of the file + * @param offset The offset returned by the previous chunk upload + * @param fileKey The filekey returned by the previous chunk upload + * @param countingRequestBody Request body with chunk file + * @return + */ + Observable uploadChunkToStash(final String filename, + final long fileSize, + final long offset, + final String fileKey, + final CountingRequestBody countingRequestBody) { + final MultipartBody.Part filePart = MultipartBody.Part + .createFormData("chunk", filename, countingRequestBody); + try { + return uploadInterface.uploadFileToStash(toRequestBody(filename), + toRequestBody(String.valueOf(fileSize)), + toRequestBody(String.valueOf(offset)), + toRequestBody(fileKey), + toRequestBody(csrfTokenClient.getTokenBlocking()), + filePart) + .map(UploadResponse::getUpload); + } catch (final Throwable throwable) { + Timber.e(throwable, "Failed to upload chunk to stash"); + return Observable.error(throwable); } + } - Observable uploadFileToStash(Context context, String filename, File file, - NotificationUpdateProgressListener notificationUpdater) { - RequestBody requestBody = RequestBody - .create(MediaType.parse(FileUtils.getMimeType(context, Uri.parse(file.getPath()))), file); + @Nullable + private RequestBody toRequestBody(@Nullable final String value) { + return value == null ? null : RequestBody.create(okhttp3.MultipartBody.FORM, value); + } - CountingRequestBody countingRequestBody = new CountingRequestBody(requestBody, - (bytesWritten, contentLength) -> notificationUpdater - .onProgress(bytesWritten, contentLength)); - MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", filename, countingRequestBody); - RequestBody fileNameRequestBody = RequestBody.create(okhttp3.MultipartBody.FORM, filename); - RequestBody tokenRequestBody; - try { - tokenRequestBody = RequestBody.create(MultipartBody.FORM, csrfTokenClient.getTokenBlocking()); - return uploadInterface.uploadFileToStash(fileNameRequestBody, tokenRequestBody, filePart) - .map(stashUploadResponse -> stashUploadResponse.getUpload()); - } catch (Throwable throwable) { - throwable.printStackTrace(); - return Observable.error(throwable); - } - } - - Observable uploadFileFromStash(Context context, - Contribution contribution, - String uniqueFileName, - String fileKey) { - try { - return uploadInterface - .uploadFileFromStash(csrfTokenClient.getTokenBlocking(), - pageContentsCreator.createFrom(contribution), - CommonsApplication.DEFAULT_EDIT_SUMMARY, - uniqueFileName, - fileKey).map(uploadResponse -> uploadResponse.getUpload()); - } catch (Throwable throwable) { - throwable.printStackTrace(); - return Observable.error(throwable); - } + Observable uploadFileFromStash(final Context context, + final Contribution contribution, + final String uniqueFileName, + final String fileKey) { + try { + return uploadInterface + .uploadFileFromStash(csrfTokenClient.getTokenBlocking(), + pageContentsCreator.createFrom(contribution), + CommonsApplication.DEFAULT_EDIT_SUMMARY, + uniqueFileName, + fileKey).map(UploadResponse::getUpload); + } catch (final Throwable throwable) { + throwable.printStackTrace(); + return Observable.error(throwable); } + } } diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadInterface.java b/app/src/main/java/fr/free/nrw/commons/upload/UploadInterface.java index af3ffd77d..9ee3023a6 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/UploadInterface.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadInterface.java @@ -16,19 +16,22 @@ import static org.wikipedia.dataclient.Service.MW_API_PREFIX; public interface UploadInterface { - @Multipart - @POST(MW_API_PREFIX + "action=upload&stash=1&ignorewarnings=1") - Observable uploadFileToStash(@Part("filename") RequestBody filename, - @Part("token") RequestBody token, - @Part MultipartBody.Part filePart); + @Multipart + @POST(MW_API_PREFIX + "action=upload&stash=1&ignorewarnings=1") + Observable uploadFileToStash(@Part("filename") RequestBody filename, + @Part("filesize") RequestBody totalFileSize, + @Part("offset") RequestBody offset, + @Part("filekey") RequestBody fileKey, + @Part("token") RequestBody token, + @Part MultipartBody.Part filePart); - @Headers("Cache-Control: no-cache") - @POST(MW_API_PREFIX + "action=upload&ignorewarnings=1") - @FormUrlEncoded - @NonNull - Observable uploadFileFromStash(@NonNull @Field("token") String token, - @NonNull @Field("text") String text, - @NonNull @Field("comment") String comment, - @NonNull @Field("filename") String filename, - @NonNull @Field("filekey") String filekey); + @Headers("Cache-Control: no-cache") + @POST(MW_API_PREFIX + "action=upload&ignorewarnings=1") + @FormUrlEncoded + @NonNull + Observable uploadFileFromStash(@NonNull @Field("token") String token, + @NonNull @Field("text") String text, + @NonNull @Field("comment") String comment, + @NonNull @Field("filename") String filename, + @NonNull @Field("filekey") String filekey); } diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadResult.kt b/app/src/main/java/fr/free/nrw/commons/upload/UploadResult.kt index d3ec0cc64..0f3f6761e 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/UploadResult.kt +++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadResult.kt @@ -7,6 +7,7 @@ private const val RESULT_SUCCESS = "Success" data class UploadResult( val result: String, val filekey: String, + val offset: Int, val filename: String, val sessionkey: String, val imageinfo: ImageInfo diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadService.java b/app/src/main/java/fr/free/nrw/commons/upload/UploadService.java index 0d9459c90..ad879aa39 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/UploadService.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadService.java @@ -22,24 +22,16 @@ import fr.free.nrw.commons.contributions.MainActivity; import fr.free.nrw.commons.di.CommonsApplicationModule; import fr.free.nrw.commons.di.CommonsDaggerService; import fr.free.nrw.commons.media.MediaClient; -import fr.free.nrw.commons.utils.CommonsDateUtil; import fr.free.nrw.commons.wikidata.WikidataEditService; -import io.reactivex.Completable; import io.reactivex.Observable; import io.reactivex.Scheduler; -import io.reactivex.Single; import io.reactivex.disposables.CompositeDisposable; -import io.reactivex.disposables.Disposable; -import io.reactivex.functions.Action; -import io.reactivex.functions.Consumer; import io.reactivex.processors.PublishProcessor; import io.reactivex.schedulers.Schedulers; import java.io.File; import java.io.IOException; -import java.text.ParseException; import java.util.HashSet; import java.util.Set; -import java.util.concurrent.Callable; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.inject.Inject; @@ -316,7 +308,7 @@ public class UploadService extends CommonsDaggerService { .add(wikidataEditService.addDepictionsAndCaptions(uploadResult, contribution)); WikidataPlace wikidataPlace = contribution.getWikidataPlace(); if (wikidataPlace != null && wikidataPlace.getImageValue() == null) { - wikidataEditService.createImageClaim(wikidataPlace, uploadResult); + wikidataEditService.createClaim(wikidataPlace, uploadResult.getFilename(), contribution.getCaptions()); } saveCompletedContribution(contribution, uploadResult); } diff --git a/app/src/main/java/fr/free/nrw/commons/wikidata/WikidataClient.java b/app/src/main/java/fr/free/nrw/commons/wikidata/WikidataClient.java index 24f518b52..ebee708e9 100644 --- a/app/src/main/java/fr/free/nrw/commons/wikidata/WikidataClient.java +++ b/app/src/main/java/fr/free/nrw/commons/wikidata/WikidataClient.java @@ -1,6 +1,6 @@ package fr.free.nrw.commons.wikidata; -import fr.free.nrw.commons.upload.WikidataItem; +import com.google.gson.Gson; import org.jetbrains.annotations.NotNull; import javax.inject.Inject; @@ -9,64 +9,38 @@ import javax.inject.Singleton; import fr.free.nrw.commons.wikidata.model.AddEditTagResponse; import io.reactivex.Observable; import io.reactivex.ObservableSource; -import okhttp3.MediaType; -import okhttp3.RequestBody; +import org.wikipedia.wikidata.Statement_partial; @Singleton public class WikidataClient { - private final WikidataInterface wikidataInterface; + private final WikidataInterface wikidataInterface; + private final Gson gson; - @Inject - public WikidataClient(WikidataInterface wikidataInterface) { - this.wikidataInterface = wikidataInterface; - } + @Inject + public WikidataClient(WikidataInterface wikidataInterface, final Gson gson) { + this.wikidataInterface = wikidataInterface; + this.gson = gson; + } - /** - * Create wikidata claim to add P18 value - * @param entity wikidata entity ID - * @param value value of the P18 edit - * @return revisionID of the edit - */ - Observable createImageClaim(WikidataItem entity, String value) { - return getCsrfToken() - .flatMap(csrfToken -> wikidataInterface.postCreateClaim( - toRequestBody(entity.getId()), - toRequestBody("value"), - toRequestBody(WikidataProperties.IMAGE.getPropertyName()), - toRequestBody(value), - toRequestBody("en"), - toRequestBody(csrfToken))) - .map(mwPostResponse -> mwPostResponse.getPageinfo().getLastrevid()); - } + /** + * Create wikidata claim to add P18 value + * + * @return revisionID of the edit + */ + Observable setClaim(Statement_partial claim, String tags) { + return getCsrfToken() + .flatMap(csrfToken -> wikidataInterface.postSetClaim(gson.toJson(claim), tags, csrfToken)) + .map(mwPostResponse -> mwPostResponse.getPageinfo().getLastrevid()); + } - /** - * Converts string value to RequestBody for multipart request - */ - private RequestBody toRequestBody(String value) { - return RequestBody.create(MediaType.parse("text/plain"), value); - } - - /** - * Get csrf token for wikidata edit - */ - @NotNull - private Observable getCsrfToken() { - return wikidataInterface.getCsrfToken().map(mwQueryResponse -> mwQueryResponse.query().csrfToken()); - } - - /** - * Add edit tag for a given revision ID. The app currently uses this to tag P18 edits - * @param revisionId revision ID of the page edited - * @param tag to be added - * @param reason to be mentioned - */ - ObservableSource addEditTag(Long revisionId, String tag, String reason) { - return getCsrfToken() - .flatMap(csrfToken -> wikidataInterface.addEditTag(String.valueOf(revisionId), - tag, - reason, - csrfToken)); - } + /** + * Get csrf token for wikidata edit + */ + @NotNull + private Observable getCsrfToken() { + return wikidataInterface.getCsrfToken() + .map(mwQueryResponse -> mwQueryResponse.query().csrfToken()); + } } diff --git a/app/src/main/java/fr/free/nrw/commons/wikidata/WikidataEditService.java b/app/src/main/java/fr/free/nrw/commons/wikidata/WikidataEditService.java index a323efb55..3f5a84cf3 100644 --- a/app/src/main/java/fr/free/nrw/commons/wikidata/WikidataEditService.java +++ b/app/src/main/java/fr/free/nrw/commons/wikidata/WikidataEditService.java @@ -20,49 +20,58 @@ import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; import java.util.Locale; +import java.util.Map; +import java.util.UUID; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.wikipedia.dataclient.mwapi.MwPostResponse; +import org.wikipedia.wikidata.DataValue; +import org.wikipedia.wikidata.DataValue.ValueString; import org.wikipedia.wikidata.EditClaim; +import org.wikipedia.wikidata.Snak_partial; +import org.wikipedia.wikidata.Statement_partial; +import org.wikipedia.wikidata.WikiBaseMonolingualTextValue; import timber.log.Timber; /** - * This class is meant to handle the Wikidata edits made through the app - * It will talk with MediaWiki Apis to make the necessary calls, log the edits and fire listeners - * on successful edits + * This class is meant to handle the Wikidata edits made through the app It will talk with MediaWiki + * Apis to make the necessary calls, log the edits and fire listeners on successful edits */ @Singleton public class WikidataEditService { - private static final String COMMONS_APP_TAG = "wikimedia-commons-app"; - private static final String COMMONS_APP_EDIT_REASON = "Add tag for edits made using Android Commons app"; + public static final String COMMONS_APP_TAG = "wikimedia-commons-app"; - private final Context context; - private final WikidataEditListener wikidataEditListener; - private final JsonKvStore directKvStore; - private final WikiBaseClient wikiBaseClient; - private final WikidataClient wikidataClient; - private final Gson gson; + private final Context context; + private final WikidataEditListener wikidataEditListener; + private final JsonKvStore directKvStore; + private final WikiBaseClient wikiBaseClient; + private final WikidataClient wikidataClient; + private final Gson gson; @Inject - public WikidataEditService(final Context context, + public WikidataEditService(final Context context, final WikidataEditListener wikidataEditListener, @Named("default_preferences") final JsonKvStore directKvStore, final WikiBaseClient wikiBaseClient, final WikidataClient wikidataClient, final Gson gson) { - this.context = context; - this.wikidataEditListener = wikidataEditListener; - this.directKvStore = directKvStore; - this.wikiBaseClient = wikiBaseClient; - this.wikidataClient = wikidataClient; + this.context = context; + this.wikidataEditListener = wikidataEditListener; + this.directKvStore = directKvStore; + this.wikiBaseClient = wikiBaseClient; + this.wikidataClient = wikidataClient; this.gson = gson; } /** - * Edits the wikibase entity by adding DEPICTS property. - * Adding DEPICTS property requires call to the wikibase API to set tag against the entity. + * Edits the wikibase entity by adding DEPICTS property. Adding DEPICTS property requires call to + * the wikibase API to set tag against the entity. */ @SuppressLint("CheckResult") private Observable addDepictsProperty(final String fileEntityId, @@ -70,7 +79,7 @@ public class WikidataEditService { final EditClaim data = editClaim( ConfigUtils.isBetaFlavour() ? "Q10" // Wikipedia:Sandbox (Q10) - : depictedItem.getId() + : depictedItem.getId() ); return wikiBaseClient.postEditEntity(PAGE_ID_PREFIX + fileEntityId, gson.toJson(data)) @@ -81,44 +90,46 @@ public class WikidataEditService { Timber.d("Unable to set DEPICTS property for %s", fileEntityId); } }) - .doOnError( throwable -> { + .doOnError(throwable -> { Timber.e(throwable, "Error occurred while setting DEPICTS property"); ViewUtil.showLongToast(context, throwable.toString()); }) .subscribeOn(Schedulers.io()); - } + } private EditClaim editClaim(final String entityId) { return EditClaim.from(entityId, WikidataProperties.DEPICTS.getPropertyName()); } /** - * Show a success toast when the edit is made successfully - */ - private void showSuccessToast(final String wikiItemName) { - final String successStringTemplate = context.getString(R.string.successful_wikidata_edit); - final String successMessage = String.format(Locale.getDefault(), successStringTemplate, wikiItemName); - ViewUtil.showLongToast(context, successMessage); - } + * Show a success toast when the edit is made successfully + */ + private void showSuccessToast(final String wikiItemName) { + final String successStringTemplate = context.getString(R.string.successful_wikidata_edit); + final String successMessage = String + .format(Locale.getDefault(), successStringTemplate, wikiItemName); + ViewUtil.showLongToast(context, successMessage); + } /** - * Adds label to Wikidata using the fileEntityId and the edit token, obtained from csrfTokenClient - * - * @param fileEntityId - * @return - */ + * Adds label to Wikidata using the fileEntityId and the edit token, obtained from + * csrfTokenClient + * + * @param fileEntityId + * @return + */ - @SuppressLint("CheckResult") - private Observable addCaption(final long fileEntityId, final String languageCode, - final String captionValue) { - return wikiBaseClient.addLabelstoWikidata(fileEntityId, languageCode, captionValue) - .doOnNext(mwPostResponse -> onAddCaptionResponse(fileEntityId, mwPostResponse) ) - .doOnError(throwable -> { - Timber.e(throwable, "Error occurred while setting Captions"); - ViewUtil.showLongToast(context, context.getString(R.string.wikidata_edit_failure)); - }) - .map(mwPostResponse -> mwPostResponse != null); - } + @SuppressLint("CheckResult") + private Observable addCaption(final long fileEntityId, final String languageCode, + final String captionValue) { + return wikiBaseClient.addLabelstoWikidata(fileEntityId, languageCode, captionValue) + .doOnNext(mwPostResponse -> onAddCaptionResponse(fileEntityId, mwPostResponse)) + .doOnError(throwable -> { + Timber.e(throwable, "Error occurred while setting Captions"); + ViewUtil.showLongToast(context, context.getString(R.string.wikidata_edit_failure)); + }) + .map(mwPostResponse -> mwPostResponse != null); + } private void onAddCaptionResponse(Long fileEntityId, MwPostResponse response) { if (response != null) { @@ -128,29 +139,41 @@ public class WikidataEditService { } } - public void createImageClaim(@Nullable final WikidataPlace wikidataPlace, final UploadResult imageUpload) { + public void createClaim(@Nullable final WikidataPlace wikidataPlace, final String fileName, final + Map captions) { if (!(directKvStore.getBoolean("Picture_Has_Correct_Location", true))) { - Timber.d("Image location and nearby place location mismatched, so Wikidata item won't be edited"); + Timber + .d("Image location and nearby place location mismatched, so Wikidata item won't be edited"); return; } - editWikidataImageProperty(wikidataPlace, imageUpload); + addImageAndMediaLegends(wikidataPlace, fileName, captions); } - @SuppressLint("CheckResult") - private void editWikidataImageProperty(final WikidataItem wikidataItem, final UploadResult imageUpload) { - wikidataClient.createImageClaim(wikidataItem, String.format("\"%s\"", imageUpload.getFilename())) - .flatMap(revisionId -> { - if (revisionId != -1) { - return wikidataClient.addEditTag(revisionId, COMMONS_APP_TAG, COMMONS_APP_EDIT_REASON); - } - throw new RuntimeException("Unable to edit wikidata item"); - }) - .subscribeOn(Schedulers.io()) + public void addImageAndMediaLegends(final WikidataItem wikidataItem, final String fileName, + final Map captions) { + final Snak_partial p18 = new Snak_partial("value", WikidataProperties.IMAGE.getPropertyName(), + new ValueString(fileName.replace("File:", ""))); + + final List snaks = new ArrayList<>(); + for (final Map.Entry entry : captions.entrySet()) { + snaks.add(new Snak_partial("value", + WikidataProperties.MEDIA_LEGENDS.getPropertyName(), new DataValue.MonoLingualText( + new WikiBaseMonolingualTextValue(entry.getValue(), entry.getKey())))); + } + + final String id = wikidataItem.getId() + "$" + UUID.randomUUID().toString(); + final Statement_partial claim = new Statement_partial(p18, "statement", "normal", id, + Collections.singletonMap(WikidataProperties.MEDIA_LEGENDS.getPropertyName(), snaks), + Arrays.asList(WikidataProperties.MEDIA_LEGENDS.getPropertyName())); + + wikidataClient.setClaim(claim, COMMONS_APP_TAG).subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe(revisionId -> handleImageClaimResult(wikidataItem, String.valueOf(revisionId)), throwable -> { - Timber.e(throwable, "Error occurred while making claim"); - ViewUtil.showLongToast(context, context.getString(R.string.wikidata_edit_failure)); - }); + .subscribe(revisionId -> handleImageClaimResult(wikidataItem, String.valueOf(revisionId)), + throwable -> { + Timber.e(throwable, "Error occurred while making claim"); + ViewUtil.showLongToast(context, context.getString(R.string.wikidata_edit_failure)); + }); + ; } private void handleImageClaimResult(final WikidataItem wikidataItem, final String revisionId) { @@ -185,9 +208,9 @@ public class WikidataEditService { } } ).subscribe( - success -> Timber.d("edit response: %s", success), - throwable -> Timber.e(throwable, "posting edits failed") - ); + success -> Timber.d("edit response: %s", success), + throwable -> Timber.e(throwable, "posting edits failed") + ); } private Observable captionEdits(Contribution contribution, Long fileEntityId) { @@ -202,6 +225,6 @@ public class WikidataEditService { depictedItems.add(wikidataPlace); } return Observable.fromIterable(depictedItems) - .concatMap( wikidataItem -> addDepictsProperty(fileEntityId.toString(), wikidataItem)); + .concatMap(wikidataItem -> addDepictsProperty(fileEntityId.toString(), wikidataItem)); } } diff --git a/app/src/main/java/fr/free/nrw/commons/wikidata/WikidataInterface.java b/app/src/main/java/fr/free/nrw/commons/wikidata/WikidataInterface.java index 25d5f43ea..9f240c150 100644 --- a/app/src/main/java/fr/free/nrw/commons/wikidata/WikidataInterface.java +++ b/app/src/main/java/fr/free/nrw/commons/wikidata/WikidataInterface.java @@ -2,6 +2,7 @@ package fr.free.nrw.commons.wikidata; import androidx.annotation.NonNull; +import com.google.gson.JsonObject; import org.wikipedia.dataclient.mwapi.MwQueryResponse; import fr.free.nrw.commons.wikidata.model.AddEditTagResponse; @@ -20,35 +21,21 @@ import static org.wikipedia.dataclient.Service.MW_API_PREFIX; public interface WikidataInterface { - /** - * Wikidata create claim API. Posts a new claim for the given entity ID - */ - @Headers("Cache-Control: no-cache") - @POST("w/api.php?format=json&errorformat=plaintext&action=wbcreateclaim&errorlang=uselang") - @Multipart - Observable postCreateClaim(@NonNull @Part("entity") RequestBody entity, - @NonNull @Part("snaktype") RequestBody snakType, - @NonNull @Part("property") RequestBody property, - @NonNull @Part("value") RequestBody value, - @NonNull @Part("uselang") RequestBody useLang, - @NonNull @Part("token") RequestBody token); + /** + * Get edit token for wikidata wiki site + */ + @Headers("Cache-Control: no-cache") + @GET(MW_API_PREFIX + "action=query&meta=tokens&type=csrf") + @NonNull + Observable getCsrfToken(); - /** - * Add edit tag and reason for any revision - */ - @Headers("Cache-Control: no-cache") - @POST(MW_API_PREFIX + "action=tag") - @FormUrlEncoded - Observable addEditTag(@NonNull @Field("revid") String revId, - @NonNull @Field("add") String tagName, - @NonNull @Field("reason") String reason, - @NonNull @Field("token") String token); - - /** - * Get edit token for wikidata wiki site - */ - @Headers("Cache-Control: no-cache") - @GET(MW_API_PREFIX + "action=query&meta=tokens&type=csrf") - @NonNull - Observable getCsrfToken(); + /** + * Wikidata create claim API. Posts a new claim for the given entity ID + */ + @Headers("Cache-Control: no-cache") + @POST("w/api.php?format=json&action=wbsetclaim") + @FormUrlEncoded + Observable postSetClaim(@NonNull @Field("claim") String request, + @NonNull @Field("tags") String tags, + @NonNull @Field("token") String token); } diff --git a/app/src/main/java/fr/free/nrw/commons/wikidata/WikidataProperties.kt b/app/src/main/java/fr/free/nrw/commons/wikidata/WikidataProperties.kt index 14db02a27..dbeb0dc76 100644 --- a/app/src/main/java/fr/free/nrw/commons/wikidata/WikidataProperties.kt +++ b/app/src/main/java/fr/free/nrw/commons/wikidata/WikidataProperties.kt @@ -6,5 +6,6 @@ enum class WikidataProperties(val propertyName: String) { IMAGE("P18"), DEPICTS(BuildConfig.DEPICTS_PROPERTY), COMMONS_CATEGORY("P373"), - INSTANCE_OF("P31"); + INSTANCE_OF("P31"), + MEDIA_LEGENDS("P2096"); } diff --git a/app/src/main/res/layout/dialog_nearby.xml b/app/src/main/res/layout/dialog_nearby.xml new file mode 100644 index 000000000..46a35d477 --- /dev/null +++ b/app/src/main/res/layout/dialog_nearby.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index 84f0de442..2681347cc 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -4,6 +4,7 @@ * Abijeet Patro * Amirsara * Arash.pt +* BaRaN6161 TURK * Ebraminio * Eshagh79 * FarsiNevis @@ -28,7 +29,11 @@ %1$d پرونده در حال بارگذاری %1$d پرونده در حال بارگذاری - {{%1$d|zero=@string/contributions_subtitle_zero|one=(%1$d)|(%1$d)}} + + \@string/contributions_subtitle_zero + (%1$d) + (%1$d) + شروع %1$d بارگذاری پرونده شروع بارگذاری %1$d پرونده diff --git a/app/src/main/res/values-ko-rKP/strings.xml b/app/src/main/res/values-ko-rKP/strings.xml index fef89a157..b695243bc 100644 --- a/app/src/main/res/values-ko-rKP/strings.xml +++ b/app/src/main/res/values-ko-rKP/strings.xml @@ -19,6 +19,7 @@ %1$d개 올리적재 + %1$d개 올리적재 이 그림은 %1$s에 따라 사용이 허가됩니다 찾아보기 diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 864545c5b..37901f6c6 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -33,6 +33,7 @@ %1$d개 업로드 + %1$d개 업로드 이 그림은 %1$s에 따라 사용이 허가됩니다 diff --git a/app/src/main/res/values-nqo/error.xml b/app/src/main/res/values-nqo/error.xml new file mode 100644 index 000000000..64dc09445 --- /dev/null +++ b/app/src/main/res/values-nqo/error.xml @@ -0,0 +1,10 @@ + + + + ߞߐߡߐ߲ ߓߘߊ߫ ߗߌߙߏ߲߫ + ߋߜߋ߫. ߞߏ ߘߏ߫ ߓߍ߲߬ߣߍ߲߫ ߕߎ߲߬ ߕߍ߫߹ + ߊ߲ ߠߎ߬ ߘߍ߬ߡߍ߲߬ ߊ߬ ߘߐߓߍ߲߬ߠߌ߲ ߡߊ߬߹ + ߌ ߣߌ߫ ߗߋ߫߹ + diff --git a/app/src/main/res/values-nqo/strings.xml b/app/src/main/res/values-nqo/strings.xml index 6e4735df0..5f6a85cec 100644 --- a/app/src/main/res/values-nqo/strings.xml +++ b/app/src/main/res/values-nqo/strings.xml @@ -50,10 +50,14 @@ ߒ ߠߊ߫ ߟߊ߬ߦߟߍ߬ߣߍ߲ ߠߎ߬ ߊ߬ ߟߊߖߍ߲ߛߍ߲߫ ߊ߬ ߘߐߜߍ߫ ߛߏ߲߯ߓߊߟߊ߲ ߠߊ߫ - ߞߎ߲߬ߕߐ߰ ߞߊ߬ߣߌ߲߬ߣߍ߲ + ߝߍ߬ߛߓߍߟߌ (ߡߊߢߌ߬ߣߌ߲߬ߞߊ߬ߣߍ߲) + ߝߍ߬ߛߓߍߟߌ ߘߏ߫ ߡߊߛߐ߫ ߞߐߕߐ߮ ߣߌ߲߬ ߠߊ߫ ߖߊ߰ߣߌ߲߫ ߞߊ߲߬ߛߓߍߟߌ + ߝߍ߬ߛߓߍߟߌ (ߞߐߘߊ߲ ߦߋ߫ ߛߓߍߘߋ߲߫ ߂߅߅ ߟߋ߬ ߘߌ߫) ߜߊ߲߬ߞߎ߲߬ߠߌ߲ ߛߐ߲߬ߣߍ߲߫ ߕߍ߫ ߞߍ߫ ߟߊ߫ - ߞߙߏߝߏ ߟߊ߫ ߗߌߙߏ߲ߠߌ߲ ߜߊ߲߬ߞߎ߲߬ߠߌ߲ ߛߐ߲߬ߣߍ߲߫ ߕߍ߫ ߞߍ߫ ߟߊ߫ - ߌ ߟߊ߫ ߕߐ߯ ߟߊߓߊ߯ߕߊ ߣߌ߫ ߕߊ߬ߡߌ߲߬ߞߊ߲ ߠߎ߬ ߡߊߝߟߍ߫ ߖߊ߰ߣߌ߲߬ + ߛߊ߯ߛߊ߯ߟߌ߫ ߛߎߘߊ߲ߓߊߟߌ߫ ߛߘߍߡߊ߲߫ ߓߘߊ߫ ߞߍ߫ ߢߐ߲߮ ߞߐ߫ ߞߏߖߎ߯ߦߊ߫.ߊ߬ ߡߊߝߍߣߍ߲߫ ߡߌ߬ߛߍ߲߬ ߘߊ߲ߘߐ߫ ߞߐ߫ ߖߊ߰ߣߌ߲߫. + ߤߊߞߍ߬ߕߏ߫߸ ߟߊ߬ߓߊ߰ߙߊ߬ߟߊ ߣߌ߲߬ ߓߘߊ߫ ߓߊ߬ߟߌ߬ ߞߐ߬ߡߐ߲ ߞߊ߲߬ ߜߊ߲߬ߞߎ߲߬ߠߌ߲ ߓߘߊ߫ ߗߌߙߏ߲߫ ߊ߬ ߟߊߦߟߍ߬ ߟߊ߬ߘߏ߲߬ߠߌ߲ ߣߌ߲߬ ߕߐ߯ߟߊ߫ @@ -74,7 +78,7 @@ ߟߊߓߊ߯ߙߊߟߌ ߖߌ߬ߦߊ߬ߓߍ ߟߎ߬ ߦߌߟߡߊ ߞߊ߬ ߓߍ߲߬ - <u>ߜߎ߲߬ߘߎ߬ߢߐ߲߰ߦߊ ߞߎߙߎ߲߬ߘߎ</u> + ߜߎ߲߬ߘߎ߬ߢߐ߲߰ߦߊ ߞߎߙߎ߲߬ߘߎ ߞߊ߬ ߓߍ߲߬ ߞߙߐ߬ߛߌ߬ߕߊ ߗߋ߫ (ߢ:ߞߏ߲ߘߏ ߟߊ߫) ߢߎߡߍߙߋ߲ߞߏ߲ߘߏ ߛߊ߲߬ߠߌ߲߬ߢߐ߲߰ ߡߊߞߍߣߍ߲߫ ߕߴߦߋ߲߬ @@ -107,15 +111,21 @@ ߦߌߟߡߊ ߟߎ߬ ߟߊ߬ߢߎ߲߬ߠߌ߲ ߦߵߌ ߘߐ߫... ߊ߬ ߡߊ߫ ߓߊߕߐ߬ߡߐ߲߬ + ߝߍ߬ߛߓߍߟߌ߫ ߕߍ߫ ߦߋ߲߬ ߞߊ߲߬ߛߓߍߟߌ߫ ߕߴߦߋ߲߬ ߘߊߘߐߖߊߥߏ߫ ߕߴߦߋ߲߬ ߏ߬ߞߍ߫ ߛߌ߰ߢߐ߲߰ ߦߙߐ ߟߎ߬ ߛߌ߰ߢߐ߲߰ ߦߙߐ߫ ߡߊ߫ ߛߐ߬ߘߐ߲߫ ߖߊ߲߬ߓߌ߬ߟߊ߬ߟߌ + ߖߌ߬ߦߊ߬ߓߍ߫ ߓߊߟߌߣߍ߲ ߠߎ߬ ߦߋ߫ ߦߋ߲߬ + ߞߐߕߐ߮ ߣߌ߲߬ ߓߘߊ߫ ߘߐߕߌߢߍ߫ ߞߐߡߐ߲ ߞߊ߲߬. ߌ ߟߊߣߴߊ߬ ߟߊ߫ ߞߴߌ ߦߴߊ߬ ߝߍ߬ ߞߵߊ߬ ߘߊߓߊ߲߫؟ + ߊ߬ ߟߊߦߟߍ߬ ߐ߲߬ߐ߲߬ߐ߲߫ ߍ߲߬ߍ߲߫ + ߝߍ߬ߛߓߍߟߌ ߞߎ߲߬ߕߐ߮ + ߞߐߦߌߘߊߟߌ ߞߊ߲߬ߛߓߍߟߌ ߘߊߘߐߖߊߥߏ ߛߓߍߦߟߊ @@ -166,11 +176,15 @@ ߖߌ߬ߦߊ߬ߓߍ ߣߌ߲߬ ߕߊ߬ߣߍ߲߬ ߦߋ߫ ߦߙߐ߫ ߜߘߍ߫ ߟߋ߬ ߌ ߦߋ߫ ߖߌ߬ߦߊ߬ߓߍ ߟߋ߬ ߟߊߦߍ߬ߟߍ߫ ߖߊ߰ߣߌ߲߫ ߌ ߖߍ߬ߘߍ ߞߊ߬ ߡߍ߲ ߠߎ߬ ߕߊ߬. ߒ߬ߞߊ߬ ߌ ߞߊ߫ ߖߌ߬ߦߊ߬ߓߍ߫ ߟߊߦߍ߬ߟߍ߫ ߡߎ߰ߡߍ߫ ߌ ߞߊ߬ ߡߍ߲ ߠߎ߬ ߕߊ߬ ߡߐ߱ ߟߎ߬ ߟߊ߫ ߝߋߛߑߓߎߞ ߞߊ߲߬. ߌ ߦߴߊ߬ ߝߍ߬ ߞߊ߬ ߖߌ߬ߦߊ߬ߓߍ ߣߌ߲߬ ߠߊߦߟߍ߬ ߡߎߣߎ߲߬؟ + ߝߙߋߞߋ ߓߘߊ߫ ߦߋ߫ ߖߌ߬ߦߊ߬ߓߍ ߟߊ߫ ߖߊ߰ߣߌ߲߬ ߌ ߦߋ߫ ߖߌ߬ߦߊ߬ߓߍ߬ ߟߎ߫ ߟߋ߬ ߟߊߦߟߍ߬ ߌ ߖߍ߬ߘߍ ߞߊ߬ ߡߍ߲ ߠߎ߬ ߕߊ߬. ߞߏ߬ߣߌ߲߬ ߌ ߞߊߣߊ߬ ߖߌ߬ߦߊ߬ߓߍ߬ ߟߊߦߟߍ߬ ߌ ߣߊ߬ ߡߍ߲ ߠߊߖߌ߰ ߟߴߌ ߞߎ߲߬ ߓߟߐߟߐ ߟߊ߫. ߟߊ߬ߘߌ߬ߢߍ߬ߟߌ ߦߌ߬ߘߊ߬ ߌ ߜߊ߲߬ߞߎ߲߫ ߌ ߟߊ߫ ߖߊ߬ߕߋ߬ߘߊ ߟߊ߫. ߓߟߐߟߐ ߛߏ߲߯ߓߊߟߊ߲߫ ߡߊ߫ ߛߐ߬ߘߐ߲߬ ߞߊ߬ URL ߟߊߞߊ߬ ߝߎ߬ߕߎ߲߬ߕߌ߹ URL ߡߊ߫ ߛߐ߬ߘߐ߲߬ + ߞߍߦߙߐ߫ ߞߐߜߍ ߘߐߜߍ߫ ߝߊߙߊ߲ߝߊ߯ߛߌ߫ ߞߏ ߘߐ߫ + ߕߐ߯ߦߊߟߌ ߖߏ߰ߛߌ߬ߟߌ ߞߊ߲ߡߊ߬ + ߞߐߕߐ߮ ߕߐ߯ߦߊߟߌ ߖߏ߬ߛߟߌ߬ ߞߊ߲ߡߊ߬: %1$s ߊ߬ ߘߐߜߍ߫ ߛߏ߲߯ߓߊߟߊ߲ ߠߊ߫ ߊ߬ ߟߊߜߊ߲߫ ߌ ߜߊ߲߬ߞߎ߲߬ @@ -187,11 +201,11 @@ ߥߞߌߘߕߊ ߥߞߌߔߋߘߌߦߊ߫ ߞߐߡߐ߲ - <u>ߊ߲ ߡߐ߬ߟߐ߲ ߦߌ߬ߘߊ߬</u> - <u>ߢ.ߡ</u> + ߡߐ߬ߟߐ߲ ߘߴߊ߲ ߡߊ߬ + ߢ.ߡ ߓߟߐߟߐ߫ ߕߴߦߋ߲߬ ߓߟߐߟߐ ߦߋ߫ ߦߋ߲߬ - <u>ߘߟߊߡߌߘߊߟߌ</u> + ߘߟߊߡߌߘߊߟߌ ߞߊ߲ ߠߎ߬ ߞߊ߲ ߘߏ߫ ߓߊߕߐ߬ߡߐ߲߬ ߌ ߦߴߊ߬ ߝߍ߬ ߞߵߊ߬ ߘߟߊߡߌ߬ߘߊ߬ ߡߍ߲ ߘߐ߫. ߊ߬ ߘߐߛߊ߬ @@ -274,6 +288,11 @@ ߊ߬ ߟߐ߯ ߦߴߌ ߟߊ߫ ߞߊ߬ ߓߟߏߓߌߟߊߢߐ߲߯ߞߊ߲ߠߊ ߟߎ߬ ߞߎߟߎ߲ߖߋ߫ ߓߊ߬؟ ߐ߲߬ߤߐ߲߯߹ ߣߌ߲߬ ߝߊ߲߭ ߡߊ߫ ߦߌߟߡߊߦߊ߫ ߟߋ߬߹ ߖߌ߬ߦߊ߬ߓߍ ߣߌ߲߬ ߦߋ߫ %1$s ߦߌߟߡߊ ߟߎ߬ ߞߘߐ߫. + ߊ߬ ߦߋ߫ ߓߊߦߟߍߡߊ߲ߠߌ߲ ߤߊߞߍ ߞߎ߬ߙߎ߲߬ߘߎ ߕߌߢߍ ߟߋ߬ ߘߌ߫ ߓߊߏ߬ ߊ߬ ߦߋ߫ + ߒ߬ߒ߫߸ ߦߌߟߡߦߊߟߌ ߞߐߢߌ߬ߣߊ߬ߣߍ߲߹ + ߊ߬ ߞߍߣߍ߲߫ ߦߴߊ߬ ߢߊ߬ߣߍ߲߫ + ߒ߬ߒ߫߸ ߊ߬ ߞߍߣߍ߲߫ ߦߏ߫ ߞߣߍ ߕߴߦߋ߲߬ + ߊ߬ ߞߍߣߍ߲߫ ߦߴߊ߬ ߢߊ߬ߣߍ߲߫ ߒ߬ߒ߫߸ ߓߊߦߟߍߡߊ߲ ߤߊߞߍ ߡߊ߫ ߕߌߢߍ߫ ߊ߬ ߞߍߣߍ߲߫ ߦߴߊ߬ ߓߍ߲߬ߣߍ߲߫ ߐ߲߬ߐ߲ߐ߲߫߸ ߡߎ߲߬ߠߊ߫ ߍ߲߬ߍ߲߫ @@ -287,10 +306,16 @@ ߓߊߦߟߍߡߊ߲ ߤߊߞߍ ߘߌ߲߬ߞߌߙߊ ߖߌ߬ߦߊ߬ߕߊ߬ߟߊ߲ ߛߎ߮ߦߊ + ߖߌ߬ߦߊ߬ߓߍ ߞߌ߬ߓߊ߬ߙߏ߬ߦߊ + ߦߌߟߡߊߙߋ߲߫ ߕߴߦߋ߲߬ + ߘߊ߲߬ߠߊ߬ߕߍ߰ߟߌ ߡߊ߫ ߛߐ߬ߘߐ߲߬ + ߟߊ߬ߦߟߍ߬ߟߌ ߘߊߓߌ߬ߟߊ߬ ߡߎ߲߬ߠߊ߫ %1$s ߖߏ߬ߛߌ߬ߕߐ߫؟ %1$s ߟߊߦߟߍ߬ߣߍ߲߬ ߦߋ߫: %2$s ߟߋ߬ ߓߟߏ߫ ߞߊ߲߬ߛߓߍߟߌ ߞߍ߫ ߞߊ߲ ߓߊߖߎߡߊ + ߌ ߦߴߌ ߞߊߘߊ߲ ߞߊ߲߬ ߞߊ߬ %1$s ߕߐ߯ߦߊ߫ ߖߏ߬ߛߟߌ߬ ߞߊ߲ߡߊ߬ ߕߐ߯ߦߊߟߌ ߖߏ߰ߛߌ߬ߟߌ ߞߊ߲ߡߊ߬ + ߊ߬ ߓߘߊ߫ ߛߎߘߊ߲߫ %1$s ߕߐ߯ߦߊߟߌ ߦߴߌ ߘߐ߫ ߖߏ߬ߛߌ߬ߟߌ ߞߊ߲ߡߊ߬ ߊ߬ ߓߘߊ߫ ߗߌߙߏ߲߫ ߌ ߕߍ߫ ߛߋ߫ ߖߏ߰ߛߌ߬ߟߌ ߡߊߢߌߣߌ߲߫ ߠߊ߫ @@ -302,5 +327,14 @@ ߓߊߏ߬ ߊ߬ ߦߋ߫ ߖߌ߬ߦߊ߬ߓߍ ߟߊߖߍ߲ߛߍ߲߫ ߞߊߕߙߍ߬ ߌ ߡߊ߫ ߓߟߏߓߌߟߊߢߐ߲߯ߞߊ߲ ߛߌ߫ ߞߍ߫ ߡߎߣߎ߲߬ + ߖߊ߬ߕߋ߬ߘߊ ߓߘߊ߫ ߛߌ߲ߘߌ߫߹ ߝߎ߬ߕߎ߲߬ߕߌ ߘߏ߫ ߕߘߍ߬ ߦߋ߫ ߦߋ߲߬߹ + ߘߌ߬ߓߌ + ߘߏߜߘߍ߫ ߟߎ߫ ߟߊߢߎ߲߫ + ߖߌ߬ߦߊ߬ߓߍ ߝߙߊ߬ ߥߞߌߔߋߘߌߦߊ ߞߊ߲߬ + ߌ ߦߴߊ߬ ߝߍ߬ ߞߊ߬ ߖߌ߬ߦߊ߬ߓߍ ߣߌ߲߬ ߓߌ߬ߟߊ߬ %1$ ߞߊ߲ ߥߞߌߔߋߘߌߦߊ ߞߎߡߘߊ ߟߊ߫ ߓߊ߬؟ + ߊ߬ ߟߊߛߙߋߦߊ߫ + ߁߭. ߥߞߌߛߓߍߟߌ ߢߌ߲߬ ߠߎ߬ ߟߊߓߊ߯ߙߊ߫: + ߂.ߟߊ߬ߛߙߋ߬ߦߊ߬ߟߌ ߛߐ߲߬ߞߌ߲ ߓߍߣߊ߬ ߥߞߌߔߋߘߌߦߊ ߞߎߡߘߊ ߘߊߦߟߍ߬ + ߇߲. ߞߎߡߘߊ ߟߊߥߊ߲߬ߞߊ߫ diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 4e8db38ad..53b1d4390 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -600,4 +600,18 @@ Tog du dessa två bilder på samma plats? Vill du använda den högra bildens latitud/longitud? Läs in fler Inga platser hittades, försök ändra dina sökkriterier. + Lägg till bild på Wikipedia + Vill du lägga till denna bild i Wikipedia-artikeln på %1$s? + Instruktioner + Se till att följa riktlinjerna för hur man redigerar! + Bekräfta + Instruktioner + 1. Använd följande wikitext: + 2. Klicka på \"Bekräfta\" för att öppna Wikipedia-artikeln + 3. Hitta ett lämplig avsnitt i artikeln för din bild + 4. Klicka på redigeringsikonen (ser ut som en penna) för detta avsnitt. + 5. Klistra in wikitexten på det lämpliga stället. + 6. Redigera wikitexten för att placera den mer lämpligt vid behov. För mer information, se <a href=\"https://en.wikipedia.org/wiki/Wikipedia:Manual_of_Style/Images#How_to_place_an_image\">här</a>. + 7. Publicera artikeln + Kopiera wikikod till urklipp diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 624394be8..4071bc780 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -419,7 +419,7 @@ Fotoğraf yüklerken kullanıcı adınız yerine özel bir yazar adı kullanın Özel yazar adı Katkılar - Yakınımdakiler + Yakınındakiler Bildirimler Bildirimler (okunmuş) Yakınımdakiler bildirimi görüntüle diff --git a/app/src/main/res/values-xmf/strings.xml b/app/src/main/res/values-xmf/strings.xml index c7cc0bd02..880d0aaae 100644 --- a/app/src/main/res/values-xmf/strings.xml +++ b/app/src/main/res/values-xmf/strings.xml @@ -14,10 +14,12 @@ იჭყაფუ %1$d ეხარგუა იჭყაფუ %1$d ეხარგუა + იჭყაფუ %1$d ეხარგუა %1$d ეხარგუა %1$d ეხარგუა + %1$d ეხარგუა თე სურათი გიბჟინუ %1$s ლიცენზიათ diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index a8f6d71c5..3fadb61a8 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -28,9 +28,11 @@ 開始 %1$d 次上傳 + 開始 %1$d 次上傳 %1$d 次上傳 + %1$d 次上傳 此圖片會按 %1$s 協議授權上載 diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index 680ccbf6e..0613283d3 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -99,9 +99,10 @@ 我的上传 分享 在浏览器中查看 - 标题 (要求) + 说明 (要求) 请提供此文件的描述 描述 + 说明(255个字符以内) 无法登录 - 网络故障 无法登录——请检查您的用户名和密码 失败次数过多。请在几分钟后重试。 @@ -206,8 +207,10 @@ 找不到附近地点 警告 此文件已在共享资源下存在。您确定要继续吗? + 上传 + 说明 标题 描述 讨论 @@ -315,8 +318,8 @@ 维基数据 维基百科 共享资源 - <u>评价我们</u> - <u>常见问题</u> + 评价我们 + 常见问题 跳过指导 互联网不可用 互联网可用 @@ -324,7 +327,7 @@ 获取审查图片错误。按刷新键重试。 获取审查图片类别错误。按刷新按键重试。 找不到通知 - <u>翻译</u> + 翻译 语言 选择您希望提交翻译的语言 已处理 @@ -351,6 +354,7 @@ 加载子分类时发生错误。 媒体 分类 + 项目 特色 通过移动端上传 图片已添加到维基数据上的%1$s! @@ -575,6 +579,9 @@ 地点类型: 桥梁、博物馆、旅馆等 登录时出现一些问题,您必须重新设置您的密码! + 探索 + 书签 + 设置 出错了。无法设置壁纸 设为壁纸 正在设置壁纸。请稍等… @@ -582,4 +589,5 @@ 说明 确认 说明 + 复制维基代码到剪贴板 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ceb3986d1..2631e5d44 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -331,6 +331,9 @@ Retry Got it! These are the places near you that need pictures to illustrate their Wikipedia articles.\n\nClicking on \'SEARCH THIS AREA\' locks the map and launches a nearby search around that location. + This place needs a photo. + This place already has a photo. + This place no longer exists. Tapping this button brings up a list of these places You can upload a picture for any place from your gallery or camera diff --git a/app/src/test/kotlin/fr/free/nrw/commons/wikidata/WikidataClientTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/wikidata/WikidataClientTest.kt index ad51203de..a1ef96274 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/wikidata/WikidataClientTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/wikidata/WikidataClientTest.kt @@ -1,7 +1,9 @@ package fr.free.nrw.commons.wikidata -import com.nhaarman.mockitokotlin2.mock -import fr.free.nrw.commons.wikidata.model.AddEditTagResponse +import com.google.gson.Gson +import com.nhaarman.mockitokotlin2.whenever +import fr.free.nrw.commons.wikidata.model.PageInfo +import fr.free.nrw.commons.wikidata.model.WbCreateClaimResponse import io.reactivex.Observable import org.junit.Before import org.junit.Test @@ -14,12 +16,16 @@ import org.mockito.Mockito.mock import org.mockito.MockitoAnnotations import org.wikipedia.dataclient.mwapi.MwQueryResponse import org.wikipedia.dataclient.mwapi.MwQueryResult +import org.wikipedia.wikidata.Statement_partial class WikidataClientTest { @Mock internal var wikidataInterface: WikidataInterface? = null + @Mock + internal var gson: Gson? = null + @InjectMocks var wikidataClient: WikidataClient? = null @@ -35,26 +41,18 @@ class WikidataClientTest { .thenReturn(Observable.just(mwQueryResponse)) } - @Test - fun createClaim() { - `when`( - wikidataInterface!!.postCreateClaim( - any(), - any(), - any(), - any(), - any(), - any() - ) - ) - .thenReturn(Observable.just(mock())) - wikidataClient!!.createImageClaim(mock(), "test.jpg") - } - @Test fun addEditTag() { - `when`(wikidataInterface!!.addEditTag(anyString(), anyString(), anyString(), anyString())) - .thenReturn(Observable.just(mock(AddEditTagResponse::class.java))) - wikidataClient!!.addEditTag(1L, "test", "test") + val response = mock(WbCreateClaimResponse::class.java) + val pageInfo = mock(PageInfo::class.java) + whenever(pageInfo.lastrevid).thenReturn(1) + whenever(response.pageinfo).thenReturn(pageInfo) + `when`(wikidataInterface!!.postSetClaim(anyString(), anyString(), anyString())) + .thenReturn(Observable.just(response)) + whenever(gson!!.toJson(any(Statement_partial::class.java))).thenReturn("claim") + val request = mock(Statement_partial::class.java) + + val claim = wikidataClient!!.setClaim(request, "test").test() + .assertValue(1L) } } diff --git a/app/src/test/kotlin/fr/free/nrw/commons/wikidata/WikidataEditServiceTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/wikidata/WikidataEditServiceTest.kt index cc41f756c..2f6379ef3 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/wikidata/WikidataEditServiceTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/wikidata/WikidataEditServiceTest.kt @@ -1,13 +1,13 @@ package fr.free.nrw.commons.wikidata import android.content.Context +import com.google.gson.Gson import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.verifyZeroInteractions import com.nhaarman.mockitokotlin2.whenever import fr.free.nrw.commons.kvstore.JsonKvStore import fr.free.nrw.commons.upload.UploadResult import fr.free.nrw.commons.upload.WikidataPlace -import fr.free.nrw.commons.wikidata.model.AddEditTagResponse import io.reactivex.Observable import org.junit.Before import org.junit.Test @@ -15,7 +15,6 @@ import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.anyString import org.mockito.InjectMocks import org.mockito.Mock -import org.mockito.Mockito.* import org.mockito.MockitoAnnotations class WikidataEditServiceTest { @@ -31,6 +30,9 @@ class WikidataEditServiceTest { @Mock internal lateinit var wikibaseClient: WikiBaseClient + @Mock + internal lateinit var gson: Gson + @InjectMocks lateinit var wikidataEditService: WikidataEditService @@ -44,7 +46,7 @@ class WikidataEditServiceTest { fun noClaimsWhenLocationIsNotCorrect() { whenever(directKvStore.getBoolean("Picture_Has_Correct_Location", true)) .thenReturn(false) - wikidataEditService.createImageClaim(mock(), mock()) + wikidataEditService.createClaim(mock(), "Test.jpg", hashMapOf()) verifyZeroInteractions(wikidataClient) } @@ -52,15 +54,16 @@ class WikidataEditServiceTest { fun createImageClaim() { whenever(directKvStore.getBoolean("Picture_Has_Correct_Location", true)) .thenReturn(true) - whenever(wikidataClient.createImageClaim(any(), any())) - .thenReturn(Observable.just(1L)) - whenever(wikidataClient.addEditTag(anyLong(), anyString(), anyString())) - .thenReturn(Observable.just(mock(AddEditTagResponse::class.java))) whenever(wikibaseClient.getFileEntityId(any())).thenReturn(Observable.just(1L)) - val wikidataPlace:WikidataPlace = mock() + whenever(wikidataClient.setClaim(any(), anyString())) + .thenReturn(Observable.just(1L)) + val wikidataPlace: WikidataPlace = mock() val uploadResult = mock() whenever(uploadResult.filename).thenReturn("file") - wikidataEditService.createImageClaim(wikidataPlace, uploadResult) - verify(wikidataClient, times(1)).createImageClaim(wikidataPlace, """"file"""") + wikidataEditService.createClaim( + wikidataPlace, + uploadResult.filename, + hashMapOf() + ) } } diff --git a/data-client/src/main/java/org/wikipedia/wikidata/DataValue.kt b/data-client/src/main/java/org/wikipedia/wikidata/DataValue.kt index a1bf63577..c42ec05b9 100644 --- a/data-client/src/main/java/org/wikipedia/wikidata/DataValue.kt +++ b/data-client/src/main/java/org/wikipedia/wikidata/DataValue.kt @@ -12,7 +12,7 @@ sealed class DataValue(val type: String) { .registerSubtype(GlobeCoordinate_partial::class.java, GlobeCoordinate_partial.TYPE) .registerSubtype(Time_partial::class.java, Time_partial.TYPE) .registerSubtype(Quantity_partial::class.java, Quantity_partial.TYPE) - .registerSubtype(MonoLingualText_partial::class.java, MonoLingualText_partial.TYPE) + .registerSubtype(MonoLingualText::class.java, MonoLingualText.TYPE) } // "value": { @@ -87,7 +87,7 @@ sealed class DataValue(val type: String) { // "language": "ko" // } // } - class MonoLingualText_partial() : DataValue(TYPE) { + class MonoLingualText(val value: WikiBaseMonolingualTextValue) : DataValue(TYPE) { companion object { const val TYPE = "monolingualtext" } diff --git a/data-client/src/main/java/org/wikipedia/wikidata/Statement_partial.kt b/data-client/src/main/java/org/wikipedia/wikidata/Statement_partial.kt index 7258e354e..24d032b33 100644 --- a/data-client/src/main/java/org/wikipedia/wikidata/Statement_partial.kt +++ b/data-client/src/main/java/org/wikipedia/wikidata/Statement_partial.kt @@ -21,5 +21,8 @@ import com.google.gson.annotations.SerializedName data class Statement_partial( @SerializedName("mainsnak") val mainSnak: Snak_partial, val type: String, - val rank: String + val rank: String, + val id: String? = null, + val qualifiers: Map> = mapOf(), + @SerializedName("qualifiers-order") val qualifiersOrder: List = listOf() ) diff --git a/data-client/src/main/java/org/wikipedia/wikidata/WikiBaseMonolingualTextValue.kt b/data-client/src/main/java/org/wikipedia/wikidata/WikiBaseMonolingualTextValue.kt new file mode 100644 index 000000000..ab93fc168 --- /dev/null +++ b/data-client/src/main/java/org/wikipedia/wikidata/WikiBaseMonolingualTextValue.kt @@ -0,0 +1,13 @@ +package org.wikipedia.wikidata + +import com.google.gson.annotations.SerializedName + +/*"value": { + "type": "monolingualtext", + "value": { + "text": "some value", + "language": "en" + } +}*/ + +data class WikiBaseMonolingualTextValue(val text: String, val language: String) \ No newline at end of file