extraMap = new HashMap<>(4);
- extraMap.put(QUEUE_TIME, Long.toString(fetchState.responseTime - fetchState.submitTime));
- extraMap
- .put(FETCH_TIME, Long.toString(fetchState.fetchCompleteTime - fetchState.responseTime));
- extraMap
- .put(TOTAL_TIME, Long.toString(fetchState.fetchCompleteTime - fetchState.submitTime));
- extraMap.put(IMAGE_SIZE, Integer.toString(byteSize));
- return extraMap;
- }
-
- protected void fetchWithRequest(
- final OkHttpNetworkFetchState fetchState,
- final NetworkFetcher.Callback callback,
- final Request request) {
- final Call call = mCallFactory.newCall(request);
-
- fetchState
- .getContext()
- .addCallbacks(
- new BaseProducerContextCallbacks() {
- @Override
- public void onCancellationRequested() {
- onFetchCancellationRequested(call);
- }
- });
-
- call.enqueue(
- new okhttp3.Callback() {
- @Override
- public void onResponse(final Call call, final Response response) {
- onFetchResponse(fetchState, call, response, callback);
- }
-
- @Override
- public void onFailure(final Call call, final IOException e) {
- handleException(call, e, callback);
- }
- });
- }
-
- private void onFetchCancellationRequested(final Call call) {
- if (Looper.myLooper() != Looper.getMainLooper()) {
- call.cancel();
- } else {
- mCancellationExecutor.execute(call::cancel);
- }
- }
-
- private void onFetchResponse(final OkHttpNetworkFetchState fetchState, final Call call,
- final Response response,
- final NetworkFetcher.Callback callback) {
- fetchState.responseTime = SystemClock.elapsedRealtime();
- try (final ResponseBody body = response.body()) {
- if (!response.isSuccessful()) {
- handleException(
- call, new IOException("Unexpected HTTP code " + response),
- callback);
- return;
- }
-
- final BytesRange responseRange =
- BytesRange.fromContentRangeHeader(response.header("Content-Range"));
- if (responseRange != null
- && !(responseRange.from == 0
- && responseRange.to == BytesRange.TO_END_OF_CONTENT)) {
- // Only treat as a partial image if the range is not all of the content
- fetchState.setResponseBytesRange(responseRange);
- fetchState.setOnNewResultStatusFlags(Consumer.IS_PARTIAL_RESULT);
- }
-
- long contentLength = body.contentLength();
- if (contentLength < 0) {
- contentLength = 0;
- }
- callback.onResponse(body.byteStream(), (int) contentLength);
- } catch (final Exception e) {
- handleException(call, e, callback);
- }
- }
-
- /**
- * Handles exceptions.
- *
- * OkHttp notifies callers of cancellations via an IOException. If IOException is caught
- * after request cancellation, then the exception is interpreted as successful cancellation and
- * onCancellation is called. Otherwise onFailure is called.
- */
- private void handleException(final Call call, final Exception e, final Callback callback) {
- if (call.isCanceled()) {
- callback.onCancellation();
- } else {
- callback.onFailure(e);
- }
- }
-
- public static class OkHttpNetworkFetchState extends FetchState {
-
- public long submitTime;
- public long responseTime;
- public long fetchCompleteTime;
-
- public OkHttpNetworkFetchState(
- final Consumer consumer, final ProducerContext producerContext) {
- super(consumer, producerContext);
- }
- }
-}
diff --git a/app/src/main/java/fr/free/nrw/commons/media/CustomOkHttpNetworkFetcher.kt b/app/src/main/java/fr/free/nrw/commons/media/CustomOkHttpNetworkFetcher.kt
new file mode 100644
index 0000000000..c8de4022b3
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/media/CustomOkHttpNetworkFetcher.kt
@@ -0,0 +1,199 @@
+package fr.free.nrw.commons.media
+
+import android.os.Looper
+import android.os.SystemClock
+import com.facebook.imagepipeline.common.BytesRange
+import com.facebook.imagepipeline.image.EncodedImage
+import com.facebook.imagepipeline.producers.BaseNetworkFetcher
+import com.facebook.imagepipeline.producers.BaseProducerContextCallbacks
+import com.facebook.imagepipeline.producers.Consumer
+import com.facebook.imagepipeline.producers.FetchState
+import com.facebook.imagepipeline.producers.NetworkFetcher
+import com.facebook.imagepipeline.producers.ProducerContext
+import fr.free.nrw.commons.CommonsApplication
+import fr.free.nrw.commons.kvstore.JsonKvStore
+import okhttp3.CacheControl
+import okhttp3.Call
+import okhttp3.Callback
+import okhttp3.OkHttpClient
+import okhttp3.Request
+import okhttp3.Response
+import timber.log.Timber
+import java.io.IOException
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import javax.inject.Named
+import javax.inject.Singleton
+
+// Custom implementation of Fresco's Network fetcher to skip downloading of images when limited connection mode is enabled
+// https://github.com/facebook/fresco/blob/master/imagepipeline-backends/imagepipeline-okhttp3/src/main/java/com/facebook/imagepipeline/backends/okhttp3/OkHttpNetworkFetcher.java
+@Singleton
+class CustomOkHttpNetworkFetcher
+@JvmOverloads constructor(
+ private val mCallFactory: Call.Factory,
+ private val mCancellationExecutor: Executor,
+ private val defaultKvStore: JsonKvStore,
+ disableOkHttpCache: Boolean = true
+) : BaseNetworkFetcher() {
+
+ private val mCacheControl =
+ if (disableOkHttpCache) CacheControl.Builder().noStore().build() else null
+ private val isLimitedConnectionMode: Boolean
+ get() = defaultKvStore.getBoolean(
+ CommonsApplication.IS_LIMITED_CONNECTION_MODE_ENABLED,
+ false
+ )
+
+ /**
+ * @param okHttpClient client to use
+ */
+ @Inject
+ constructor(
+ okHttpClient: OkHttpClient,
+ @Named("default_preferences") defaultKvStore: JsonKvStore
+ ) : this(okHttpClient, okHttpClient.dispatcher.executorService, defaultKvStore)
+
+ /**
+ * @param mCallFactory custom [Call.Factory] for fetching image from the network
+ * @param mCancellationExecutor executor on which fetching cancellation is performed if
+ * cancellation is requested from the UI Thread
+ * @param disableOkHttpCache true if network requests should not be cached by OkHttp
+ */
+ override fun createFetchState(consumer: Consumer, context: ProducerContext) =
+ OkHttpNetworkFetchState(consumer, context)
+
+ override fun fetch(
+ fetchState: OkHttpNetworkFetchState, callback: NetworkFetcher.Callback
+ ) {
+ fetchState.submitTime = SystemClock.elapsedRealtime()
+
+ try {
+ if (isLimitedConnectionMode) {
+ Timber.d("Skipping loading of image as limited connection mode is enabled")
+ callback.onFailure(Exception("Failing image request as limited connection mode is enabled"))
+ return
+ }
+
+ val requestBuilder = Request.Builder().url(fetchState.uri.toString()).get()
+
+ if (mCacheControl != null) {
+ requestBuilder.cacheControl(mCacheControl)
+ }
+
+ val bytesRange = fetchState.context.imageRequest.bytesRange
+ if (bytesRange != null) {
+ requestBuilder.addHeader("Range", bytesRange.toHttpRangeHeaderValue())
+ }
+
+ fetchWithRequest(fetchState, callback, requestBuilder.build())
+ } catch (e: Exception) {
+ // handle error while creating the request
+ callback.onFailure(e)
+ }
+ }
+
+ override fun onFetchCompletion(fetchState: OkHttpNetworkFetchState, byteSize: Int) {
+ fetchState.fetchCompleteTime = SystemClock.elapsedRealtime()
+ }
+
+ override fun getExtraMap(fetchState: OkHttpNetworkFetchState, byteSize: Int) =
+ fetchState.toExtraMap(byteSize)
+
+ private fun fetchWithRequest(
+ fetchState: OkHttpNetworkFetchState, callback: NetworkFetcher.Callback, request: Request
+ ) {
+ val call = mCallFactory.newCall(request)
+
+ fetchState.context.addCallbacks(object : BaseProducerContextCallbacks() {
+ override fun onCancellationRequested() {
+ onFetchCancellationRequested(call)
+ }
+ })
+
+ call.enqueue(object : Callback {
+ override fun onResponse(call: Call, response: Response) =
+ onFetchResponse(fetchState, call, response, callback)
+
+ override fun onFailure(call: Call, e: IOException) =
+ handleException(call, e, callback)
+ })
+ }
+
+ private fun onFetchCancellationRequested(call: Call) {
+ if (Looper.myLooper() != Looper.getMainLooper()) {
+ call.cancel()
+ } else {
+ mCancellationExecutor.execute { call.cancel() }
+ }
+ }
+
+ private fun onFetchResponse(
+ fetchState: OkHttpNetworkFetchState,
+ call: Call,
+ response: Response,
+ callback: NetworkFetcher.Callback
+ ) {
+ fetchState.responseTime = SystemClock.elapsedRealtime()
+ try {
+ response.body.use { body ->
+ if (!response.isSuccessful) {
+ handleException(call, IOException("Unexpected HTTP code $response"), callback)
+ return
+ }
+ val responseRange =
+ BytesRange.fromContentRangeHeader(response.header("Content-Range"))
+ if (responseRange != null && !(responseRange.from == 0 && responseRange.to == BytesRange.TO_END_OF_CONTENT)) {
+ // Only treat as a partial image if the range is not all of the content
+ fetchState.responseBytesRange = responseRange
+ fetchState.onNewResultStatusFlags = Consumer.IS_PARTIAL_RESULT
+ }
+
+ var contentLength = body!!.contentLength()
+ if (contentLength < 0) {
+ contentLength = 0
+ }
+ callback.onResponse(body.byteStream(), contentLength.toInt())
+ }
+ } catch (e: Exception) {
+ handleException(call, e, callback)
+ }
+ }
+
+ /**
+ * Handles exceptions.
+ *
+ * OkHttp notifies callers of cancellations via an IOException. If IOException is caught
+ * after request cancellation, then the exception is interpreted as successful cancellation and
+ * onCancellation is called. Otherwise onFailure is called.
+ */
+ private fun handleException(call: Call, e: Exception, callback: NetworkFetcher.Callback) {
+ if (call.isCanceled()) {
+ callback.onCancellation()
+ } else {
+ callback.onFailure(e)
+ }
+ }
+}
+
+class OkHttpNetworkFetchState(
+ consumer: Consumer?, producerContext: ProducerContext?
+) : FetchState(consumer, producerContext) {
+ var submitTime: Long = 0
+ var responseTime: Long = 0
+ var fetchCompleteTime: Long = 0
+
+ fun toExtraMap(byteSize: Int) = buildMap {
+ put(QUEUE_TIME, (responseTime - submitTime).toString())
+ put(FETCH_TIME, (fetchCompleteTime - responseTime).toString())
+ put(TOTAL_TIME, (fetchCompleteTime - submitTime).toString())
+ put(IMAGE_SIZE, byteSize.toString())
+ }
+
+ companion object {
+ private const val QUEUE_TIME = "queue_time"
+ private const val FETCH_TIME = "fetch_time"
+ private const val TOTAL_TIME = "total_time"
+ private const val IMAGE_SIZE = "image_size"
+ }
+}
+
diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailAdapter.kt b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailAdapter.kt
new file mode 100644
index 0000000000..ccc1761540
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailAdapter.kt
@@ -0,0 +1,76 @@
+package fr.free.nrw.commons.media
+
+import android.view.ViewGroup
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentManager
+import androidx.fragment.app.FragmentStatePagerAdapter
+import fr.free.nrw.commons.media.MediaDetailFragment.Companion.forMedia
+import timber.log.Timber
+
+// FragmentStatePagerAdapter allows user to swipe across collection of images (no. of images undetermined)
+class MediaDetailAdapter(
+ val mediaDetailPagerFragment: MediaDetailPagerFragment,
+ fm: FragmentManager
+) : FragmentStatePagerAdapter(fm) {
+ /**
+ * Keeps track of the current displayed fragment.
+ */
+ private var currentFragment: Fragment? = null
+
+ override fun getItem(i: Int): Fragment {
+ if (i == 0) {
+ // See bug https://code.google.com/p/android/issues/detail?id=27526
+ if (mediaDetailPagerFragment.activity == null) {
+ Timber.d("Skipping getItem. Returning as activity is destroyed!")
+ return Fragment()
+ }
+ mediaDetailPagerFragment.binding!!.mediaDetailsPager.postDelayed(
+ { mediaDetailPagerFragment.requireActivity().invalidateOptionsMenu() }, 5
+ )
+ }
+ return if (mediaDetailPagerFragment.isFromFeaturedRootFragment) {
+ forMedia(
+ mediaDetailPagerFragment.position + i,
+ mediaDetailPagerFragment.editable, mediaDetailPagerFragment.isFeaturedImage,
+ mediaDetailPagerFragment.isWikipediaButtonDisplayed
+ )
+ } else {
+ forMedia(
+ i, mediaDetailPagerFragment.editable,
+ mediaDetailPagerFragment.isFeaturedImage,
+ mediaDetailPagerFragment.isWikipediaButtonDisplayed
+ )
+ }
+ }
+
+ override fun getCount(): Int {
+ if (mediaDetailPagerFragment.activity == null) {
+ Timber.d("Skipping getCount. Returning as activity is destroyed!")
+ return 0
+ }
+ return mediaDetailPagerFragment.mediaDetailProvider!!.getTotalMediaCount()
+ }
+
+ /**
+ * If current fragment is of type MediaDetailFragment, return it, otherwise return null.
+ *
+ * @return MediaDetailFragment
+ */
+ val currentMediaDetailFragment: MediaDetailFragment?
+ get() = currentFragment as? MediaDetailFragment
+
+ /**
+ * Called to inform the adapter of which item is currently considered to be the "primary", that
+ * is the one show to the user as the current page.
+ */
+ override fun setPrimaryItem(
+ container: ViewGroup, position: Int,
+ obj: Any
+ ) {
+ // Update the current fragment if changed
+ if (currentFragment !== obj) {
+ currentFragment = (obj as Fragment)
+ }
+ super.setPrimaryItem(container, position, obj)
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.kt b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.kt
index 8a4d530c47..d34c162dcf 100644
--- a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.kt
+++ b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.kt
@@ -74,11 +74,9 @@ import fr.free.nrw.commons.BuildConfig
import fr.free.nrw.commons.CameraPosition
import fr.free.nrw.commons.CommonsApplication
import fr.free.nrw.commons.CommonsApplication.Companion.instance
-import fr.free.nrw.commons.locationpicker.LocationPicker
import fr.free.nrw.commons.Media
import fr.free.nrw.commons.MediaDataExtractor
import fr.free.nrw.commons.R
-import fr.free.nrw.commons.Utils
import fr.free.nrw.commons.actions.ThanksClient
import fr.free.nrw.commons.auth.SessionManager
import fr.free.nrw.commons.auth.csrf.InvalidLoginTokenException
@@ -102,7 +100,7 @@ import fr.free.nrw.commons.explore.depictions.WikidataItemDetailsActivity
import fr.free.nrw.commons.kvstore.JsonKvStore
import fr.free.nrw.commons.language.AppLanguageLookUpTable
import fr.free.nrw.commons.location.LocationServiceManager
-import fr.free.nrw.commons.media.MediaDetailPagerFragment.MediaDetailProvider
+import fr.free.nrw.commons.locationpicker.LocationPicker
import fr.free.nrw.commons.profile.ProfileActivity
import fr.free.nrw.commons.review.ReviewHelper
import fr.free.nrw.commons.settings.Prefs
@@ -116,8 +114,13 @@ import fr.free.nrw.commons.utils.LangCodeUtils.getLocalizedResources
import fr.free.nrw.commons.utils.PermissionUtils.PERMISSIONS_STORAGE
import fr.free.nrw.commons.utils.PermissionUtils.checkPermissionsAndPerformAction
import fr.free.nrw.commons.utils.PermissionUtils.hasPermission
+import fr.free.nrw.commons.utils.ViewUtil
import fr.free.nrw.commons.utils.ViewUtil.showShortToast
import fr.free.nrw.commons.utils.ViewUtilWrapper
+import fr.free.nrw.commons.utils.copyToClipboard
+import fr.free.nrw.commons.utils.handleGeoCoordinates
+import fr.free.nrw.commons.utils.handleWebUrl
+import fr.free.nrw.commons.utils.setUnderlinedText
import fr.free.nrw.commons.wikidata.mwapi.MwQueryPage.Revision
import io.reactivex.Observable
import io.reactivex.Single
@@ -125,6 +128,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import org.apache.commons.lang3.StringUtils
import timber.log.Timber
+import java.lang.String.format
import java.util.Date
import java.util.Locale
import java.util.Objects
@@ -314,14 +318,7 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C
_binding = FragmentMediaDetailBinding.inflate(inflater, container, false)
val view: View = binding.root
-
- Utils.setUnderlinedText(binding.seeMore, R.string.nominated_see_more, requireContext())
-
- if (isCategoryImage) {
- binding.authorLinearLayout.visibility = View.VISIBLE
- } else {
- binding.authorLinearLayout.visibility = View.GONE
- }
+ binding.seeMore.setUnderlinedText(R.string.nominated_see_more)
if (!sessionManager.isUserLoggedIn) {
binding.categoryEditButton.visibility = View.GONE
@@ -811,10 +808,27 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C
categoryNames.clear()
categoryNames.addAll(media.categories!!)
- if (media.author == null || media.author == "") {
- binding.authorLinearLayout.visibility = View.GONE
- } else {
- binding.mediaDetailAuthor.text = media.author
+ // Show author or uploader information for licensing compliance
+ val authorName = media.getAttributedAuthor()
+ val uploaderName = media.user
+
+ when {
+ !authorName.isNullOrEmpty() -> {
+ // Show author if available
+ binding.mediaDetailAuthorLabel.text = getString(R.string.media_detail_author)
+ binding.mediaDetailAuthor.text = authorName
+ binding.authorLinearLayout.visibility = View.VISIBLE
+ }
+ !uploaderName.isNullOrEmpty() -> {
+ // Show uploader as fallback
+ binding.mediaDetailAuthorLabel.text = getString(R.string.media_detail_uploader)
+ binding.mediaDetailAuthor.text = uploaderName
+ binding.authorLinearLayout.visibility = View.VISIBLE
+ }
+ else -> {
+ // Hide if neither author nor uploader is available
+ binding.authorLinearLayout.visibility = View.GONE
+ }
}
}
@@ -907,7 +921,7 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C
private fun onMediaDetailLicenceClicked() {
val url: String? = media!!.licenseUrl
if (!StringUtils.isBlank(url) && activity != null) {
- Utils.handleWebUrl(activity, Uri.parse(url))
+ handleWebUrl(requireContext(), Uri.parse(url))
} else {
viewUtil.showShortToast(requireActivity(), getString(R.string.null_url))
}
@@ -915,17 +929,17 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C
private fun onMediaDetailCoordinatesClicked() {
if (media!!.coordinates != null && activity != null) {
- Utils.handleGeoCoordinates(activity, media!!.coordinates)
+ handleGeoCoordinates(requireContext(), media!!.coordinates!!)
}
}
private fun onCopyWikicodeClicked() {
val data: String =
"[[" + media!!.filename + "|thumb|" + media!!.fallbackDescription + "]]"
- Utils.copy("wikiCode", data, context)
+ requireContext().copyToClipboard("wikiCode", data)
Timber.d("Generated wikidata copy code: %s", data)
- Toast.makeText(context, getString(R.string.wikicode_copied), Toast.LENGTH_SHORT)
+ Toast.makeText(requireContext(), getString(R.string.wikicode_copied), Toast.LENGTH_SHORT)
.show()
}
@@ -1646,7 +1660,7 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C
getString(R.string.cancel),
{
val reason: String = input.text.toString()
- onDeleteClickeddialogtext(reason)
+ onDeleteClickedDialogText(reason)
},
{},
input
@@ -1700,26 +1714,48 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C
resultSingle
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
- .subscribe { _ ->
- if (applicationKvStore.getBoolean(
- String.format(
- NOMINATING_FOR_DELETION_MEDIA, media!!.imageUrl
- ), false
- )
- ) {
- applicationKvStore.remove(
- String.format(
- NOMINATING_FOR_DELETION_MEDIA,
- media!!.imageUrl
- )
- )
- callback!!.nominatingForDeletion(index)
- }
- }
+ .subscribe(this::handleDeletionResult, this::handleDeletionError);
+ }
+
+ /**
+ * Disables Progress Bar and Update delete button text.
+ */
+ private fun disableProgressBar() {
+ activity?.run {
+ runOnUiThread(Runnable {
+ binding.progressBarDeletion.visibility = View.GONE
+ })
+ } ?: return // Prevent NullPointerException when fragment is not attached to activity
+ }
+
+ private fun handleDeletionResult(success: Boolean) {
+ if (success) {
+ binding.nominateDeletion.text = getString(R.string.nominated_for_deletion_btn)
+ ViewUtil.showLongSnackbar(requireView(), getString(R.string.nominated_for_deletion))
+ disableProgressBar()
+ checkAndClearDeletionFlag()
+ } else {
+ disableProgressBar()
+ }
+ }
+
+ private fun handleDeletionError(throwable: Throwable) {
+ throwable.printStackTrace()
+ disableProgressBar()
+ checkAndClearDeletionFlag()
+ }
+
+ private fun checkAndClearDeletionFlag() {
+ if (applicationKvStore
+ .getBoolean(format(NOMINATING_FOR_DELETION_MEDIA, media!!.imageUrl), false)
+ ) {
+ applicationKvStore.remove(format(NOMINATING_FOR_DELETION_MEDIA, media!!.imageUrl))
+ callback!!.nominatingForDeletion(index)
+ }
}
@SuppressLint("CheckResult")
- private fun onDeleteClickeddialogtext(reason: String) {
+ private fun onDeleteClickedDialogText(reason: String) {
applicationKvStore.putBoolean(
String.format(
NOMINATING_FOR_DELETION_MEDIA,
@@ -1736,27 +1772,12 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C
resultSingletext
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
- .subscribe { _ ->
- if (applicationKvStore.getBoolean(
- String.format(
- NOMINATING_FOR_DELETION_MEDIA, media!!.imageUrl
- ), false
- )
- ) {
- applicationKvStore.remove(
- String.format(
- NOMINATING_FOR_DELETION_MEDIA,
- media!!.imageUrl
- )
- )
- callback!!.nominatingForDeletion(index)
- }
- }
+ .subscribe(this::handleDeletionResult, this::handleDeletionError);
}
private fun onSeeMoreClicked() {
if (binding.nominatedDeletionBanner.visibility == View.VISIBLE && activity != null) {
- Utils.handleWebUrl(activity, Uri.parse(media!!.pageTitle.mobileUri))
+ handleWebUrl(requireContext(), Uri.parse(media!!.pageTitle.mobileUri))
}
}
diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java
deleted file mode 100644
index cba582a35a..0000000000
--- a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java
+++ /dev/null
@@ -1,678 +0,0 @@
-package fr.free.nrw.commons.media;
-
-import static fr.free.nrw.commons.Utils.handleWebUrl;
-
-import android.os.Handler;
-import android.os.Looper;
-import android.widget.ProgressBar;
-import android.content.ActivityNotFoundException;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.net.Uri;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.Toast;
-import androidx.appcompat.app.ActionBar;
-import androidx.appcompat.app.AlertDialog;
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.annotation.NonNull;
-import androidx.core.content.ContextCompat;
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentManager;
-import androidx.fragment.app.FragmentStatePagerAdapter;
-import androidx.viewpager.widget.ViewPager;
-import com.google.android.material.snackbar.Snackbar;
-import fr.free.nrw.commons.CommonsApplication;
-import fr.free.nrw.commons.Media;
-import fr.free.nrw.commons.R;
-import fr.free.nrw.commons.Utils;
-import fr.free.nrw.commons.auth.SessionManager;
-import fr.free.nrw.commons.bookmarks.models.Bookmark;
-import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesContentProvider;
-import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao;
-import fr.free.nrw.commons.contributions.Contribution;
-import fr.free.nrw.commons.contributions.MainActivity;
-import fr.free.nrw.commons.databinding.FragmentMediaDetailPagerBinding;
-import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
-import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient;
-import fr.free.nrw.commons.profile.ProfileActivity;
-import fr.free.nrw.commons.utils.DownloadUtils;
-import fr.free.nrw.commons.utils.ImageUtils;
-import fr.free.nrw.commons.utils.NetworkUtils;
-import fr.free.nrw.commons.utils.ViewUtil;
-import io.reactivex.Observable;
-import io.reactivex.android.schedulers.AndroidSchedulers;
-import io.reactivex.disposables.CompositeDisposable;
-import io.reactivex.schedulers.Schedulers;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.Objects;
-import java.util.concurrent.Callable;
-import javax.inject.Inject;
-import timber.log.Timber;
-
-public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment implements ViewPager.OnPageChangeListener, MediaDetailFragment.Callback {
-
- @Inject BookmarkPicturesDao bookmarkDao;
-
- @Inject
- protected OkHttpJsonApiClient okHttpJsonApiClient;
-
- @Inject
- protected SessionManager sessionManager;
-
- private static CompositeDisposable compositeDisposable = new CompositeDisposable();
-
- private FragmentMediaDetailPagerBinding binding;
-
- private boolean editable;
- private boolean isFeaturedImage;
- private boolean isWikipediaButtonDisplayed;
- MediaDetailAdapter adapter;
- private Bookmark bookmark;
- private MediaDetailProvider provider;
- private boolean isFromFeaturedRootFragment;
- private int position;
-
- /**
- * ProgressBar used to indicate the loading status of media items.
- */
- private ProgressBar imageProgressBar;
-
- private ArrayList removedItems=new ArrayList();
-
- public void clearRemoved(){
- removedItems.clear();
- }
- public ArrayList getRemovedItems() {
- return removedItems;
- }
-
-
- /**
- * Use this factory method to create a new instance of this fragment using the provided
- * parameters.
- *
- * This method will create a new instance of MediaDetailPagerFragment and the arguments will be
- * saved to a bundle which will be later available in the {@link #onCreate(Bundle)}
- * @param editable
- * @param isFeaturedImage
- * @return
- */
- public static MediaDetailPagerFragment newInstance(boolean editable, boolean isFeaturedImage) {
- MediaDetailPagerFragment mediaDetailPagerFragment = new MediaDetailPagerFragment();
- Bundle args = new Bundle();
- args.putBoolean("is_editable", editable);
- args.putBoolean("is_featured_image", isFeaturedImage);
- mediaDetailPagerFragment.setArguments(args);
- return mediaDetailPagerFragment;
- }
-
- public MediaDetailPagerFragment() {
- // Required empty public constructor
- };
-
-
- @Override
- public View onCreateView(LayoutInflater inflater,
- ViewGroup container,
- Bundle savedInstanceState) {
- binding = FragmentMediaDetailPagerBinding.inflate(inflater, container, false);
- binding.mediaDetailsPager.addOnPageChangeListener(this);
- // Initialize the ProgressBar by finding it in the layout
- imageProgressBar = binding.getRoot().findViewById(R.id.itemProgressBar);
- adapter = new MediaDetailAdapter(getChildFragmentManager());
-
- // ActionBar is now supported in both activities - if this crashes something is quite wrong
- final ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar();
- if (actionBar != null) {
- actionBar.setDisplayHomeAsUpEnabled(true);
- }
- else {
- throw new AssertionError("Action bar should not be null!");
- }
-
- // If fragment is associated with ProfileActivity, then hide the tabLayout
- if (getActivity() instanceof ProfileActivity) {
- ((ProfileActivity)getActivity()).setTabLayoutVisibility(false);
- }
-
- // Else if fragment is associated with MainActivity then hide that tab layout
- else if (getActivity() instanceof MainActivity) {
- ((MainActivity)getActivity()).hideTabs();
- }
-
- binding.mediaDetailsPager.setAdapter(adapter);
-
- if (savedInstanceState != null) {
- final int pageNumber = savedInstanceState.getInt("current-page");
- binding.mediaDetailsPager.setCurrentItem(pageNumber, false);
- getActivity().invalidateOptionsMenu();
- }
- adapter.notifyDataSetChanged();
-
- return binding.getRoot();
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- outState.putInt("current-page", binding.mediaDetailsPager.getCurrentItem());
- outState.putBoolean("editable", editable);
- outState.putBoolean("isFeaturedImage", isFeaturedImage);
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- if (savedInstanceState != null) {
- editable = savedInstanceState.getBoolean("editable", false);
- isFeaturedImage = savedInstanceState.getBoolean("isFeaturedImage", false);
-
- }
- setHasOptionsMenu(true);
- initProvider();
- }
-
- /**
- * initialise the provider, based on from where the fragment was started, as in from an activity
- * or a fragment
- */
- private void initProvider() {
- if (getParentFragment() instanceof MediaDetailProvider) {
- provider = (MediaDetailProvider) getParentFragment();
- } else if (getActivity() instanceof MediaDetailProvider) {
- provider = (MediaDetailProvider) getActivity();
- } else {
- throw new ClassCastException("Parent must implement MediaDetailProvider");
- }
- }
-
- public MediaDetailProvider getMediaDetailProvider() {
- return provider;
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- if (getActivity() == null) {
- Timber.d("Returning as activity is destroyed!");
- return true;
- }
-
- Media m = provider.getMediaAtPosition(binding.mediaDetailsPager.getCurrentItem());
- MediaDetailFragment mediaDetailFragment = this.adapter.getCurrentMediaDetailFragment();
- switch (item.getItemId()) {
- case R.id.menu_bookmark_current_image:
- boolean bookmarkExists = bookmarkDao.updateBookmark(bookmark);
- Snackbar snackbar = bookmarkExists ? Snackbar.make(getView(), R.string.add_bookmark, Snackbar.LENGTH_LONG) : Snackbar.make(getView(), R.string.remove_bookmark, Snackbar.LENGTH_LONG);
- snackbar.show();
- updateBookmarkState(item);
- return true;
- case R.id.menu_copy_link:
- String uri = m.getPageTitle().getCanonicalUri();
- Utils.copy("shareLink", uri, requireContext());
- Timber.d("Copied share link to clipboard: %s", uri);
- Toast.makeText(requireContext(), getString(R.string.menu_link_copied),
- Toast.LENGTH_SHORT).show();
- return true;
- 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.getPageTitle().getCanonicalUri());
- startActivity(Intent.createChooser(shareIntent, "Share image via..."));
-
- //Add media detail to backstack when the share button is clicked
- //So that when the share is cancelled or completed the media detail page is on top
- // of back stack fixing:https://github.com/commons-app/apps-android-commons/issues/2296
- FragmentManager supportFragmentManager = getActivity().getSupportFragmentManager();
- if (supportFragmentManager.getBackStackEntryCount() < 2) {
- supportFragmentManager
- .beginTransaction()
- .addToBackStack(MediaDetailPagerFragment.class.getName())
- .commit();
- supportFragmentManager.executePendingTransactions();
- }
- return true;
- case R.id.menu_browser_current_image:
- // View in browser
- handleWebUrl(requireContext(), Uri.parse(m.getPageTitle().getMobileUri()));
- return true;
- case R.id.menu_download_current_image:
- // Download
- if (!NetworkUtils.isInternetConnectionEstablished(getActivity())) {
- ViewUtil.showShortSnackbar(getView(), R.string.no_internet);
- return false;
- }
- DownloadUtils.downloadMedia(getActivity(), m);
- return true;
- case R.id.menu_set_as_wallpaper:
- // Set wallpaper
- setWallpaper(m);
- return true;
- case R.id.menu_set_as_avatar:
- // Set avatar
- setAvatar(m);
- return true;
- case R.id.menu_view_user_page:
- if (m != null && m.getUser() != null) {
- ProfileActivity.startYourself(getActivity(), m.getUser(),
- !Objects.equals(sessionManager.getUserName(), m.getUser()));
- }
- return true;
- case R.id.menu_view_report:
- showReportDialog(m);
- case R.id.menu_view_set_white_background:
- if (mediaDetailFragment != null) {
- mediaDetailFragment.onImageBackgroundChanged(ContextCompat.getColor(getContext(), R.color.white));
- }
- return true;
- case R.id.menu_view_set_black_background:
- if (mediaDetailFragment != null) {
- mediaDetailFragment.onImageBackgroundChanged(ContextCompat.getColor(getContext(), R.color.black));
- }
- return true;
- default:
- return super.onOptionsItemSelected(item);
- }
- }
-
- private void showReportDialog(final Media media) {
- if (media == null) {
- return;
- }
- final AlertDialog.Builder builder = new AlertDialog.Builder(requireActivity());
- final String[] values = requireContext().getResources()
- .getStringArray(R.array.report_violation_options);
- builder.setTitle(R.string.report_violation);
- builder.setItems(R.array.report_violation_options, (dialog, which) -> {
- sendReportEmail(media, values[which]);
- });
- builder.setNegativeButton(R.string.cancel, (dialog, which) -> {});
- builder.setCancelable(false);
- builder.show();
- }
-
- private void sendReportEmail(final Media media, final String type) {
- final String technicalInfo = getTechInfo(media, type);
-
- final Intent feedbackIntent = new Intent(Intent.ACTION_SENDTO);
- feedbackIntent.setType("message/rfc822");
- feedbackIntent.setData(Uri.parse("mailto:"));
- feedbackIntent.putExtra(Intent.EXTRA_EMAIL,
- new String[]{CommonsApplication.REPORT_EMAIL});
- feedbackIntent.putExtra(Intent.EXTRA_SUBJECT,
- CommonsApplication.REPORT_EMAIL_SUBJECT);
- feedbackIntent.putExtra(Intent.EXTRA_TEXT, technicalInfo);
- try {
- startActivity(feedbackIntent);
- } catch (final ActivityNotFoundException e) {
- Toast.makeText(getActivity(), R.string.no_email_client, Toast.LENGTH_SHORT).show();
- }
- }
-
- private String getTechInfo(final Media media, final String type) {
- final StringBuilder builder = new StringBuilder();
-
- builder.append("Report type: ")
- .append(type)
- .append("\n\n");
-
- builder.append("Image that you want to report: ")
- .append(media.getImageUrl())
- .append("\n\n");
-
- builder.append("User that you want to report: ")
- .append(media.getUser())
- .append("\n\n");
-
- if (sessionManager.getUserName() != null) {
- builder.append("Your username: ")
- .append(sessionManager.getUserName())
- .append("\n\n");
- }
-
- builder.append("Violation reason: ")
- .append("\n");
-
- builder.append("----------------------------------------------")
- .append("\n")
- .append("(please write reason here)")
- .append("\n")
- .append("----------------------------------------------")
- .append("\n\n")
- .append("Thank you for your report! Our team will investigate as soon as possible.")
- .append("\n")
- .append("Please note that images also have a `Nominate for deletion` button.");
-
- return builder.toString();
- }
-
- /**
- * Set the media as the device's wallpaper if the imageUrl is not null
- * Fails silently if setting the wallpaper fails
- * @param media
- */
- private void setWallpaper(Media media) {
- if (media.getImageUrl() == null || media.getImageUrl().isEmpty()) {
- Timber.d("Media URL not present");
- return;
- }
- ImageUtils.setWallpaperFromImageUrl(getActivity(), Uri.parse(media.getImageUrl()));
- }
-
- /**
- * Set the media as user's leaderboard avatar
- * @param media
- */
- private void setAvatar(Media media) {
- if (media.getImageUrl() == null || media.getImageUrl().isEmpty()) {
- Timber.d("Media URL not present");
- return;
- }
- ImageUtils.setAvatarFromImageUrl(getActivity(), media.getImageUrl(),
- Objects.requireNonNull(sessionManager.getCurrentAccount()).name,
- okHttpJsonApiClient, compositeDisposable);
- }
-
- @Override
- public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
- if (!editable) { // Disable menu options for editable views
- menu.clear(); // see http://stackoverflow.com/a/8495697/17865
- inflater.inflate(R.menu.fragment_image_detail, menu);
- if (binding.mediaDetailsPager != null) {
- MediaDetailProvider provider = getMediaDetailProvider();
- if(provider == null) {
- return;
- }
- final int position;
- if (isFromFeaturedRootFragment) {
- position = this.position;
- } else {
- position = binding.mediaDetailsPager.getCurrentItem();
- }
-
- Media m = provider.getMediaAtPosition(position);
- if (m != null) {
- // Enable default set of actions, then re-enable different set of actions only if it is a failed contrib
- menu.findItem(R.id.menu_browser_current_image).setEnabled(true).setVisible(true);
- menu.findItem(R.id.menu_copy_link).setEnabled(true).setVisible(true);
- menu.findItem(R.id.menu_share_current_image).setEnabled(true).setVisible(true);
- menu.findItem(R.id.menu_download_current_image).setEnabled(true).setVisible(true);
- menu.findItem(R.id.menu_bookmark_current_image).setEnabled(true).setVisible(true);
- menu.findItem(R.id.menu_set_as_wallpaper).setEnabled(true).setVisible(true);
- if (m.getUser() != null) {
- menu.findItem(R.id.menu_view_user_page).setEnabled(true).setVisible(true);
- }
-
- try {
- URL mediaUrl = new URL(m.getImageUrl());
- this.handleBackgroundColorMenuItems(
- () -> BitmapFactory.decodeStream(mediaUrl.openConnection().getInputStream()),
- menu
- );
- } catch (Exception e) {
- Timber.e("Cant detect media transparency");
- }
-
- // Initialize bookmark object
- bookmark = new Bookmark(
- m.getFilename(),
- m.getAuthorOrUser(),
- BookmarkPicturesContentProvider.uriForName(m.getFilename())
- );
- updateBookmarkState(menu.findItem(R.id.menu_bookmark_current_image));
- final Integer contributionState = provider.getContributionStateAt(position);
- if (contributionState != null) {
- switch (contributionState) {
- case Contribution.STATE_FAILED:
- case Contribution.STATE_IN_PROGRESS:
- case Contribution.STATE_QUEUED:
- menu.findItem(R.id.menu_browser_current_image).setEnabled(false)
- .setVisible(false);
- menu.findItem(R.id.menu_copy_link).setEnabled(false)
- .setVisible(false);
- menu.findItem(R.id.menu_share_current_image).setEnabled(false)
- .setVisible(false);
- menu.findItem(R.id.menu_download_current_image).setEnabled(false)
- .setVisible(false);
- menu.findItem(R.id.menu_bookmark_current_image).setEnabled(false)
- .setVisible(false);
- menu.findItem(R.id.menu_set_as_wallpaper).setEnabled(false)
- .setVisible(false);
- break;
- case Contribution.STATE_COMPLETED:
- // Default set of menu items works fine. Treat same as regular media object
- break;
- }
- }
- } else {
- menu.findItem(R.id.menu_browser_current_image).setEnabled(false)
- .setVisible(false);
- menu.findItem(R.id.menu_copy_link).setEnabled(false)
- .setVisible(false);
- menu.findItem(R.id.menu_share_current_image).setEnabled(false)
- .setVisible(false);
- menu.findItem(R.id.menu_download_current_image).setEnabled(false)
- .setVisible(false);
- menu.findItem(R.id.menu_bookmark_current_image).setEnabled(false)
- .setVisible(false);
- menu.findItem(R.id.menu_set_as_wallpaper).setEnabled(false)
- .setVisible(false);
- }
-
- if (!sessionManager.isUserLoggedIn()) {
- menu.findItem(R.id.menu_set_as_avatar).setVisible(false);
- }
-
- }
- }
- }
-
- /**
- * Decide wether or not we should display the background color menu items
- * We display them if the image is transparent
- * @param getBitmap
- * @param menu
- */
- private void handleBackgroundColorMenuItems(Callable getBitmap, Menu menu) {
- Observable.fromCallable(
- getBitmap
- ).subscribeOn(Schedulers.newThread())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(image -> {
- if (image.hasAlpha()) {
- menu.findItem(R.id.menu_view_set_white_background).setVisible(true).setEnabled(true);
- menu.findItem(R.id.menu_view_set_black_background).setVisible(true).setEnabled(true);
- }
- });
- }
-
- private void updateBookmarkState(MenuItem item) {
- boolean isBookmarked = bookmarkDao.findBookmark(bookmark);
- if(isBookmarked) {
- if(removedItems.contains(binding.mediaDetailsPager.getCurrentItem())) {
- removedItems.remove(new Integer(binding.mediaDetailsPager.getCurrentItem()));
- }
- }
- else {
- if(!removedItems.contains(binding.mediaDetailsPager.getCurrentItem())) {
- removedItems.add(binding.mediaDetailsPager.getCurrentItem());
- }
- }
- int icon = isBookmarked ? R.drawable.menu_ic_round_star_filled_24px : R.drawable.menu_ic_round_star_border_24px;
- item.setIcon(icon);
- }
-
- public void showImage(int i, boolean isWikipediaButtonDisplayed) {
- this.isWikipediaButtonDisplayed = isWikipediaButtonDisplayed;
- setViewPagerCurrentItem(i);
- }
-
- public void showImage(int i) {
- setViewPagerCurrentItem(i);
- }
-
- /**
- * This function waits for the item to load then sets the item to current item
- * @param position current item that to be shown
- */
- private void setViewPagerCurrentItem(int position) {
-
- final Handler handler = new Handler(Looper.getMainLooper());
- final Runnable runnable = new Runnable() {
- @Override
- public void run() {
- // Show the ProgressBar while waiting for the item to load
- imageProgressBar.setVisibility(View.VISIBLE);
- // Check if the adapter has enough items loaded
- if(adapter.getCount() > position){
- // Set the current item in the ViewPager
- binding.mediaDetailsPager.setCurrentItem(position, false);
- // Hide the ProgressBar once the item is loaded
- imageProgressBar.setVisibility(View.GONE);
- } else {
- // If the item is not ready yet, post the Runnable again
- handler.post(this);
- }
- }
- };
- // Start the Runnable
- handler.post(runnable);
- }
-
- /**
- * The method notify the viewpager that number of items have changed.
- */
- public void notifyDataSetChanged(){
- if (null != adapter) {
- adapter.notifyDataSetChanged();
- }
- }
-
- @Override
- public void onPageScrolled(int i, float v, int i2) {
- if(getActivity() == null) {
- Timber.d("Returning as activity is destroyed!");
- return;
- }
-
- getActivity().invalidateOptionsMenu();
- }
-
- @Override
- public void onPageSelected(int i) {
- }
-
- @Override
- public void onPageScrollStateChanged(int i) {
- }
-
- public void onDataSetChanged() {
- if (null != adapter) {
- adapter.notifyDataSetChanged();
- }
- }
-
- /**
- * Called after the media is nominated for deletion
- *
- * @param index item position that has been nominated
- */
- @Override
- public void nominatingForDeletion(int index) {
- provider.refreshNominatedMedia(index);
- }
-
- public interface MediaDetailProvider {
- Media getMediaAtPosition(int i);
-
- int getTotalMediaCount();
-
- Integer getContributionStateAt(int position);
-
- // Reload media detail fragment once media is nominated
- void refreshNominatedMedia(int index);
- }
-
- //FragmentStatePagerAdapter allows user to swipe across collection of images (no. of images undetermined)
- private class MediaDetailAdapter extends FragmentStatePagerAdapter {
-
- /**
- * Keeps track of the current displayed fragment.
- */
- private Fragment mCurrentFragment;
-
- public MediaDetailAdapter(FragmentManager fm) {
- super(fm);
- }
-
- @Override
- public Fragment getItem(int i) {
- if (i == 0) {
- // See bug https://code.google.com/p/android/issues/detail?id=27526
- if(getActivity() == null) {
- Timber.d("Skipping getItem. Returning as activity is destroyed!");
- return null;
- }
- binding.mediaDetailsPager.postDelayed(() -> getActivity().invalidateOptionsMenu(), 5);
- }
- if (isFromFeaturedRootFragment) {
- return MediaDetailFragment.forMedia(position+i, editable, isFeaturedImage, isWikipediaButtonDisplayed);
- } else {
- return MediaDetailFragment.forMedia(i, editable, isFeaturedImage, isWikipediaButtonDisplayed);
- }
- }
-
- @Override
- public int getCount() {
- if (getActivity() == null) {
- Timber.d("Skipping getCount. Returning as activity is destroyed!");
- return 0;
- }
- return provider.getTotalMediaCount();
- }
-
- /**
- * Get the currently displayed fragment.
- * @return
- */
- public Fragment getCurrentFragment() {
- return mCurrentFragment;
- }
-
- /**
- * If current fragment is of type MediaDetailFragment, return it, otherwise return null.
- * @return MediaDetailFragment
- */
- public MediaDetailFragment getCurrentMediaDetailFragment() {
- if (mCurrentFragment instanceof MediaDetailFragment) {
- return (MediaDetailFragment) mCurrentFragment;
- }
-
- return null;
- }
-
- /**
- * Called to inform the adapter of which item is currently considered to be the "primary",
- * that is the one show to the user as the current page.
- * @param container
- * @param position
- * @param object
- */
- @Override
- public void setPrimaryItem(@NonNull final ViewGroup container, final int position,
- @NonNull final Object object) {
- // Update the current fragment if changed
- if(getCurrentFragment() != object) {
- mCurrentFragment = ((Fragment)object);
- }
- super.setPrimaryItem(container, position, object);
- }
- }
-}
diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.kt b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.kt
new file mode 100644
index 0000000000..b66c888aa0
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.kt
@@ -0,0 +1,622 @@
+package fr.free.nrw.commons.media
+
+import android.content.ActivityNotFoundException
+import android.content.DialogInterface
+import android.content.Intent
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.net.Uri
+import android.os.Bundle
+import android.os.Handler
+import android.os.Looper
+import android.view.LayoutInflater
+import android.view.Menu
+import android.view.MenuInflater
+import android.view.MenuItem
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ProgressBar
+import android.widget.Toast
+import androidx.appcompat.app.AlertDialog
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.content.ContextCompat
+import androidx.viewpager.widget.ViewPager.OnPageChangeListener
+import com.google.android.material.snackbar.Snackbar
+import fr.free.nrw.commons.CommonsApplication
+import fr.free.nrw.commons.Media
+import fr.free.nrw.commons.R
+import fr.free.nrw.commons.auth.SessionManager
+import fr.free.nrw.commons.bookmarks.models.Bookmark
+import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesContentProvider
+import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao
+import fr.free.nrw.commons.contributions.Contribution
+import fr.free.nrw.commons.contributions.MainActivity
+import fr.free.nrw.commons.databinding.FragmentMediaDetailPagerBinding
+import fr.free.nrw.commons.di.CommonsDaggerSupportFragment
+import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient
+import fr.free.nrw.commons.profile.ProfileActivity
+import fr.free.nrw.commons.profile.ProfileActivity.Companion.startYourself
+import fr.free.nrw.commons.utils.ClipboardUtils.copy
+import fr.free.nrw.commons.utils.DownloadUtils.downloadMedia
+import fr.free.nrw.commons.utils.ImageUtils.setAvatarFromImageUrl
+import fr.free.nrw.commons.utils.ImageUtils.setWallpaperFromImageUrl
+import fr.free.nrw.commons.utils.NetworkUtils.isInternetConnectionEstablished
+import fr.free.nrw.commons.utils.ViewUtil.showShortSnackbar
+import fr.free.nrw.commons.utils.handleWebUrl
+import io.reactivex.Observable
+import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.disposables.CompositeDisposable
+import io.reactivex.functions.Consumer
+import io.reactivex.schedulers.Schedulers
+import timber.log.Timber
+import java.net.URL
+import java.util.concurrent.Callable
+import javax.inject.Inject
+import androidx.core.net.toUri
+
+class MediaDetailPagerFragment : CommonsDaggerSupportFragment(), OnPageChangeListener,
+ MediaDetailFragment.Callback {
+ @JvmField
+ @Inject
+ var bookmarkDao: BookmarkPicturesDao? = null
+
+ @JvmField
+ @Inject
+ var okHttpJsonApiClient: OkHttpJsonApiClient? = null
+
+ @JvmField
+ @Inject
+ var sessionManager: SessionManager? = null
+
+ var binding: FragmentMediaDetailPagerBinding? = null
+ var editable: Boolean = false
+ var isFeaturedImage: Boolean = false
+ var isWikipediaButtonDisplayed: Boolean = false
+ var adapter: MediaDetailAdapter? = null
+ var bookmark: Bookmark? = null
+ var mediaDetailProvider: MediaDetailProvider? = null
+ var isFromFeaturedRootFragment: Boolean = false
+ var position: Int = 0
+
+ /**
+ * ProgressBar used to indicate the loading status of media items.
+ */
+ var imageProgressBar: ProgressBar? = null
+
+ var removedItems: ArrayList = ArrayList()
+
+ fun clearRemoved() = removedItems.clear()
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ binding = FragmentMediaDetailPagerBinding.inflate(inflater, container, false)
+ binding!!.mediaDetailsPager.addOnPageChangeListener(this)
+ // Initialize the ProgressBar by finding it in the layout
+ imageProgressBar = binding!!.root.findViewById(R.id.itemProgressBar)
+ adapter = MediaDetailAdapter(this, childFragmentManager)
+
+ // ActionBar is now supported in both activities - if this crashes something is quite wrong
+ val actionBar = (activity as AppCompatActivity).supportActionBar
+ if (actionBar != null) {
+ actionBar.setDisplayHomeAsUpEnabled(true)
+ } else {
+ throw AssertionError("Action bar should not be null!")
+ }
+
+ // If fragment is associated with ProfileActivity, then hide the tabLayout
+ if (activity is ProfileActivity) {
+ (activity as ProfileActivity).setTabLayoutVisibility(false)
+ } else if (activity is MainActivity) {
+ (activity as MainActivity).hideTabs()
+ }
+
+ binding!!.mediaDetailsPager.adapter = adapter
+
+ if (savedInstanceState != null) {
+ val pageNumber = savedInstanceState.getInt("current-page")
+ binding!!.mediaDetailsPager.setCurrentItem(pageNumber, false)
+ requireActivity().invalidateOptionsMenu()
+ }
+ adapter!!.notifyDataSetChanged()
+
+ return binding!!.root
+ }
+
+ override fun onSaveInstanceState(outState: Bundle) {
+ super.onSaveInstanceState(outState)
+ outState.putInt("current-page", binding!!.mediaDetailsPager.currentItem)
+ outState.putBoolean("editable", editable)
+ outState.putBoolean("isFeaturedImage", isFeaturedImage)
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ if (savedInstanceState != null) {
+ editable = savedInstanceState.getBoolean("editable", false)
+ isFeaturedImage = savedInstanceState.getBoolean("isFeaturedImage", false)
+ }
+ setHasOptionsMenu(true)
+ initProvider()
+ }
+
+ /**
+ * initialise the provider, based on from where the fragment was started, as in from an activity
+ * or a fragment
+ */
+ private fun initProvider() {
+ if (parentFragment is MediaDetailProvider) {
+ mediaDetailProvider = parentFragment as MediaDetailProvider
+ } else if (activity is MediaDetailProvider) {
+ mediaDetailProvider = activity as MediaDetailProvider?
+ } else {
+ throw ClassCastException("Parent must implement MediaDetailProvider")
+ }
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ if (activity == null) {
+ Timber.d("Returning as activity is destroyed!")
+ return true
+ }
+
+ val m = mediaDetailProvider!!.getMediaAtPosition(binding!!.mediaDetailsPager.currentItem)
+ val mediaDetailFragment = adapter!!.currentMediaDetailFragment
+ when (item.itemId) {
+ R.id.menu_bookmark_current_image -> {
+ val bookmarkExists = bookmarkDao!!.updateBookmark(bookmark)
+ val snackbar = if (bookmarkExists) Snackbar.make(
+ requireView(),
+ R.string.add_bookmark,
+ Snackbar.LENGTH_LONG
+ ) else Snackbar.make(
+ requireView(), R.string.remove_bookmark, Snackbar.LENGTH_LONG
+ )
+ snackbar.show()
+ updateBookmarkState(item)
+ return true
+ }
+
+ R.id.menu_copy_link -> {
+ val uri = m!!.pageTitle.canonicalUri
+ copy("shareLink", uri, requireContext())
+ Timber.d("Copied share link to clipboard: %s", uri)
+ Toast.makeText(
+ requireContext(), getString(R.string.menu_link_copied),
+ Toast.LENGTH_SHORT
+ ).show()
+ return true
+ }
+
+ R.id.menu_share_current_image -> {
+ val shareIntent = Intent(Intent.ACTION_SEND)
+ shareIntent.setType("text/plain")
+ shareIntent.putExtra(
+ Intent.EXTRA_TEXT, """${m!!.displayTitle}
+${m.pageTitle.canonicalUri}"""
+ )
+ startActivity(Intent.createChooser(shareIntent, "Share image via..."))
+
+ //Add media detail to backstack when the share button is clicked
+ //So that when the share is cancelled or completed the media detail page is on top
+ // of back stack fixing:https://github.com/commons-app/apps-android-commons/issues/2296
+ val supportFragmentManager = requireActivity().supportFragmentManager
+ if (supportFragmentManager.backStackEntryCount < 2) {
+ supportFragmentManager
+ .beginTransaction()
+ .addToBackStack(MediaDetailPagerFragment::class.java.name)
+ .commit()
+ supportFragmentManager.executePendingTransactions()
+ }
+ return true
+ }
+
+ R.id.menu_browser_current_image -> {
+ // View in browser
+ handleWebUrl(requireContext(), m!!.pageTitle.mobileUri.toUri())
+ return true
+ }
+
+ R.id.menu_download_current_image -> {
+ // Download
+ if (!isInternetConnectionEstablished(activity)) {
+ showShortSnackbar(requireView(), R.string.no_internet)
+ return false
+ }
+ downloadMedia(activity, m!!)
+ return true
+ }
+
+ R.id.menu_set_as_wallpaper -> {
+ // Set wallpaper
+ setWallpaper(m!!)
+ return true
+ }
+
+ R.id.menu_set_as_avatar -> {
+ // Set avatar
+ setAvatar(m!!)
+ return true
+ }
+
+ R.id.menu_view_user_page -> {
+ if (m?.user != null) {
+ startYourself(
+ requireActivity(), m.user!!,
+ sessionManager!!.userName != m.user
+ )
+ }
+ return true
+ }
+
+ R.id.menu_view_report -> {
+ showReportDialog(m)
+ mediaDetailFragment?.onImageBackgroundChanged(
+ ContextCompat.getColor(
+ requireContext(),
+ R.color.white
+ )
+ )
+ return true
+ }
+
+ R.id.menu_view_set_white_background -> {
+ mediaDetailFragment?.onImageBackgroundChanged(
+ ContextCompat.getColor(
+ requireContext(),
+ R.color.white
+ )
+ )
+ return true
+ }
+
+ R.id.menu_view_set_black_background -> {
+ mediaDetailFragment?.onImageBackgroundChanged(
+ ContextCompat.getColor(
+ requireContext(),
+ R.color.black
+ )
+ )
+ return true
+ }
+
+ else -> return super.onOptionsItemSelected(item)
+ }
+ }
+
+ private fun showReportDialog(media: Media?) {
+ if (media == null) {
+ return
+ }
+ val builder = AlertDialog.Builder(requireActivity())
+ val values = requireContext().resources
+ .getStringArray(R.array.report_violation_options)
+ builder.setTitle(R.string.report_violation)
+ builder.setItems(
+ R.array.report_violation_options
+ ) { dialog: DialogInterface?, which: Int ->
+ sendReportEmail(media, values[which])
+ }
+ builder.setNegativeButton(
+ R.string.cancel
+ ) { dialog: DialogInterface?, which: Int -> }
+ builder.setCancelable(false)
+ builder.show()
+ }
+
+ private fun sendReportEmail(media: Media, type: String) {
+ val technicalInfo = getTechInfo(media, type)
+
+ val feedbackIntent = Intent(Intent.ACTION_SENDTO)
+ feedbackIntent.setType("message/rfc822")
+ feedbackIntent.setData(Uri.parse("mailto:"))
+ feedbackIntent.putExtra(
+ Intent.EXTRA_EMAIL,
+ arrayOf(CommonsApplication.REPORT_EMAIL)
+ )
+ feedbackIntent.putExtra(
+ Intent.EXTRA_SUBJECT,
+ CommonsApplication.REPORT_EMAIL_SUBJECT
+ )
+ feedbackIntent.putExtra(Intent.EXTRA_TEXT, technicalInfo)
+ try {
+ startActivity(feedbackIntent)
+ } catch (e: ActivityNotFoundException) {
+ Toast.makeText(activity, R.string.no_email_client, Toast.LENGTH_SHORT).show()
+ }
+ }
+
+ private fun getTechInfo(media: Media, type: String): String {
+ val builder = StringBuilder()
+
+ builder.append("Report type: ")
+ .append(type)
+ .append("\n\n")
+
+ builder.append("Image that you want to report: ")
+ .append(media.imageUrl)
+ .append("\n\n")
+
+ builder.append("User that you want to report: ")
+ .append(media.user)
+ .append("\n\n")
+
+ if (sessionManager!!.userName != null) {
+ builder.append("Your username: ")
+ .append(sessionManager!!.userName)
+ .append("\n\n")
+ }
+
+ builder.append("Violation reason: ")
+ .append("\n")
+
+ builder.append("----------------------------------------------")
+ .append("\n")
+ .append("(please write reason here)")
+ .append("\n")
+ .append("----------------------------------------------")
+ .append("\n\n")
+ .append("Thank you for your report! Our team will investigate as soon as possible.")
+ .append("\n")
+ .append("Please note that images also have a `Nominate for deletion` button.")
+
+ return builder.toString()
+ }
+
+ /**
+ * Set the media as the device's wallpaper if the imageUrl is not null
+ * Fails silently if setting the wallpaper fails
+ * @param media
+ */
+ private fun setWallpaper(media: Media) {
+ if (media.imageUrl == null || media.imageUrl!!.isEmpty()) {
+ Timber.d("Media URL not present")
+ return
+ }
+ setWallpaperFromImageUrl(requireActivity(), media.imageUrl!!.toUri())
+ }
+
+ /**
+ * Set the media as user's leaderboard avatar
+ * @param media
+ */
+ private fun setAvatar(media: Media) {
+ if (media.imageUrl == null || media.imageUrl!!.isEmpty()) {
+ Timber.d("Media URL not present")
+ return
+ }
+ setAvatarFromImageUrl(
+ requireActivity(), media.imageUrl!!,
+ sessionManager!!.currentAccount!!.name,
+ okHttpJsonApiClient!!, Companion.compositeDisposable
+ )
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
+ if (!editable) { // Disable menu options for editable views
+ menu.clear() // see http://stackoverflow.com/a/8495697/17865
+ inflater.inflate(R.menu.fragment_image_detail, menu)
+ if (binding!!.mediaDetailsPager != null) {
+ val provider = mediaDetailProvider ?: return
+ val position = if (isFromFeaturedRootFragment) {
+ position
+ } else {
+ binding!!.mediaDetailsPager.currentItem
+ }
+
+ val m = provider.getMediaAtPosition(position)
+ if (m != null) {
+ // Enable default set of actions, then re-enable different set of actions only if it is a failed contrib
+ menu.findItem(R.id.menu_browser_current_image).setEnabled(true).setVisible(true)
+ menu.findItem(R.id.menu_copy_link).setEnabled(true).setVisible(true)
+ menu.findItem(R.id.menu_share_current_image).setEnabled(true).setVisible(true)
+ menu.findItem(R.id.menu_download_current_image).setEnabled(true)
+ .setVisible(true)
+ menu.findItem(R.id.menu_bookmark_current_image).setEnabled(true)
+ .setVisible(true)
+ menu.findItem(R.id.menu_set_as_wallpaper).setEnabled(true).setVisible(true)
+ if (m.user != null) {
+ menu.findItem(R.id.menu_view_user_page).setEnabled(true).setVisible(true)
+ }
+
+ try {
+ val mediaUrl = URL(m.imageUrl)
+ handleBackgroundColorMenuItems({
+ BitmapFactory.decodeStream(
+ mediaUrl.openConnection().getInputStream()
+ )
+ }, menu)
+ } catch (e: Exception) {
+ Timber.e("Cant detect media transparency")
+ }
+
+ // Initialize bookmark object
+ bookmark = Bookmark(
+ m.filename,
+ m.getAuthorOrUser(),
+ BookmarkPicturesContentProvider.uriForName(m.filename)
+ )
+ updateBookmarkState(menu.findItem(R.id.menu_bookmark_current_image))
+ val contributionState = provider.getContributionStateAt(position)
+ if (contributionState != null) {
+ when (contributionState) {
+ Contribution.STATE_FAILED, Contribution.STATE_IN_PROGRESS, Contribution.STATE_QUEUED -> {
+ menu.findItem(R.id.menu_browser_current_image).setEnabled(false)
+ .setVisible(false)
+ menu.findItem(R.id.menu_copy_link).setEnabled(false)
+ .setVisible(false)
+ menu.findItem(R.id.menu_share_current_image).setEnabled(false)
+ .setVisible(false)
+ menu.findItem(R.id.menu_download_current_image).setEnabled(false)
+ .setVisible(false)
+ menu.findItem(R.id.menu_bookmark_current_image).setEnabled(false)
+ .setVisible(false)
+ menu.findItem(R.id.menu_set_as_wallpaper).setEnabled(false)
+ .setVisible(false)
+ }
+
+ Contribution.STATE_COMPLETED -> {}
+ }
+ }
+ } else {
+ menu.findItem(R.id.menu_browser_current_image).setEnabled(false)
+ .setVisible(false)
+ menu.findItem(R.id.menu_copy_link).setEnabled(false)
+ .setVisible(false)
+ menu.findItem(R.id.menu_share_current_image).setEnabled(false)
+ .setVisible(false)
+ menu.findItem(R.id.menu_download_current_image).setEnabled(false)
+ .setVisible(false)
+ menu.findItem(R.id.menu_bookmark_current_image).setEnabled(false)
+ .setVisible(false)
+ menu.findItem(R.id.menu_set_as_wallpaper).setEnabled(false)
+ .setVisible(false)
+ }
+
+ if (!sessionManager!!.isUserLoggedIn) {
+ menu.findItem(R.id.menu_set_as_avatar).setVisible(false)
+ }
+ }
+ }
+ }
+
+ /**
+ * Decide wether or not we should display the background color menu items
+ * We display them if the image is transparent
+ * @param getBitmap
+ * @param menu
+ */
+ private fun handleBackgroundColorMenuItems(getBitmap: Callable, menu: Menu) {
+ Observable.fromCallable(
+ getBitmap
+ ).subscribeOn(Schedulers.newThread())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(Consumer { image: Bitmap ->
+ if (image.hasAlpha()) {
+ menu.findItem(R.id.menu_view_set_white_background).setVisible(true)
+ .setEnabled(true)
+ menu.findItem(R.id.menu_view_set_black_background).setVisible(true)
+ .setEnabled(true)
+ }
+ })
+ }
+
+ private fun updateBookmarkState(item: MenuItem) {
+ val isBookmarked = bookmarkDao!!.findBookmark(bookmark)
+ if (isBookmarked) {
+ if (removedItems.contains(binding!!.mediaDetailsPager.currentItem)) {
+ removedItems.remove(binding!!.mediaDetailsPager.currentItem)
+ }
+ } else {
+ if (!removedItems.contains(binding!!.mediaDetailsPager.currentItem)) {
+ removedItems.add(binding!!.mediaDetailsPager.currentItem)
+ }
+ }
+
+ item.setIcon(if (isBookmarked) {
+ R.drawable.menu_ic_round_star_filled_24px
+ } else {
+ R.drawable.menu_ic_round_star_border_24px
+ })
+ }
+
+ fun showImage(i: Int, isWikipediaButtonDisplayed: Boolean) {
+ this.isWikipediaButtonDisplayed = isWikipediaButtonDisplayed
+ setViewPagerCurrentItem(i)
+ }
+
+ fun showImage(i: Int) {
+ setViewPagerCurrentItem(i)
+ }
+
+ /**
+ * This function waits for the item to load then sets the item to current item
+ * @param position current item that to be shown
+ */
+ private fun setViewPagerCurrentItem(position: Int) {
+ val handler = Handler(Looper.getMainLooper())
+ val runnable: Runnable = object : Runnable {
+ override fun run() {
+ // Show the ProgressBar while waiting for the item to load
+ imageProgressBar!!.visibility = View.VISIBLE
+ // Check if the adapter has enough items loaded
+ if (adapter!!.count > position) {
+ // Set the current item in the ViewPager
+ binding!!.mediaDetailsPager.setCurrentItem(position, false)
+ // Hide the ProgressBar once the item is loaded
+ imageProgressBar!!.visibility = View.GONE
+ } else {
+ // If the item is not ready yet, post the Runnable again
+ handler.post(this)
+ }
+ }
+ }
+ // Start the Runnable
+ handler.post(runnable)
+ }
+
+ /**
+ * The method notify the viewpager that number of items have changed.
+ */
+ fun notifyDataSetChanged() {
+ if (null != adapter) {
+ adapter!!.notifyDataSetChanged()
+ }
+ }
+
+ override fun onPageScrolled(i: Int, v: Float, i2: Int) {
+ if (activity == null) {
+ Timber.d("Returning as activity is destroyed!")
+ return
+ }
+
+ requireActivity().invalidateOptionsMenu()
+ }
+
+ override fun onPageSelected(i: Int) {
+ }
+
+ override fun onPageScrollStateChanged(i: Int) {
+ }
+
+ fun onDataSetChanged() {
+ if (null != adapter) {
+ adapter!!.notifyDataSetChanged()
+ }
+ }
+
+ /**
+ * Called after the media is nominated for deletion
+ *
+ * @param index item position that has been nominated
+ */
+ override fun nominatingForDeletion(index: Int) {
+ mediaDetailProvider!!.refreshNominatedMedia(index)
+ }
+
+ companion object {
+ private val compositeDisposable = CompositeDisposable()
+
+ /**
+ * Use this factory method to create a new instance of this fragment using the provided
+ * parameters.
+ *
+ * This method will create a new instance of MediaDetailPagerFragment and the arguments will be
+ * saved to a bundle which will be later available in the [.onCreate]
+ * @param editable
+ * @param isFeaturedImage
+ * @return
+ */
+ @JvmStatic
+ fun newInstance(editable: Boolean, isFeaturedImage: Boolean): MediaDetailPagerFragment {
+ val mediaDetailPagerFragment = MediaDetailPagerFragment()
+ val args = Bundle()
+ args.putBoolean("is_editable", editable)
+ args.putBoolean("is_featured_image", isFeaturedImage)
+ mediaDetailPagerFragment.arguments = args
+ return mediaDetailPagerFragment
+ }
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailProvider.kt b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailProvider.kt
new file mode 100644
index 0000000000..591adfe75b
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailProvider.kt
@@ -0,0 +1,14 @@
+package fr.free.nrw.commons.media
+
+import fr.free.nrw.commons.Media
+
+interface MediaDetailProvider {
+ fun getMediaAtPosition(i: Int): Media?
+
+ fun getTotalMediaCount(): Int
+
+ fun getContributionStateAt(position: Int): Int?
+
+ // Reload media detail fragment once media is nominated
+ fun refreshNominatedMedia(index: Int)
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaInterface.kt b/app/src/main/java/fr/free/nrw/commons/media/MediaInterface.kt
index 643374e54c..d6c87410a1 100644
--- a/app/src/main/java/fr/free/nrw/commons/media/MediaInterface.kt
+++ b/app/src/main/java/fr/free/nrw/commons/media/MediaInterface.kt
@@ -1,6 +1,6 @@
package fr.free.nrw.commons.media
-import fr.free.nrw.commons.OkHttpConnectionFactory.UnsuccessfulResponseInterceptor.SUPPRESS_ERROR_LOG_HEADER
+import fr.free.nrw.commons.SUPPRESS_ERROR_LOG_HEADER
import fr.free.nrw.commons.wikidata.mwapi.MwQueryResponse
import io.reactivex.Single
import retrofit2.http.GET
diff --git a/app/src/main/java/fr/free/nrw/commons/media/MwParseResponse.java b/app/src/main/java/fr/free/nrw/commons/media/MwParseResponse.java
deleted file mode 100644
index 28df3811a6..0000000000
--- a/app/src/main/java/fr/free/nrw/commons/media/MwParseResponse.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package fr.free.nrw.commons.media;
-
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-
-import fr.free.nrw.commons.wikidata.mwapi.MwResponse;
-
-public class MwParseResponse extends MwResponse {
- @Nullable
- private MwParseResult parse;
-
- @Nullable
- public MwParseResult parse() {
- return parse;
- }
-
- public boolean success() {
- return parse != null;
- }
-
- @VisibleForTesting
- protected void setParse(@Nullable MwParseResult parse) {
- this.parse = parse;
- }
-}
diff --git a/app/src/main/java/fr/free/nrw/commons/media/MwParseResponse.kt b/app/src/main/java/fr/free/nrw/commons/media/MwParseResponse.kt
new file mode 100644
index 0000000000..fc0282a9e9
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/media/MwParseResponse.kt
@@ -0,0 +1,17 @@
+package fr.free.nrw.commons.media
+
+import androidx.annotation.VisibleForTesting
+import fr.free.nrw.commons.wikidata.mwapi.MwResponse
+
+class MwParseResponse : MwResponse() {
+ private var parse: MwParseResult? = null
+
+ fun parse(): MwParseResult? = parse
+
+ fun success(): Boolean = parse != null
+
+ @VisibleForTesting
+ protected fun setParse(parse: MwParseResult?) {
+ this.parse = parse
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/media/MwParseResult.java b/app/src/main/java/fr/free/nrw/commons/media/MwParseResult.java
deleted file mode 100644
index edb7ff4478..0000000000
--- a/app/src/main/java/fr/free/nrw/commons/media/MwParseResult.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package fr.free.nrw.commons.media;
-
-import com.google.gson.annotations.SerializedName;
-
-public class MwParseResult {
- @SuppressWarnings("unused") private int pageid;
- @SuppressWarnings("unused") private int index;
- private MwParseText text;
-
- public String text() {
- return text.text;
- }
-
-
- public class MwParseText{
- @SerializedName("*") private String text;
- }
-}
diff --git a/app/src/main/java/fr/free/nrw/commons/media/MwParseResult.kt b/app/src/main/java/fr/free/nrw/commons/media/MwParseResult.kt
new file mode 100644
index 0000000000..7aacdea095
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/media/MwParseResult.kt
@@ -0,0 +1,18 @@
+package fr.free.nrw.commons.media
+
+import com.google.gson.annotations.SerializedName
+
+class MwParseResult {
+ private val pageid = 0
+ private val index = 0
+ private val text: MwParseText? = null
+
+ fun text(): String? {
+ return text?.text
+ }
+
+ inner class MwParseText {
+ @SerializedName("*")
+ internal val text: String? = null
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/mwapi/OkHttpJsonApiClient.kt b/app/src/main/java/fr/free/nrw/commons/mwapi/OkHttpJsonApiClient.kt
index 4fa7979d2f..a2f92c2e67 100644
--- a/app/src/main/java/fr/free/nrw/commons/mwapi/OkHttpJsonApiClient.kt
+++ b/app/src/main/java/fr/free/nrw/commons/mwapi/OkHttpJsonApiClient.kt
@@ -281,6 +281,7 @@ class OkHttpJsonApiClient @Inject constructor(
FeedbackResponse::class.java
)
} catch (e: Exception) {
+ e.printStackTrace()
return@fromCallable FeedbackResponse(0, 0, 0, FeaturedImages(0, 0), 0, "")
}
}
diff --git a/app/src/main/java/fr/free/nrw/commons/navtab/MoreBottomSheetFragment.kt b/app/src/main/java/fr/free/nrw/commons/navtab/MoreBottomSheetFragment.kt
index 3f7a196fe6..a4f08f2410 100644
--- a/app/src/main/java/fr/free/nrw/commons/navtab/MoreBottomSheetFragment.kt
+++ b/app/src/main/java/fr/free/nrw/commons/navtab/MoreBottomSheetFragment.kt
@@ -18,7 +18,6 @@ import fr.free.nrw.commons.BuildConfig
import fr.free.nrw.commons.CommonsApplication
import fr.free.nrw.commons.CommonsApplication.ActivityLogoutListener
import fr.free.nrw.commons.R
-import fr.free.nrw.commons.WelcomeActivity
import fr.free.nrw.commons.actions.PageEditClient
import fr.free.nrw.commons.databinding.FragmentMoreBottomSheetBinding
import fr.free.nrw.commons.di.ApplicationlessInjection
@@ -32,6 +31,7 @@ import fr.free.nrw.commons.logging.CommonsLogSender
import fr.free.nrw.commons.profile.ProfileActivity
import fr.free.nrw.commons.review.ReviewActivity
import fr.free.nrw.commons.settings.SettingsActivity
+import fr.free.nrw.commons.startWelcome
import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
@@ -114,13 +114,13 @@ class MoreBottomSheetFragment : BottomSheetDialogFragment() {
val level = store.getString("userAchievementsLevel", "0")
if (level == "0"){
binding?.moreProfile?.text = getString(
- R.string.profileLevel,
+ R.string.profile_withoutLevel,
getUserName(),
getString(R.string.see_your_achievements) // Second argument
)
} else {
binding?.moreProfile?.text = getString(
- R.string.profileLevel,
+ R.string.profile_withLevel,
getUserName(),
level
)
@@ -241,7 +241,7 @@ class MoreBottomSheetFragment : BottomSheetDialogFragment() {
}
fun onTutorialClicked() {
- WelcomeActivity.startYourself(requireActivity())
+ requireContext().startWelcome()
}
fun onSettingsClicked() {
diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyPlaces.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyPlaces.java
index caae8ee45f..3bb2f549f0 100644
--- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyPlaces.java
+++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyPlaces.java
@@ -1,10 +1,14 @@
package fr.free.nrw.commons.nearby;
+import static java.util.Collections.emptyList;
+
import android.location.Location;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import fr.free.nrw.commons.nearby.model.NearbyQueryParams;
+import fr.free.nrw.commons.nearby.model.NearbyQueryParams.Radial;
+import fr.free.nrw.commons.nearby.model.NearbyQueryParams.Rectangular;
import java.io.IOException;
-import java.util.Collections;
import java.util.List;
import javax.inject.Inject;
@@ -46,13 +50,14 @@ public NearbyPlaces(OkHttpJsonApiClient okHttpJsonApiClient) {
* @param customQuery
* @return list of places obtained
*/
+ @NonNull
List radiusExpander(final LatLng currentLatLng, final String lang,
final boolean returnClosestResult, @Nullable final String customQuery) throws Exception {
final int minResults;
final double maxRadius;
- List places = Collections.emptyList();
+ List places = emptyList();
// If returnClosestResult is true, then this means that we are trying to get closest point
// to use in cardView in Contributions fragment
@@ -113,6 +118,7 @@ public List getFromWikidataQuery(final LatLng cur, final String lang,
* @return A list of places obtained from the Wikidata query.
* @throws Exception If an error occurs during the retrieval process.
*/
+ @NonNull
public List getFromWikidataQuery(
final fr.free.nrw.commons.location.LatLng centerPoint,
final fr.free.nrw.commons.location.LatLng screenTopRight,
@@ -120,11 +126,11 @@ public List getFromWikidataQuery(
final boolean shouldQueryForMonuments,
@Nullable final String customQuery) throws Exception {
if (customQuery != null) {
- return okHttpJsonApiClient
- .getNearbyPlaces(
- new NearbyQueryParams.Rectangular(screenTopRight, screenBottomLeft), lang,
+ final List nearbyPlaces = okHttpJsonApiClient.getNearbyPlaces(
+ new Rectangular(screenTopRight, screenBottomLeft), lang,
shouldQueryForMonuments,
customQuery);
+ return nearbyPlaces != null ? nearbyPlaces : emptyList();
}
final int lowerLimit = 1000, upperLimit = 1500;
@@ -141,9 +147,10 @@ public List getFromWikidataQuery(
final int itemCount = okHttpJsonApiClient.getNearbyItemCount(
new NearbyQueryParams.Rectangular(screenTopRight, screenBottomLeft));
if (itemCount < upperLimit) {
- return okHttpJsonApiClient.getNearbyPlaces(
- new NearbyQueryParams.Rectangular(screenTopRight, screenBottomLeft), lang,
+ final List nearbyPlaces = okHttpJsonApiClient.getNearbyPlaces(
+ new Rectangular(screenTopRight, screenBottomLeft), lang,
shouldQueryForMonuments, null);
+ return nearbyPlaces != null ? nearbyPlaces : emptyList();
}
}
@@ -175,9 +182,10 @@ public List getFromWikidataQuery(
maxRadius = targetRadius - 1;
}
}
- return okHttpJsonApiClient.getNearbyPlaces(
- new NearbyQueryParams.Radial(centerPoint, targetRadius / 100f), lang, shouldQueryForMonuments,
+ final List nearbyPlaces = okHttpJsonApiClient.getNearbyPlaces(
+ new Radial(centerPoint, targetRadius / 100f), lang, shouldQueryForMonuments,
null);
+ return nearbyPlaces != null ? nearbyPlaces : emptyList();
}
/**
diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/fragments/CommonPlaceClickActions.kt b/app/src/main/java/fr/free/nrw/commons/nearby/fragments/CommonPlaceClickActions.kt
index 202f2c3057..5f4d0ab13b 100644
--- a/app/src/main/java/fr/free/nrw/commons/nearby/fragments/CommonPlaceClickActions.kt
+++ b/app/src/main/java/fr/free/nrw/commons/nearby/fragments/CommonPlaceClickActions.kt
@@ -10,12 +10,13 @@ import androidx.activity.result.ActivityResultLauncher
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.PopupMenu
import fr.free.nrw.commons.R
-import fr.free.nrw.commons.Utils
import fr.free.nrw.commons.auth.LoginActivity
import fr.free.nrw.commons.contributions.ContributionController
import fr.free.nrw.commons.kvstore.JsonKvStore
import fr.free.nrw.commons.nearby.Place
import fr.free.nrw.commons.utils.ActivityUtils
+import fr.free.nrw.commons.utils.handleGeoCoordinates
+import fr.free.nrw.commons.utils.handleWebUrl
import fr.free.nrw.commons.wikidata.WikidataConstants
import timber.log.Timber
import javax.inject.Inject
@@ -104,7 +105,7 @@ class CommonPlaceClickActions
fun onDirectionsClicked(): (Place) -> Unit =
{
- Utils.handleGeoCoordinates(activity, it.getLocation())
+ handleGeoCoordinates(activity, it.getLocation())
}
private fun storeSharedPrefs(selectedPlace: Place) {
@@ -113,7 +114,7 @@ class CommonPlaceClickActions
}
private fun openWebView(link: Uri): Boolean {
- Utils.handleWebUrl(activity, link)
+ handleWebUrl(activity, link)
return true
}
diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.kt b/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.kt
index 5b52c0ce5a..5c991f465e 100644
--- a/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.kt
+++ b/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.kt
@@ -58,12 +58,10 @@ import fr.free.nrw.commons.CommonsApplication
import fr.free.nrw.commons.MapController.NearbyPlacesInfo
import fr.free.nrw.commons.Media
import fr.free.nrw.commons.R
-import fr.free.nrw.commons.Utils
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao
import fr.free.nrw.commons.contributions.ContributionController
import fr.free.nrw.commons.contributions.MainActivity
import fr.free.nrw.commons.contributions.MainActivity.ActiveFragment
-import fr.free.nrw.commons.customselector.ui.selector.ImageLoader
import fr.free.nrw.commons.databinding.FragmentNearbyParentBinding
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment
import fr.free.nrw.commons.filepicker.FilePicker
@@ -76,7 +74,7 @@ import fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType
import fr.free.nrw.commons.location.LocationUpdateListener
import fr.free.nrw.commons.media.MediaClient
import fr.free.nrw.commons.media.MediaDetailPagerFragment
-import fr.free.nrw.commons.media.MediaDetailPagerFragment.MediaDetailProvider
+import fr.free.nrw.commons.media.MediaDetailProvider
import fr.free.nrw.commons.navtab.NavTab
import fr.free.nrw.commons.nearby.BottomSheetAdapter
import fr.free.nrw.commons.nearby.BottomSheetAdapter.ItemClickListener
@@ -105,6 +103,10 @@ import fr.free.nrw.commons.utils.NearbyFABUtils.removeAnchorFromFAB
import fr.free.nrw.commons.utils.NetworkUtils.isInternetConnectionEstablished
import fr.free.nrw.commons.utils.SystemThemeUtils
import fr.free.nrw.commons.utils.ViewUtil.showLongToast
+import fr.free.nrw.commons.utils.copyToClipboard
+import fr.free.nrw.commons.utils.handleGeoCoordinates
+import fr.free.nrw.commons.utils.handleWebUrl
+import fr.free.nrw.commons.utils.isMonumentsEnabled
import fr.free.nrw.commons.wikidata.WikidataConstants
import fr.free.nrw.commons.wikidata.WikidataEditListener
import fr.free.nrw.commons.wikidata.WikidataEditListener.WikidataP18EditListener
@@ -123,6 +125,7 @@ import org.osmdroid.views.CustomZoomButtonsController
import org.osmdroid.views.MapView
import org.osmdroid.views.overlay.MapEventsOverlay
import org.osmdroid.views.overlay.Marker
+import org.osmdroid.views.overlay.Overlay
import org.osmdroid.views.overlay.ScaleBarOverlay
import org.osmdroid.views.overlay.ScaleDiskOverlay
import org.osmdroid.views.overlay.TilesOverlay
@@ -139,7 +142,6 @@ import java.util.UUID
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import javax.inject.Named
-import javax.sql.DataSource
import kotlin.concurrent.Volatile
@@ -149,7 +151,7 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
LocationUpdateListener,
LocationPermissionCallback,
ItemClickListener,
- MediaDetailPagerFragment.MediaDetailProvider {
+ MediaDetailProvider {
var binding: FragmentNearbyParentBinding? = null
val mapEventsOverlay: MapEventsOverlay = MapEventsOverlay(object : MapEventsReceiver {
@@ -267,6 +269,9 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
private var dataList: MutableList? = null
private var bottomSheetAdapter: BottomSheetAdapter? = null
+ private var userLocationOverlay: Overlay? = null
+ private var userLocationErrorOverlay: Overlay? = null
+
private val galleryPickLauncherForResult =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
controller?.handleActivityResultWithCallback(
@@ -463,7 +468,7 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
}
}
_isDarkTheme = systemThemeUtils?.isDeviceInNightMode() == true
- if (Utils.isMonumentsEnabled(Date())) {
+ if (isMonumentsEnabled) {
binding?.rlContainerWlmMonthMessage?.visibility = View.VISIBLE
} else {
binding?.rlContainerWlmMonthMessage?.visibility = View.GONE
@@ -724,7 +729,7 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
val targetP = GeoPoint(target.latitude, target.longitude)
mapCenter = targetP
binding?.map?.controller?.setCenter(targetP)
- recenterMarkerToPosition(targetP)
+ updateUserLocationOverlays(targetP, true)
if (!isCameFromExploreMap()) {
moveCameraToPosition(targetP)
}
@@ -832,7 +837,7 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
loadAnimations()
setBottomSheetCallbacks()
addActionToTitle()
- if (!Utils.isMonumentsEnabled(Date())) {
+ if (!isMonumentsEnabled) {
NearbyFilterState.setWlmSelected(false)
}
}
@@ -1013,11 +1018,10 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
*/
private fun addActionToTitle() {
binding!!.bottomSheetDetails.title.setOnLongClickListener { view ->
- Utils.copy(
- "place", binding!!.bottomSheetDetails.title.text.toString(),
- context
+ requireContext().copyToClipboard(
+ "place", binding!!.bottomSheetDetails.title.text.toString()
)
- Toast.makeText(context, fr.free.nrw.commons.R.string.text_copy, Toast.LENGTH_SHORT)
+ Toast.makeText(requireContext(), fr.free.nrw.commons.R.string.text_copy, Toast.LENGTH_SHORT)
.show()
true
}
@@ -1576,7 +1580,7 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
searchLatLng,
false,
true,
- Utils.isMonumentsEnabled(Date()),
+ isMonumentsEnabled,
customQuery
)
}
@@ -1629,7 +1633,7 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
searchLatLng,
false,
true,
- Utils.isMonumentsEnabled(Date()),
+ isMonumentsEnabled,
customQuery
)
}
@@ -1863,6 +1867,8 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
lastKnownLocation = latLng
NearbyController.currentLocation = lastKnownLocation
presenter!!.updateMapAndList(locationChangeType)
+
+ updateUserLocationOverlays(GeoPoint(latLng.latitude, latLng.longitude), true)
}
override fun onLocationChangedSignificantly(latLng: LatLng) {
@@ -2644,43 +2650,14 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
*/
override fun clearAllMarkers() {
binding!!.map.overlayManager.clear()
- binding!!.map.invalidate()
- val geoPoint = mapCenter
- if (geoPoint != null) {
- val diskOverlay =
- ScaleDiskOverlay(
- this.context,
- geoPoint, 2000, UnitOfMeasure.foot
- )
- val circlePaint = Paint()
- circlePaint.color = Color.rgb(128, 128, 128)
- circlePaint.style = Paint.Style.STROKE
- circlePaint.strokeWidth = 2f
- diskOverlay.setCirclePaint2(circlePaint)
- val diskPaint = Paint()
- diskPaint.color = Color.argb(40, 128, 128, 128)
- diskPaint.style = Paint.Style.FILL_AND_STROKE
- diskOverlay.setCirclePaint1(diskPaint)
- diskOverlay.setDisplaySizeMin(900)
- diskOverlay.setDisplaySizeMax(1700)
- binding!!.map.overlays.add(diskOverlay)
- val startMarker = Marker(
- binding!!.map
- )
- startMarker.position = geoPoint
- startMarker.setAnchor(
- Marker.ANCHOR_CENTER,
- Marker.ANCHOR_BOTTOM
- )
- startMarker.icon =
- getDrawable(
- this.requireContext(),
- fr.free.nrw.commons.R.drawable.current_location_marker
- )
- startMarker.title = "Your Location"
- startMarker.textLabelFontSize = 24
- binding!!.map.overlays.add(startMarker)
+
+ var geoPoint = mapCenter
+ val lastLatLng = locationManager.getLastLocation()
+ if (lastLatLng != null) {
+ geoPoint = GeoPoint(lastLatLng.latitude, lastLatLng.longitude)
}
+ updateUserLocationOverlays(geoPoint, false)
+
val scaleBarOverlay = ScaleBarOverlay(binding!!.map)
scaleBarOverlay.setScaleBarOffset(15, 25)
val barPaint = Paint()
@@ -2690,6 +2667,7 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
binding!!.map.overlays.add(scaleBarOverlay)
binding!!.map.overlays.add(mapEventsOverlay)
binding!!.map.setMultiTouchControls(true)
+ binding!!.map.invalidate()
}
/**
@@ -2700,43 +2678,147 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
private fun recenterMarkerToPosition(geoPoint: GeoPoint?) {
geoPoint?.let {
binding?.map?.controller?.setCenter(it)
- val overlays = binding?.map?.overlays ?: return@let
-
- // Remove markers and disks using index-based removal
- var i = 0
- while (i < overlays.size) {
- when (overlays[i]) {
- is Marker, is ScaleDiskOverlay -> overlays.removeAt(i)
- else -> i++
- }
- }
- // Add disk overlay
- ScaleDiskOverlay(context, it, 2000, UnitOfMeasure.foot).apply {
- setCirclePaint2(Paint().apply {
- color = Color.rgb(128, 128, 128)
- style = Paint.Style.STROKE
- strokeWidth = 2f
- })
- setCirclePaint1(Paint().apply {
- color = Color.argb(40, 128, 128, 128)
- style = Paint.Style.FILL_AND_STROKE
- })
- setDisplaySizeMin(900)
- setDisplaySizeMax(1700)
- overlays.add(this)
+ updateUserLocationOverlays(it, true);
+ }
+ }
+
+ /**
+ * Updates the user current location overlays (both the location and error overlays) by
+ * replacing any existing location overlays with new overlays at the given GeoPoint. If there
+ * are no existing location and error overlays, then new overlays are added.
+ *
+ * @param geoPoint The GeoPoint representing the user's current location.
+ * @param invalidate If true, the map overlays will be invalidated after the user
+ * location overlays are updated/added. If false, the overlays will not be invalidated.
+ */
+ private fun updateUserLocationOverlays(geoPoint: GeoPoint?, invalidate: Boolean) {
+ geoPoint?.let{
+ updateUserLocationOverlay(geoPoint)
+ updateUserLocationErrorOverlay(geoPoint)
+ }
+
+ if (invalidate) {
+ binding!!.map.invalidate()
+ }
+ }
+
+ /**
+ * Updates the user location error overlay by either replacing it with or adding a new one.
+ *
+ * If the user location error overlay is null, the new overlay is added. If the
+ * overlay is not null, it is replaced by the new overlay.
+ *
+ * @param geoPoint The GeoPoint representing the user's location
+ */
+ private fun updateUserLocationErrorOverlay(geoPoint: GeoPoint) {
+ val overlays = binding?.map?.overlays ?: return
+
+ // Multiply accuracy by 2 to get 95% confidence interval
+ val accuracy = getCurrentLocationAccuracy() * 2
+ val overlay = createCurrentLocationErrorOverlay(this.context, geoPoint,
+ (accuracy).toInt(), UnitOfMeasure.meter)
+
+ val index = overlays.indexOf(userLocationErrorOverlay)
+
+ if (userLocationErrorOverlay == null || index == -1) {
+ overlays.add(overlay)
+ } else {
+ overlays[index] = overlay
}
- // Add marker
- Marker(binding?.map).apply {
- position = it
- setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM)
- icon = getDrawable(context, R.drawable.current_location_marker)
- title = "Your Location"
- textLabelFontSize = 24
- overlays.add(this)
+ userLocationErrorOverlay = overlay
+ }
+
+ /**
+ * Updates the user location overlay by either replacing it with or adding a new one.
+ *
+ * If the user location overlay is null, the new overlay is added. If the
+ * overlay is not null, it is replaced by the new overlay.
+ *
+ * @param geoPoint The GeoPoint representing the user's location
+ */
+ private fun updateUserLocationOverlay(geoPoint: GeoPoint) {
+ val overlays = binding?.map?.overlays ?: return
+
+ val overlay = createCurrentLocationOverlay(geoPoint)
+
+ val index = overlays.indexOf(userLocationOverlay)
+
+ if (userLocationOverlay == null || index == -1) {
+ overlays.add(overlay)
+ } else {
+ overlays[index] = overlay
}
+
+ userLocationOverlay = overlay
+ }
+
+ /**
+ * @return The accuracy of the current location with a confidence at the 68th percentile.
+ * Units are in meters. Returning 0 may indicate failure.
+ */
+ private fun getCurrentLocationAccuracy(): Float {
+ var accuracy = 0f
+ val lastLocation = locationManager.getLastLocation()
+ if (lastLocation != null) {
+ accuracy = lastLocation.accuracy
}
+
+ return accuracy
+ }
+
+ /**
+ * Creates the current location overlay
+ *
+ * @param geoPoint The GeoPoint where the current location overlay will be placed.
+ *
+ * @return The current location overlay as a Marker
+ */
+ private fun createCurrentLocationOverlay(geoPoint: GeoPoint): Marker {
+ val currentLocationOverlay = Marker(
+ binding!!.map
+ )
+ currentLocationOverlay.position = geoPoint
+ currentLocationOverlay.icon =
+ getDrawable(
+ this.requireContext(),
+ fr.free.nrw.commons.R.drawable.current_location_marker
+ )
+ currentLocationOverlay.title = "Your Location"
+ currentLocationOverlay.textLabelFontSize = 24
+ currentLocationOverlay.setAnchor(0.5f, 0.5f)
+
+ return currentLocationOverlay
+ }
+
+ /**
+ * Creates the location error overlay to show the user how accurate the current location
+ * overlay is. The edge of the disk is the 95% confidence interval.
+ *
+ * @param context The Android context
+ * @param point The user's location as a GeoPoint
+ * @param value The radius of the disk
+ * @param unitOfMeasure The unit of measurement of the value/disk radius.
+ *
+ * @return The location error overlay as a ScaleDiskOverlay.
+ */
+ private fun createCurrentLocationErrorOverlay(context: Context?, point: GeoPoint, value: Int,
+ unitOfMeasure: UnitOfMeasure): ScaleDiskOverlay {
+ val scaleDisk = ScaleDiskOverlay(context, point, value, unitOfMeasure)
+
+ scaleDisk.setCirclePaint2(Paint().apply {
+ color = Color.rgb(128, 128, 128)
+ style = Paint.Style.STROKE
+ strokeWidth = 2f
+ })
+
+ scaleDisk.setCirclePaint1(Paint().apply {
+ color = Color.argb(40, 128, 128, 128)
+ style = Paint.Style.FILL_AND_STROKE
+ })
+
+ return scaleDisk
}
private fun moveCameraToPosition(geoPoint: GeoPoint) {
@@ -2772,14 +2854,14 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
R.drawable.ic_directions_black_24dp -> {
selectedPlace?.let {
- Utils.handleGeoCoordinates(this.context, it.getLocation())
+ handleGeoCoordinates(requireContext(), it.getLocation())
binding?.map?.zoomLevelDouble ?: 0.0
}
}
R.drawable.ic_wikidata_logo_24dp -> {
selectedPlace?.siteLinks?.wikidataLink?.let {
- Utils.handleWebUrl(this.context, it)
+ handleWebUrl(requireContext(), it)
}
}
@@ -2797,13 +2879,13 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
R.drawable.ic_wikipedia_logo_24dp -> {
selectedPlace?.siteLinks?.wikipediaLink?.let {
- Utils.handleWebUrl(this.context, it)
+ handleWebUrl(requireContext(), it)
}
}
R.drawable.ic_commons_icon_vector -> {
selectedPlace?.siteLinks?.commonsLink?.let {
- Utils.handleWebUrl(this.context, it)
+ handleWebUrl(requireContext(), it)
}
}
diff --git a/app/src/main/java/fr/free/nrw/commons/notification/NotificationActivity.kt b/app/src/main/java/fr/free/nrw/commons/notification/NotificationActivity.kt
index 1547f89ad0..76975964b4 100644
--- a/app/src/main/java/fr/free/nrw/commons/notification/NotificationActivity.kt
+++ b/app/src/main/java/fr/free/nrw/commons/notification/NotificationActivity.kt
@@ -13,7 +13,6 @@ import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.snackbar.Snackbar
import fr.free.nrw.commons.CommonsApplication
import fr.free.nrw.commons.R
-import fr.free.nrw.commons.Utils
import fr.free.nrw.commons.auth.SessionManager
import fr.free.nrw.commons.auth.csrf.InvalidLoginTokenException
import fr.free.nrw.commons.databinding.ActivityNotificationBinding
@@ -22,6 +21,7 @@ import fr.free.nrw.commons.notification.models.NotificationType
import fr.free.nrw.commons.theme.BaseActivity
import fr.free.nrw.commons.utils.NetworkUtils
import fr.free.nrw.commons.utils.ViewUtil
+import fr.free.nrw.commons.utils.handleWebUrl
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
@@ -197,7 +197,7 @@ class NotificationActivity : BaseActivity() {
private fun handleUrl(url: String?) {
if (url.isNullOrEmpty()) return
- Utils.handleWebUrl(this, Uri.parse(url))
+ handleWebUrl(this, Uri.parse(url))
}
private fun setItems(notificationList: List?) {
diff --git a/app/src/main/java/fr/free/nrw/commons/profile/ProfileActivity.kt b/app/src/main/java/fr/free/nrw/commons/profile/ProfileActivity.kt
index 164842c9ac..48e61051c4 100644
--- a/app/src/main/java/fr/free/nrw/commons/profile/ProfileActivity.kt
+++ b/app/src/main/java/fr/free/nrw/commons/profile/ProfileActivity.kt
@@ -3,16 +3,17 @@ package fr.free.nrw.commons.profile
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
-import android.net.Uri
import android.os.Bundle
import android.util.Log
-import android.view.*
+import android.view.Menu
+import android.view.MenuItem
+import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.core.content.FileProvider
+import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment
import fr.free.nrw.commons.R
-import fr.free.nrw.commons.Utils
import fr.free.nrw.commons.ViewPagerAdapter
import fr.free.nrw.commons.auth.SessionManager
import fr.free.nrw.commons.contributions.ContributionsFragment
@@ -23,7 +24,7 @@ import fr.free.nrw.commons.theme.BaseActivity
import fr.free.nrw.commons.utils.DialogUtil
import java.io.File
import java.io.FileOutputStream
-import java.util.*
+import java.util.Locale
import javax.inject.Inject
/**
@@ -71,7 +72,7 @@ class ProfileActivity : BaseActivity() {
title = userName
shouldShowContributions = intent.getBooleanExtra(KEY_SHOULD_SHOW_CONTRIBUTIONS, false)
- viewPagerAdapter = ViewPagerAdapter(supportFragmentManager)
+ viewPagerAdapter = ViewPagerAdapter(this, supportFragmentManager)
binding.viewPager.adapter = viewPagerAdapter
binding.tabLayout.setupWithViewPager(binding.viewPager)
setTabs()
@@ -83,39 +84,23 @@ class ProfileActivity : BaseActivity() {
}
private fun setTabs() {
- val fragmentList = mutableListOf()
- val titleList = mutableListOf()
-
- // Add Achievements tab
achievementsFragment = AchievementsFragment().apply {
- arguments = Bundle().apply {
- putString(KEY_USERNAME, userName)
- }
+ arguments = bundleOf(KEY_USERNAME to userName)
}
- fragmentList.add(achievementsFragment)
- titleList.add(resources.getString(R.string.achievements_tab_title).uppercase())
- // Add Leaderboard tab
leaderboardFragment = LeaderboardFragment().apply {
- arguments = Bundle().apply {
- putString(KEY_USERNAME, userName)
- }
+ arguments = bundleOf(KEY_USERNAME to userName)
}
- fragmentList.add(leaderboardFragment)
- titleList.add(resources.getString(R.string.leaderboard_tab_title).uppercase(Locale.ROOT))
- // Add Contributions tab
contributionsFragment = ContributionsFragment().apply {
- arguments = Bundle().apply {
- putString(KEY_USERNAME, userName)
- }
- }
- contributionsFragment?.let {
- fragmentList.add(it)
- titleList.add(getString(R.string.contributions_fragment).uppercase(Locale.ROOT))
+ arguments = bundleOf(KEY_USERNAME to userName)
}
- viewPagerAdapter.setTabData(fragmentList, titleList)
+ viewPagerAdapter.setTabs(
+ R.string.achievements_tab_title to achievementsFragment,
+ R.string.leaderboard_tab_title to leaderboardFragment,
+ R.string.contributions_fragment to contributionsFragment!!
+ )
viewPagerAdapter.notifyDataSetChanged()
}
@@ -133,7 +118,7 @@ class ProfileActivity : BaseActivity() {
return when (item.itemId) {
R.id.share_app_icon -> {
val rootView = window.decorView.findViewById(android.R.id.content)
- val screenShot = Utils.getScreenShot(rootView)
+ val screenShot = getScreenShot(rootView)
if (screenShot == null) {
Log.e("ERROR", "ScreenShot is null")
return false
@@ -212,6 +197,24 @@ class ProfileActivity : BaseActivity() {
binding.tabLayout.visibility = if (isVisible) View.VISIBLE else View.GONE
}
+ /**
+ * To take screenshot of the screen and return it in Bitmap format
+ *
+ * @param view
+ * @return
+ */
+ fun getScreenShot(view: View): Bitmap? {
+ val screenView = view.rootView
+ screenView.isDrawingCacheEnabled = true
+ val drawingCache = screenView.drawingCache
+ if (drawingCache != null) {
+ val bitmap = Bitmap.createBitmap(drawingCache)
+ screenView.isDrawingCacheEnabled = false
+ return bitmap
+ }
+ return null
+ }
+
companion object {
const val KEY_USERNAME = "username"
const val KEY_SHOULD_SHOW_CONTRIBUTIONS = "shouldShowContributions"
diff --git a/app/src/main/java/fr/free/nrw/commons/profile/achievements/AchievementsFragment.kt b/app/src/main/java/fr/free/nrw/commons/profile/achievements/AchievementsFragment.kt
index f967b8619e..8f23674ca9 100644
--- a/app/src/main/java/fr/free/nrw/commons/profile/achievements/AchievementsFragment.kt
+++ b/app/src/main/java/fr/free/nrw/commons/profile/achievements/AchievementsFragment.kt
@@ -15,7 +15,6 @@ import com.google.android.material.badge.BadgeDrawable
import com.google.android.material.badge.BadgeUtils
import com.google.android.material.badge.ExperimentalBadgeUtils
import fr.free.nrw.commons.R
-import fr.free.nrw.commons.Utils
import fr.free.nrw.commons.auth.SessionManager
import fr.free.nrw.commons.databinding.FragmentAchievementsBinding
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment
@@ -27,6 +26,7 @@ import fr.free.nrw.commons.utils.ConfigUtils.isBetaFlavour
import fr.free.nrw.commons.utils.DialogUtil.showAlertDialog
import fr.free.nrw.commons.utils.ViewUtil.showDismissibleSnackBar
import fr.free.nrw.commons.utils.ViewUtil.showLongToast
+import fr.free.nrw.commons.utils.handleWebUrl
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import org.apache.commons.lang3.StringUtils
@@ -524,7 +524,7 @@ class AchievementsFragment : CommonsDaggerSupportFragment(){
getString(R.string.ok),
getString(R.string.read_help_link),
{},
- { Utils.handleWebUrl(requireContext(), Uri.parse(helpLinkUrl)) },
+ { handleWebUrl(requireContext(), Uri.parse(helpLinkUrl)) },
null
)
}
diff --git a/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.kt b/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.kt
index 092f057e90..387dd4672b 100644
--- a/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.kt
+++ b/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.kt
@@ -33,9 +33,7 @@ import com.karumi.dexter.MultiplePermissionsReport
import com.karumi.dexter.PermissionToken
import com.karumi.dexter.listener.PermissionRequest
import com.karumi.dexter.listener.multi.MultiplePermissionsListener
-import fr.free.nrw.commons.BuildConfig.MOBILE_META_URL
import fr.free.nrw.commons.R
-import fr.free.nrw.commons.Utils
import fr.free.nrw.commons.activity.SingleWebViewActivity
import fr.free.nrw.commons.campaigns.CampaignView
import fr.free.nrw.commons.contributions.ContributionController
@@ -53,6 +51,7 @@ import fr.free.nrw.commons.utils.DialogUtil
import fr.free.nrw.commons.utils.PermissionUtils
import fr.free.nrw.commons.utils.StringUtil
import fr.free.nrw.commons.utils.ViewUtil
+import fr.free.nrw.commons.utils.handleWebUrl
import java.util.Locale
import javax.inject.Inject
import javax.inject.Named
@@ -239,7 +238,10 @@ class SettingsFragment : PreferenceFragmentCompat() {
val betaTesterPreference: Preference? = findPreference("becomeBetaTester")
betaTesterPreference?.setOnPreferenceClickListener {
- Utils.handleWebUrl(requireActivity(), Uri.parse(getString(R.string.beta_opt_in_link)))
+ handleWebUrl(
+ requireActivity(),
+ Uri.parse(getString(R.string.beta_opt_in_link))
+ )
true
}
@@ -296,7 +298,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
getString(R.string.ok),
getString(R.string.read_help_link),
{ },
- { Utils.handleWebUrl(requireContext(), Uri.parse(GET_CONTENT_PICKER_HELP_URL)) },
+ { handleWebUrl(requireContext(), Uri.parse(GET_CONTENT_PICKER_HELP_URL)) },
null
)
}
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/PageContentsCreator.kt b/app/src/main/java/fr/free/nrw/commons/upload/PageContentsCreator.kt
index 0c4ded8b21..2de17f8499 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/PageContentsCreator.kt
+++ b/app/src/main/java/fr/free/nrw/commons/upload/PageContentsCreator.kt
@@ -1,11 +1,11 @@
package fr.free.nrw.commons.upload
import android.content.Context
-import fr.free.nrw.commons.Utils
import fr.free.nrw.commons.contributions.Contribution
import fr.free.nrw.commons.filepicker.UploadableFile.DateTimeWithSource
import fr.free.nrw.commons.settings.Prefs.Licenses
import fr.free.nrw.commons.utils.ConfigUtils.getVersionNameWithSha
+import fr.free.nrw.commons.utils.getWikiLovesMonumentsYear
import org.apache.commons.lang3.StringUtils
import java.text.SimpleDateFormat
import java.util.Calendar
@@ -49,7 +49,7 @@ class PageContentsCreator @Inject constructor(private val context: Context) {
String.format(
Locale.ENGLISH,
"{{Wiki Loves Monuments %d|1= %s}}\n",
- Utils.getWikiLovesMonumentsYear(Calendar.getInstance()),
+ getWikiLovesMonumentsYear(Calendar.getInstance()),
contribution.countryCode
)
)
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadItem.kt b/app/src/main/java/fr/free/nrw/commons/upload/UploadItem.kt
index f357cd112a..6d2321defa 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/UploadItem.kt
+++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadItem.kt
@@ -1,10 +1,10 @@
package fr.free.nrw.commons.upload
import android.net.Uri
-import fr.free.nrw.commons.Utils
import fr.free.nrw.commons.filepicker.MimeTypeMapWrapper.Companion.getExtensionFromMimeType
import fr.free.nrw.commons.nearby.Place
import fr.free.nrw.commons.utils.ImageUtils
+import fr.free.nrw.commons.utils.fixExtension
class UploadItem(
var mediaUri: Uri?,
@@ -32,7 +32,7 @@ class UploadItem(
* languages have been entered, the first language is used.
*/
val filename: String
- get() = Utils.fixExtension(
+ get() = fixExtension(
uploadMediaDetails[0].captionText,
getExtensionFromMimeType(mimeType)
)
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadProgressActivity.kt b/app/src/main/java/fr/free/nrw/commons/upload/UploadProgressActivity.kt
index aeaefa3022..5d98ebffb0 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/UploadProgressActivity.kt
+++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadProgressActivity.kt
@@ -28,8 +28,6 @@ class UploadProgressActivity : BaseActivity() {
@Inject
lateinit var contributionDao: ContributionDao
- val fragmentList: MutableList = ArrayList()
- val titleList: MutableList = ArrayList()
var isPaused = true
var isPendingIconsVisible = true
var isErrorIconsVisisble = false
@@ -38,7 +36,7 @@ class UploadProgressActivity : BaseActivity() {
super.onCreate(savedInstanceState)
binding = ActivityUploadProgressBinding.inflate(layoutInflater)
setContentView(binding.root)
- viewPagerAdapter = ViewPagerAdapter(supportFragmentManager)
+ viewPagerAdapter = ViewPagerAdapter(this, supportFragmentManager)
binding.uploadProgressViewPager.setAdapter(viewPagerAdapter)
binding.uploadProgressViewPager.setId(R.id.upload_progress_view_pager)
binding.uploadProgressTabLayout.setupWithViewPager(binding.uploadProgressViewPager)
@@ -81,11 +79,10 @@ class UploadProgressActivity : BaseActivity() {
pendingUploadsFragment = PendingUploadsFragment()
failedUploadsFragment = FailedUploadsFragment()
- fragmentList.add(pendingUploadsFragment!!)
- titleList.add(getString(R.string.pending))
- fragmentList.add(failedUploadsFragment!!)
- titleList.add(getString(R.string.failed))
- viewPagerAdapter!!.setTabData(fragmentList, titleList)
+ viewPagerAdapter!!.setTabs(
+ R.string.pending to pendingUploadsFragment!!,
+ R.string.failed to failedUploadsFragment!!
+ )
viewPagerAdapter!!.notifyDataSetChanged()
}
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/license/MediaLicenseFragment.kt b/app/src/main/java/fr/free/nrw/commons/upload/license/MediaLicenseFragment.kt
index 0415d3270a..65826a5050 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/license/MediaLicenseFragment.kt
+++ b/app/src/main/java/fr/free/nrw/commons/upload/license/MediaLicenseFragment.kt
@@ -16,11 +16,13 @@ import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.TextView
import fr.free.nrw.commons.R
-import fr.free.nrw.commons.Utils
import fr.free.nrw.commons.databinding.FragmentMediaLicenseBinding
import fr.free.nrw.commons.upload.UploadActivity
import fr.free.nrw.commons.upload.UploadBaseFragment
import fr.free.nrw.commons.utils.DialogUtil.showAlertDialog
+import fr.free.nrw.commons.utils.handleWebUrl
+import fr.free.nrw.commons.utils.toLicenseName
+import fr.free.nrw.commons.utils.toLicenseUrl
import timber.log.Timber
import javax.inject.Inject
@@ -126,20 +128,20 @@ class MediaLicenseFragment : UploadBaseFragment(), MediaLicenseContract.View {
}
override fun setSelectedLicense(license: String?) {
- var position = licenses!!.indexOf(getString(Utils.licenseNameFor(license)))
+ var position = license?.let { licenses!!.indexOf(getString(it.toLicenseName())) } ?: -1
// Check if position is valid
if (position < 0) {
Timber.d("Invalid position: %d. Using default licenses", position)
position = licenses!!.size - 1
- } else {
- Timber.d("Position: %d %s", position, getString(Utils.licenseNameFor(license)))
}
binding.spinnerLicenseList.setSelection(position)
}
override fun updateLicenseSummary(selectedLicense: String?, numberOfItems: Int) {
- val licenseHyperLink = "" +
- getString(Utils.licenseNameFor(selectedLicense)) + "
"
+ if (selectedLicense == null) return
+
+ val licenseHyperLink = "" +
+ getString(selectedLicense.toLicenseName()) + "
"
setTextViewHTML(
binding.tvShareLicenseSummary, resources
@@ -184,7 +186,7 @@ class MediaLicenseFragment : UploadBaseFragment(), MediaLicenseContract.View {
}
private fun launchBrowser(hyperLink: String) =
- Utils.handleWebUrl(context, Uri.parse(hyperLink))
+ handleWebUrl(requireContext(), Uri.parse(hyperLink))
override fun onDestroyView() {
presenter.onDetachView()
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/license/MediaLicensePresenter.kt b/app/src/main/java/fr/free/nrw/commons/upload/license/MediaLicensePresenter.kt
index 25d1a23240..df75019b22 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/license/MediaLicensePresenter.kt
+++ b/app/src/main/java/fr/free/nrw/commons/upload/license/MediaLicensePresenter.kt
@@ -1,9 +1,9 @@
package fr.free.nrw.commons.upload.license
-import fr.free.nrw.commons.Utils
import fr.free.nrw.commons.kvstore.JsonKvStore
import fr.free.nrw.commons.repository.UploadRepository
import fr.free.nrw.commons.settings.Prefs
+import fr.free.nrw.commons.utils.toLicenseName
import timber.log.Timber
import java.lang.reflect.Method
import java.lang.reflect.Proxy
@@ -34,12 +34,14 @@ class MediaLicensePresenter @Inject constructor(
val licenses = repository.getLicenses()
view.setLicenses(licenses)
- var selectedLicense = defaultKVStore.getString(
+ //CC_BY_SA_4 is the default one used by the commons web app
+ var selectedLicense: String = defaultKVStore.getString(
Prefs.DEFAULT_LICENSE,
Prefs.Licenses.CC_BY_SA_4
- ) //CC_BY_SA_4 is the default one used by the commons web app
+ ) ?: Prefs.Licenses.CC_BY_SA_4
+
try { //I have to make sure that the stored default license was not one of the deprecated one's
- Utils.licenseNameFor(selectedLicense)
+ selectedLicense.toLicenseName()
} catch (exception: IllegalStateException) {
Timber.e(exception)
selectedLicense = Prefs.Licenses.CC_BY_SA_4
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailsContract.kt b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailsContract.kt
index c368b96ac3..d6d7742085 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailsContract.kt
+++ b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailsContract.kt
@@ -54,7 +54,7 @@ interface UploadMediaDetailsContract {
fun showBadImagePopup(errorCode: Int, index: Int, uploadItem: UploadItem)
}
- interface UserActionListener : BasePresenter {
+ interface UserActionListener : BasePresenter {
fun setupBasicKvStoreFactory(factory: (String) -> BasicKvStore)
fun receiveImage(
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaPresenter.kt b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaPresenter.kt
index 77999cf2fd..4d565adb2c 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaPresenter.kt
+++ b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaPresenter.kt
@@ -107,7 +107,10 @@ class UploadMediaPresenter @Inject constructor(
view.showProgress(false)
val gpsCoords = uploadItem.gpsCoords
val hasImageCoordinates = gpsCoords != null && gpsCoords.imageCoordsExists
- if (hasImageCoordinates && place == null) {
+
+ // Only check for nearby places if image has coordinates AND no place was pre-selected
+ // This prevents the popup from appearing when uploading from Nearby feature
+ if (hasImageCoordinates && place == null && uploadItem.place == null) {
checkNearbyPlaces(uploadItem)
}
}, { throwable: Throwable? ->
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/worker/UploadWorker.kt b/app/src/main/java/fr/free/nrw/commons/upload/worker/UploadWorker.kt
index 6d28085b2f..c8a1d9b989 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/worker/UploadWorker.kt
+++ b/app/src/main/java/fr/free/nrw/commons/upload/worker/UploadWorker.kt
@@ -472,7 +472,10 @@ class UploadWorker(
if (wikiDataPlace != null) {
if (!contribution.hasInvalidLocation()) {
var revisionID: Long? = null
+ val p18WasSkipped = !wikiDataPlace.imageValue.isNullOrBlank()
try {
+ if (!p18WasSkipped) {
+ // Only set P18 if the place does not already have a picture
revisionID =
wikidataEditService.createClaim(
wikiDataPlace,
@@ -489,9 +492,11 @@ class UploadWorker(
.subscribeOn(Schedulers.io())
.blockingAwait()
Timber.d("Updated WikiItem place ${place.name} with image ${place.pic}")
+ }
}
- showSuccessNotification(contribution)
}
+ // Always show success notification, whether P18 was set or skipped
+ showSuccessNotification(contribution)
} catch (exception: Exception) {
Timber.e(exception)
}
@@ -500,6 +505,7 @@ class UploadWorker(
wikidataEditService.handleImageClaimResult(
contribution.wikidataPlace!!,
revisionID,
+ p18WasSkipped = p18WasSkipped
)
}
} else {
diff --git a/app/src/main/java/fr/free/nrw/commons/utils/ClipboardUtils.kt b/app/src/main/java/fr/free/nrw/commons/utils/ClipboardUtils.kt
new file mode 100644
index 0000000000..64d3636f0b
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/utils/ClipboardUtils.kt
@@ -0,0 +1,20 @@
+package fr.free.nrw.commons.utils
+
+import android.content.ClipData
+import android.content.ClipboardManager
+import android.content.Context
+import android.content.Context.CLIPBOARD_SERVICE
+
+object ClipboardUtils {
+ // Convenience for Java usages - remove when they are converted.
+ @JvmStatic
+ fun copy(label: String?, text: String?, context: Context) {
+ context.copyToClipboard(label, text)
+ }
+}
+
+fun Context.copyToClipboard(label: String?, text: String?) {
+ with(getSystemService(CLIPBOARD_SERVICE) as ClipboardManager) {
+ setPrimaryClip(ClipData.newPlainText(label, text))
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/utils/FixExtension.kt b/app/src/main/java/fr/free/nrw/commons/utils/FixExtension.kt
new file mode 100644
index 0000000000..b9e3988a36
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/utils/FixExtension.kt
@@ -0,0 +1,38 @@
+package fr.free.nrw.commons.utils
+
+import java.util.Locale
+import java.util.regex.Pattern
+
+private val jpegPattern = Pattern.compile("\\.jpeg$", Pattern.CASE_INSENSITIVE)
+
+/**
+ * Adds extension to filename. Converts to .jpg if system provides .jpeg, adds .jpg if no extension detected
+ * @param theTitle File name
+ * @param ext Correct extension
+ * @return File with correct extension
+ */
+fun fixExtension(theTitle: String, ext: String?): String {
+ var result = theTitle
+ var extension = ext
+
+ // People are used to ".jpg" more than ".jpeg" which the system gives us.
+ if (extension != null && extension.lowercase() == "jpeg") {
+ extension = "jpg"
+ }
+
+ result = jpegPattern.matcher(result).replaceFirst(".jpg")
+ if (extension != null &&
+ !result.lowercase(Locale.getDefault()).endsWith("." + extension.lowercase())
+ ) {
+ result += ".$extension"
+ }
+
+ // If extension is still null, make it jpg. (Hotfix for https://github.com/commons-app/apps-android-commons/issues/228)
+ // If title has an extension in it, if won't be true
+ if (extension == null && result.lastIndexOf(".") <= 0) {
+ extension = "jpg"
+ result += ".$extension"
+ }
+
+ return result
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/utils/GeoCoordinates.kt b/app/src/main/java/fr/free/nrw/commons/utils/GeoCoordinates.kt
new file mode 100644
index 0000000000..513e36f102
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/utils/GeoCoordinates.kt
@@ -0,0 +1,27 @@
+package fr.free.nrw.commons.utils
+
+import android.content.Context
+import android.content.Intent
+import fr.free.nrw.commons.R
+import fr.free.nrw.commons.location.LatLng
+import fr.free.nrw.commons.utils.ViewUtil.showShortToast
+
+/**
+ * Util function to handle geo coordinates with specified zoom level. It no longer depends on
+ * google maps and any app capable of handling the map intent can handle it
+ *
+ * @param context The context for launching intent
+ * @param latLng The latitude and longitude of the location
+ * @param zoomLevel The zoom level
+ */
+fun handleGeoCoordinates(
+ context: Context, latLng: LatLng,
+ zoomLevel: Double = 16.0
+) {
+ val mapIntent = Intent(Intent.ACTION_VIEW, latLng.getGmmIntentUri(zoomLevel))
+ if (mapIntent.resolveActivity(context.packageManager) != null) {
+ context.startActivity(mapIntent)
+ } else {
+ showShortToast(context, context.getString(R.string.map_application_missing))
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/utils/Licenses.kt b/app/src/main/java/fr/free/nrw/commons/utils/Licenses.kt
new file mode 100644
index 0000000000..065a147184
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/utils/Licenses.kt
@@ -0,0 +1,31 @@
+package fr.free.nrw.commons.utils
+
+import fr.free.nrw.commons.R
+import fr.free.nrw.commons.settings.Prefs
+
+/**
+ * Generates licence name with given ID
+ * @return Name of license
+ */
+fun String.toLicenseName(): Int = when (this) {
+ Prefs.Licenses.CC_BY_3 -> R.string.license_name_cc_by
+ Prefs.Licenses.CC_BY_4 -> R.string.license_name_cc_by_four
+ Prefs.Licenses.CC_BY_SA_3 -> R.string.license_name_cc_by_sa
+ Prefs.Licenses.CC_BY_SA_4 -> R.string.license_name_cc_by_sa_four
+ Prefs.Licenses.CC0 -> R.string.license_name_cc0
+ else -> throw IllegalStateException("Unrecognized license value: $this")
+}
+
+/**
+ * Generates license url with given ID
+ * @return Url of license
+ */
+fun String.toLicenseUrl(): String = when (this) {
+ Prefs.Licenses.CC_BY_3 -> "https://creativecommons.org/licenses/by/3.0/"
+ Prefs.Licenses.CC_BY_4 -> "https://creativecommons.org/licenses/by/4.0/"
+ Prefs.Licenses.CC_BY_SA_3 -> "https://creativecommons.org/licenses/by-sa/3.0/"
+ Prefs.Licenses.CC_BY_SA_4 -> "https://creativecommons.org/licenses/by-sa/4.0/"
+ Prefs.Licenses.CC0 -> "https://creativecommons.org/publicdomain/zero/1.0/"
+ else -> throw IllegalStateException("Unrecognized license value: $this")
+}
+
diff --git a/app/src/main/java/fr/free/nrw/commons/utils/Monuments.kt b/app/src/main/java/fr/free/nrw/commons/utils/Monuments.kt
new file mode 100644
index 0000000000..d5f5736f56
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/utils/Monuments.kt
@@ -0,0 +1,39 @@
+package fr.free.nrw.commons.utils
+
+import java.util.Calendar
+import java.util.Date
+
+/**
+ * Get the start date of wlm monument
+ * For this release we are hardcoding it to be 1st September
+ * @return
+ */
+const val wLMStartDate: String = "1 Sep"
+
+/***
+ * Get the end date of wlm monument
+ * For this release we are hardcoding it to be 31st October
+ * @return
+ */
+const val wLMEndDate: String = "30 Sep"
+
+/**
+ * For now we are enabling the monuments only when the date lies between 1 Sept & 31 OCt
+ */
+val isMonumentsEnabled: Boolean
+ get() = Date().month == 8
+
+/***
+ * Function to get the current WLM year
+ * It increments at the start of September in line with the other WLM functions
+ * (No consideration of locales for now)
+ * @param calendar
+ * @return
+ */
+fun getWikiLovesMonumentsYear(calendar: Calendar): Int {
+ var year = calendar[Calendar.YEAR]
+ if (calendar[Calendar.MONTH] < Calendar.SEPTEMBER) {
+ year -= 1
+ }
+ return year
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/utils/UnderlineUtils.kt b/app/src/main/java/fr/free/nrw/commons/utils/UnderlineUtils.kt
new file mode 100644
index 0000000000..75760d4aba
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/utils/UnderlineUtils.kt
@@ -0,0 +1,19 @@
+package fr.free.nrw.commons.utils
+
+import android.widget.TextView
+import androidx.core.text.buildSpannedString
+import androidx.core.text.underline
+
+object UnderlineUtils {
+ // Convenience method for Java usages - remove when those classes are converted
+ @JvmStatic
+ fun setUnderlinedText(textView: TextView, stringResourceName: Int) {
+ textView.setUnderlinedText(stringResourceName)
+ }
+}
+
+fun TextView.setUnderlinedText(stringResourceName: Int) {
+ text = buildSpannedString {
+ underline { append(context.getString(stringResourceName)) }
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/utils/UrlUtils.kt b/app/src/main/java/fr/free/nrw/commons/utils/UrlUtils.kt
new file mode 100644
index 0000000000..4843cf0aa1
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/utils/UrlUtils.kt
@@ -0,0 +1,33 @@
+package fr.free.nrw.commons.utils
+
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import androidx.browser.customtabs.CustomTabColorSchemeParams
+import androidx.browser.customtabs.CustomTabsIntent
+import androidx.core.content.ContextCompat
+import fr.free.nrw.commons.R
+import timber.log.Timber
+
+/**
+ * Opens Custom Tab Activity with in-app browser for the specified URL.
+ * Launches intent for web URL
+ */
+fun handleWebUrl(context: Context, url: Uri) {
+ Timber.d("Launching web url %s", url.toString())
+
+ val color = CustomTabColorSchemeParams.Builder()
+ .setToolbarColor(ContextCompat.getColor(context, R.color.primaryColor))
+ .setSecondaryToolbarColor(ContextCompat.getColor(context, R.color.primaryDarkColor))
+ .build()
+
+ val customTabsIntent = CustomTabsIntent.Builder()
+ .setDefaultColorSchemeParams(color)
+ .setExitAnimations(
+ context, android.R.anim.slide_in_left, android.R.anim.slide_out_right
+ ).build()
+
+ // Clear previous browser tasks, so that back/exit buttons work as intended.
+ customTabsIntent.intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ customTabsIntent.launchUrl(context, url)
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/wikidata/WikidataEditService.kt b/app/src/main/java/fr/free/nrw/commons/wikidata/WikidataEditService.kt
index 0b49c03e90..f4bf230734 100644
--- a/app/src/main/java/fr/free/nrw/commons/wikidata/WikidataEditService.kt
+++ b/app/src/main/java/fr/free/nrw/commons/wikidata/WikidataEditService.kt
@@ -196,13 +196,16 @@ class WikidataEditService @Inject constructor(
return wikidataClient.setClaim(claim, COMMONS_APP_TAG).blockingSingle()
}
- fun handleImageClaimResult(wikidataItem: WikidataItem, revisionId: Long?) {
+ fun handleImageClaimResult(wikidataItem: WikidataItem, revisionId: Long?, p18WasSkipped: Boolean = false) {
if (revisionId != null) {
wikidataEditListener?.onSuccessfulWikidataEdit()
showSuccessToast(wikidataItem.name)
- } else {
+ } else if (!p18WasSkipped) {
Timber.d("Unable to make wiki data edit for entity %s", wikidataItem)
showLongToast(context, context.getString(R.string.wikidata_edit_failure))
+ } else {
+ Timber.d("Wikidata edit skipped for entity %s because P18 already exists", wikidataItem)
+ // No error shown to user, as this is not a failure
}
}
diff --git a/app/src/main/res/layout-land/activity_login.xml b/app/src/main/res/layout-land/activity_login.xml
index b9adfd033f..fa36b56fff 100644
--- a/app/src/main/res/layout-land/activity_login.xml
+++ b/app/src/main/res/layout-land/activity_login.xml
@@ -125,7 +125,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/password"
- android:imeOptions="flagNoExtractUi"
+ android:imeOptions="actionNext"
android:inputType="textPassword" />
@@ -148,9 +148,9 @@
android:id="@+id/login_two_factor"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:hint="@string/_2fa_code"
- android:imeOptions="flagNoExtractUi"
+ android:imeOptions="actionDone"
android:inputType="number"
+ android:maxLines="1"
android:visibility="gone"
tools:visibility="visible" />
diff --git a/app/src/main/res/layout-xlarge/activity_login.xml b/app/src/main/res/layout-xlarge/activity_login.xml
index c255aa45f3..74f89228eb 100644
--- a/app/src/main/res/layout-xlarge/activity_login.xml
+++ b/app/src/main/res/layout-xlarge/activity_login.xml
@@ -128,7 +128,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/password"
- android:imeOptions="flagNoExtractUi"
+ android:imeOptions="actionNext"
android:inputType="textPassword" />
@@ -151,9 +151,9 @@
android:id="@+id/login_two_factor"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:hint="@string/_2fa_code"
- android:imeOptions="flagNoExtractUi"
+ android:imeOptions="actionDone"
android:inputType="number"
+ android:maxLines="1"
android:visibility="gone"
tools:visibility="visible" />
diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml
index 0da9f5d9fc..1cdfce8ae0 100644
--- a/app/src/main/res/layout/activity_login.xml
+++ b/app/src/main/res/layout/activity_login.xml
@@ -131,7 +131,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/password"
- android:imeOptions="flagNoExtractUi"
+ android:imeOptions="actionNext"
android:inputType="textPassword" />
@@ -155,7 +155,9 @@
android:id="@+id/login_two_factor"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:imeOptions="flagNoExtractUi"
+ android:imeOptions="actionDone"
+ android:inputType="number"
+ android:maxLines="1"
android:visibility="gone"
tools:visibility="visible" />
diff --git a/app/src/main/res/layout/fragment_media_detail.xml b/app/src/main/res/layout/fragment_media_detail.xml
index 7ce90d19e6..52afe3fc30 100644
--- a/app/src/main/res/layout/fragment_media_detail.xml
+++ b/app/src/main/res/layout/fragment_media_detail.xml
@@ -125,27 +125,6 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
+
Ek het besef dat dit sleg is vir my privaatheid
Ek het van plan verander, ek wil nie hê dit moet meer in die openbaar sigbaar wees nie
Jammer, hierdie foto is nie interessant vir \'n ensiklopedie nie
- Opgelaai deur myself op %1$s, gebruik in %2$d artikel(s).
+ Opgelaai deur myself op %1$s, gebruik in %2$d artikel(s).
Welkom by Commons!\n\nLaai u eerste media op deur op die byvoegknoppie te tik.
Geen kategorieë gekies nie
Beelde sonder kategorieë is selde bruikbaar. Is u seker dat u wil indien sonder om kategorieë te kies?
diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml
index 5025b94cb4..6530ab5d55 100644
--- a/app/src/main/res/values-ar/strings.xml
+++ b/app/src/main/res/values-ar/strings.xml
@@ -426,7 +426,8 @@
الصور المختارة
صور عبر \"الأماكن المجاورة\"
المستوى %d
- %s (المستوى %s)
+ %s (المستوى %s)
+ %s (%s)
الصور المرفوعة
لم يتم إرجاع الصور
الصور المستخدمة
@@ -469,7 +470,7 @@
أدركت أنه سيئ لخصوصيتي
لقد غيرت رأيي، ولا أريد أن يكون مرئيا للجميع بعد الآن
آسف هذه الصورة ليست مثيرة للاهتمام لموسوعة
- رفعته بنفسي في %1$s، مستخدم في %2$d مقالة(ات).
+ تم تحميله بواسطة نفسي على %1$s، وتم استخدامه في %2$d مقالة على الأقل.
مرحبا بك في كومنز! \n\nارفع الوسائط الأولى عن طريق النقر على زر الإضافة.
لا توجد تصنيفات مختارة
نادرًا ما تكون الصور الخالية من التصنيفات قابلة للاستخدام، هل أنت متأكد أنك تريد الإرسال دون تحديد التصنيفات؟
@@ -642,6 +643,8 @@
وسائل الإعلام
تصنيف الأطفال
تصنيف أولياء الأمور
+ الفئات الفرعية
+ فئات الوالدين
تم العثور على مكان قريب
هل هذه الصور لـ %1$s ؟
هل هذه صورة %1$s ؟
@@ -820,7 +823,7 @@
الإذن مطلوب لهذه الوظيفة
تعلم كيفية كتابة وصف مفيد
تعلم كيفية كتابة تعليق مفيد
- شاهد إنجازاتك
+ شاهد إنجازاتك
تعديل الصورة
تعديل الموقع
تم تحديث الموقع!
@@ -893,4 +896,5 @@
عرض في المناطق القريبة
تم الإنشاء والتحميل بواسطة: %1$s
تم إنشاؤه بواسطة %1$s وتم تحميله بواسطة %2$s
+ مرشح للحذف
diff --git a/app/src/main/res/values-ast/strings.xml b/app/src/main/res/values-ast/strings.xml
index 8d0dba79af..591c213951 100644
--- a/app/src/main/res/values-ast/strings.xml
+++ b/app/src/main/res/values-ast/strings.xml
@@ -395,7 +395,7 @@
Decatéme que ye malo pa la mio privacidá
Cambié d\'opinión, nun quiero que siga siendo visible públicamente
Sentímoslo, esta imaxe nun ye interesante pa una enciclopedia
- Xubíla yo mesmu\'l %1$s, usada en %2$d artículu(os).
+ Xubíla yo mesmu\'l %1$s, usada en %2$d artículu(os).
¡Afáyate\'n Commons.\n\nCarga\'l to primer ficheru tocando nel botón amestar.
Nun hai categoríes seleicionaes
Les imáxenes ensin categoríes raramente puen usase. ¿Seguro que quies siguir ensin escoyer categoría dala?
diff --git a/app/src/main/res/values-az/strings.xml b/app/src/main/res/values-az/strings.xml
index 1662c23015..c516c53d42 100644
--- a/app/src/main/res/values-az/strings.xml
+++ b/app/src/main/res/values-az/strings.xml
@@ -44,30 +44,63 @@
- (%1$d)
Yükləmələrə Başlanılır
+
+ - %d yükləmə emal edilir
+ - %d yükləmə emal edilir
+
+
+ - %d yükləmə
+ - %d yükləmə
+
+
+ - Bu şəkil %1$s lisenziyası altında yayımlanacaq
+ - Bu şəkillər %1$s lisenziyası altında yayımlanacaq
+
+
+ - %1$d yükləmə
+ - %1$d yükləmə
+
+
+ - Paylaşılan məzmun qəbul edilir. Şəklin emalı şəklin ölçüsündən və cihazınızdan asılı olaraq bir az vaxt apara bilər
+ - Paylaşılan məzmun qəbul edilir. Şəkillərin emalı şəklin ölçüsündən və cihazınızdan asılı olaraq bir az vaxt apara bilər
+
+ Kəşf et
+ Görünüş
Ümumi
+ Rəy
Məxfilik
Vikianbar
Tənzimləmələr
+ Vikianbara yüklə
+ Yükləmə davam edir
İstifadəçi adı
Parol
+ Commons Beta hesabınıza daxil olun
Daxil ol
+ Şifrəni unutmusunuz?
Qeydiyyatdan keç
Giriş edilir
Zəhmət olmasa, gözləyin…
+ Başlıqların və təsvirlərin yenilənməsi
+ Zəhmət olmasa, gözləyin…
Uğurlu giriş!
Giriş baş tutmadı!
Fayl tapılmadı. Xahiş edirik başqa bir fayl üzərində cəhd edin.
Maksimum təkrar cəhd limitinə çatdınız! Zəhmət olmasa, yükləməni ləğv edin və yenidən cəhd edin
Batareya optimallaşdırılması söndürülsün?
- Doğrulama alınmadı, xahiş edirəm yenidən daxil olun
+ 3-dən çox şəklin yüklənməsi batareyanın optimallaşdırılması söndürüldükdə daha yaxşı işləyir. Rahat yükləmə təcrübəsi üçün Vikianbar tətbiqi üçün batareyanın optimallaşdırılmasını parametrlərdən söndürün. \n\nBatareyanın optimallaşdırılmasını söndürmək üçün mümkün addımlar:\n\nAddım 1: Aşağıdakı \"Parametrlər\" düyməsinə toxunun.\n\nAddım 2: \"Optimallaşdırılmayıb\"dan \"Bütün proqramlar\"a keçin.\n\nAddım 3: \"Commons\" və ya \"fr.free.nrw.commons\" axtarın.\n\nAddım 4: \"Optimallaşdırma\"nı seçin.\n\nAddım 5: \'Bitti\' düyməsini basın.
+ Doğrulama alınmadı. Zəhmət olmasa, yenidən daxil olun
Yükləmə başladı!
+ Yükləmə növbədə (məhdud əlaqə rejimi aktivdir)
%1$s yükləndi!
Yükləmənizə baxmaq üçün toxunun
- %1$s yüklənməsi başlanır
+ Fayl yüklənir: %s
%1$s yüklənir
%1$s yüklənməsi başa çatdı
- %1$s faylının yüklənməsi alınmadı
+ %1$s yükləmək alınmadı
+ %1$s yüklənməsi dayandırıldı
Baxmaq üçün toxun
+ Baxmaq üçün toxun
Son Yükləmələrim
Növbəyə alındı
Uğursuz
@@ -77,49 +110,291 @@
Foto çək
Yaxınlıqdakılar
Yükləmələrim
+ Keçidi kopiyala
+ Link kopyalandı.
Paylaş
+ Faylın səhifəsinə bax
Başlıq (Zəruri)
+ Zəhmət olmasa, bu fayl üçün başlıq təqdim et
Açıqlama
- Daxil olmaq olmur — şəbəkə xətası
+ Başlıq
+ Daxil olmaq mümkün deyil - şəbəkə xətası
Çox sayda uğursuz daxil olma. Xahiş edirik bir neçə dəqiqə sonra yenidən cəhd edin.
Bağışlayın, bu istifadəçi Vikianbardan bloklanıb
Siz iki faktorlu autentifikasiya kodunuzu təqdim etməlisiniz.
- Daxil olma uğursuz oldu
+ E-poçt ünvanınıza giriş doğrulama kodu göndərildi. Daxil olmaq üçün kodu daxil edin.
+ Daxil olma uğursuz oldu
Yüklə
Bu dəsti adlandırın
Dəyişikliklər
Yüklə
Kateqoriyalarda axtar
+ Medianızın təsvir etdiyi elementləri axtarın (dağ, Tac Mahal və s.)
Yadda saxla
Yenilə
Siyahı
(Hələ yükləmə yoxdur)
%1$s axtarışına uyğun kateqoriya tapılmadı
+ %1$s uyğun heç bir Vikidata elementi tapılmadı
+ %1$s alt sinifləri yoxdur
+ %1$s üst sinfi yoxdur
+ Şəkillərinizi Vikianbarda daha tapıla bilən etmək üçün kateqoriyalar əlavə edin.\nKateqoriyalar əlavə etmək üçün yazmağa başlayın.
Kateqoriyalar
Nizamlamalar
Qeydiyyatdan keç
Seçilmiş şəkillər
+ Xüsusi Seçici
Kateqoriya
+ Yoxlanış
Haqqında
+ Vikianbar proqramı Vikimedia icmasının qrant alanları və könüllüləri tərəfindən yaradılmış və dəstəklənən açıq mənbəli proqramdır. Vikimedia Fondu proqramın yaradılması, inkişafı və ya texniki xidmətində iştirak etmir.
+ Xəta hesabatları və təkliflər üçün yeni <a href=\"%1$s\">GitHub sorğusu</a> yaradın.
Məxfilik siyasəti
+ Töhfə verənlər
Haqqında
+ Rəy göndər (e-poçt vasitəsilə)
+ Heç bir e-poçt tətbiqi quraşdırılmayıb
+ Son istifadə olunan kateqoriyalar
+ İlk sinxronizasiya gözlənilir…
+ Siz hələ heç bir şəkil yükləməmisiniz.
Yenidən cəhd edin
+ İmtina
+ Bu şəkli yükləməklə bildirirəm ki, bu mənim şəxsi işimdir, onda müəllif hüququ ilə qorunan material və ya selfilər yoxdur və <a href=\"https://commons.wikimedia.org/wiki/Commons:Policies_and_guidelines\">Vikianbar qaydalarına</a> riayət edir.
Endir
+ Defolt lisenziya
+ Əvvəlki başlıq və təsvirdən istifadə et
+ Tema
CC BY 3.0
+ Vikianbar Vikipediyada istifadə olunan şəkillərin əksəriyyətinə ev sahibliyi edir.
+ Şəkilləriniz bütün dünyada insanları maarifləndirməyə kömək edir!
+ Tamamilə özünüz tərəfindən çəkilmiş və ya yaradılmış şəkilləri yükləyin:
+ Təbii obyektlər (çiçəklər, heyvanlar, dağlar)
+ Faydalı obyektlər (velosipedlər, qatar stansiyaları)
+ Məşhur insanlar (icra başçınız, tanış olduğunuz olimpiya idmançıları)
+ Zəhmət olmasa, YÜKLƏMƏYİN:
+ Selfi və ya dostlarınızın şəkilləri
+ İnternetdə tapdığınız şəkillər
+ Özəl proqramların ekran görüntüləri
+ Yükləmə nümunəsi:
+ Başlıq: Sidney Opera Evi
+ Təsvir: Sidney Opera Evinin körfəzdən görünüşü
+ Kateqoriyalar: Sidney Opera Evi Qərb istiqamətindən, Sidney Opera Evinin uzaqdan mənzərələri
+ Şəkilləriniz ilə töhfə verin. Vikipediya məqalələrinin canlanmasına kömək edin!
+ Vikipediyadakı şəkillər Vikianbardan götürülüb.
+ Şəkilləriniz bütün dünyada insanları maarifləndirməyə kömək edir.
+ İnternetdən tapdığınız müəllif hüquqları ilə qorunan materialları, eləcə də afişaların, kitab üzlüklərinin və s. şəkillərini yükləməkdən çəkinin.
+ Sizcə bunu başa düşdünüz?
+ Bəli!
Əlavə məlumat
Kateqoriyalar
Yüklənir...
Heç biri seçilməmişdir
+ Başlıq yoxdur
+ Təsvir yoxdur
+ Müzakirə yoxdur
Naməlum lisenziya
Yenilə
+ Yaddaşa giriş icazəsi tələb olunur
+ Tələb olunan icazə: Xarici yaddaşı oxumaq. Bu olmadan proqram qalereyanıza daxil ola bilməz.
+ Tələb olunan icazə: Xarici yaddaşa yazmaq. Bu olmadan proqram kameranıza/qalereyanıza daxil ola bilməz.
+ Məkan icazəsi tələb olunur
+ Tətbiqdaxili çəkilişlər üçün məkanı qeyd et
+ Cihaz kamerası onu qeyd etmədiyi halda, məkanı tətbiqdaxili çəkilişlərlə əlavə etmək üçün bunu aktiv edin
+ Oldu
+ Xəbərdarlıq
+ Dublikat fayl adı tapıldı
+ Yüklə
Bəli
Xeyr
+ Başlıq
Başlıq
+ Təsvirlər
+ Təsvir
Müzakirə
Müəllif
+ Yüklənmə tarixi
Lisenziya
+ Koordinatlar
+ Heç biri göstərilməyib
+ Beta Tester ol
+ Google Play-də beta kanalımıza qoşulun və yeni funksiyalara və xəta həllərinə erkən giriş əldə edin
+ 2FA kodu
+ E-poçt doğrulama kodu
+ Həqiqətən çıxış etmək istəyirsiniz?
+ Media şəkli uğursuz oldu
+ Heç bir alt kateqoriya tapılmadı
+ Heç bir üst kateqoriya tapılmadı
+ Zao dağı
+ Lamalar
+ Göy qurşağı körpüsü
+ Lalə
+ Vikipediyaya xoş gəlmisiniz
+ Müəlliflik hüquqlarına xoş gəlmisiniz
+ Sidney Opera Evi
+ İmtina
+ Aç
+ Bağla
+ Ana səhifə
+ Yüklə
+ Yaxınlıqdakılar
+ Haqqında
+ Parametrlər
+ Rəy
+ GitHub vasitəsilə rəy
+ Çıxış
+ Təlimat
+ Bildirişlər
+ Yoxla
+ təsvir tapılmadı
+ Commons fayl səhifəsi
+ Vikidata elementi
+ Vikipediya məqaləsi
+ Zəhmət olmasa, medianı mümkün qədər təsvir edin: Harada çəkilib? Nəyi təsvir edir? Kontekst nədir? Obyektləri və ya şəxsləri təsvir edin. Asanlıqla təxmin edilə bilməyən məlumatları, məsələn, bir mənzərədirsə, günün saatını qeyd edin. Əgər media qeyri-adi bir şey göstərirsə, zəhmət olmasa, onu izah edin.
+ Zəhmət olmasa şəklin qısa təsvirini yazın. İlk başlıq şəkil üçün Başlıq kimi istifadə olunacaq. 255 simvolla məhdudlaşır.
+ Bu şəkil ilə bağlı potensial problemlər:
+ Şəkil çox qaranlıqdır.
+ Şəkil bulanıqdır.
+ Şəkil artıq Vikianbarda var.
+ Bu şəkil başqa yerdə çəkilib.
+ Zəhmət olmasa, yalnız özünüz çəkdiyiniz şəkilləri yükləyin. Başqalarının Facebook hesablarında tapdığınız şəkilləri yükləməyin.
+ Hələ də bu şəkli yükləmək istəyirsiniz?
+ Bağlantı xətası
+ Yükləmə prosesi aktiv internetə çıxış tələb edir. Şəbəkə bağlantınızı yoxlayın.
+ Şəkildə tapılan problemlər
+ Zəhmət olmasa, yalnız özünüz çəkdiyiniz şəkilləri yükləyin. İnternetdən tapdığınız şəkilləri yükləməyin.
+ Tətbiqdaxili kadrları yadda saxla
+ Tətbiqdaxili kamera ilə çəkilmiş şəkilləri cihazın yaddaşında saxla
+ Hesabınıza daxil olun
+ Jurnal faylını göndərin
+ Proqramla bağlı problemlərin aradan qaldırılmasına kömək etmək üçün jurnal faylını tərtibatçılara e-poçt vasitəsilə göndərin. Qeyd: jurnallar potensial olaraq şəxsi məlumatları ehtiva edə bilər
+ URL-i açmaq üçün heç bir veb brauzer tapılmadı
+ Xəta! URL tapılmadı
+ Silinməyə namizəd göstər
+ Bu şəkil silinməyə namizəddir.
+ Ətraflı məlumat üçün veb səhifəsinə bax
+ Ötür
+ Daxil ol
+ Daxil olma mərhələsini keçmək istədiyinizə əminsiniz?
+ Gələcəkdə şəkilləri yükləmək üçün daxil olmalısınız.
+ Bu funksiyadan istifadə etmək üçün zəhmət olmasa daxil olun
+ Vikimətni mübadilə buferinə köçür
+ Vikimətn mübadilə buferinə köçürüldü
+ Yaxınlıqdakılar düzgün işləməyə bilər, məkan əlçatan deyil.
+ İnternet əlçatan deyil. Yalnız keşlənmiş yerlər göstərilir.
+ Məkan girişi rədd edildi. Bu funksiyadan istifadə etmək üçün yerinizi manual təyin edin.
+ Yaxınlıqdakı yerlərin siyahısını göstərmək üçün icazə tələb olunur
+ Yaxınlıqdakı şəkillərin siyahısını göstərmək üçün icazə tələb olunur
+ İstiqamətlər
Vikidata
Vikipediya
+ Vikianbar
+ Bizi qiymətləndir
+ TSS
+ İstifadəçi təlimatı
+ Təlimatı ötür
+ İnternet əlçatan deyil
+ Bildirişləri əldə edərkən xəta baş verdi
+ Şəkli nəzərdən keçirmək üçün gətirilərkən xəta baş verdi. Yenidən cəhd etmək üçün yeniləmə düyməsini basın.
+ Heç bir bildiriş tapılmadı
+ Tərcümə et
+ Dillər
+ Tərcümələri təqdim etmək istədiyiniz dili seçin
+ Davam et
+ İmtina
+ Yenidən cəhd et
+ Bunlar sizə yaxın yerlərdir və onların Vikipediya məqalələrini təsvir etmək üçün şəkillərə ehtiyac duyurlar.\n\n\'BU SAHƏDƏ AXTAR\' düyməsinə klikləməklə, xəritə kilidləyib həmin məkan ətrafında yaxınlıqda axtarış başlaya bilərsiniz.
+ Bu yerə şəkil lazımdır.
+ Bu yerin artıq şəkli var.
+ Bu yer artıq mövcud deyil.
+ Şəkil tapılmadı!
+ Şəkillər yüklənərkən xəta baş verdi.
+ %1$s tərəfindən yüklənilib
+ Bloklanıb
+ Sizə Vikianbarı redaktə etmək qadağan edilib
+ Günün Şəkli
+ Axtar
+ Vikianbarda axtar
+ Axtar
+ Son axtarışlar:
+ Bu yaxınlarda axtarılanlar
+ Son dil sorğuları
+ Kateqoriyalar yüklənərkən xəta baş verdi.
+ Təsvirləri yükləyərkən xəta baş verdi.
+ Media
+ Kateqoriyalar
+ Elementlər
+ Seçilmiş
+ Mobil tətbiq vasitəsilə yüklənib
+ Xəritə
+ Şəkil Vikidatada %1$s elementinə əlavə edildi
+ Müvafiq Vikidata obyektini yeniləmək alınmadı!
+ Divar kağızı kimi təyin et
+ Divar kağızı uğurla təyin edildi!
+ Sorğu
+ Bu şəkli yükləmək olar?
+ Sual
+ Nəticə
+ Silinməyi tələb edilən şəkilləri yükləməyə davam etsəniz, hesabınız blok olunacaq. Sorğunu bitirmək istədiyinizə əminsiniz?
+ Yüklədiyiniz şəkillərin %1$s+ ədədi silinib. Silinməyi tələb edilən şəkilləri yükləməyə davam etsəniz, hesabınız blok olunacaq.\n\nTəlimata yenidən baxmaq və sonra hansı növ şəkilləri yükləməli və ya yükləməməli olduğunuzu öyrənməyə kömək etmək üçün testdən keçmək istəyirsiniz?
+ Selfielərin o qədər də məlumatlandırıcı dəyəri yoxdur. Haqqınızda Vikipediya məqaləsi yoxdursa, zəhmət olmasa, şəklinizi yükləməyin.
+ Abidələrin və mənzərələrin şəkillərini əksər ölkələrdə yükləmək olar. Nəzərə alın ki, xaricdəki müvəqqəti incəsənət abidələri tez-tez müəllif hüquqları ilə qorunur və yükləmək düzgün deyil.
+ Veb saytların skrinşotları törəmə əsərlər hesab edilir və veb-saytdakı müəllif hüququna tabedir. Bunlar müəllifin icazəsindən sonra istifadə edilə bilər. Belə icazə olmadan, onların işi əsasında yaratdığınız hər hansı əsər qanuni olaraq orijinal müəllifə məxsus lisenziyasız nüsxə sayılır.
+ Vikianbarın məqsədlərindən biri keyfiyyətli şəkillər toplamaqdır. Ona görə də bulanıq şəkillər yüklənməməlidir. Həmişə yaxşı işıqlandırma ilə gözəl şəkillər çəkməyə çalışın.
+ Texnologiya və ya mədəniyyəti təsvir edən şəkillər üçün Vikianbarda həmişə yer var.
+ Cavabların %1$s ədədi düzgündür. Təbriklər!
+ Suala cavab vermək üçün iki variantdan birini seçin
+ Giriş müddəti bitib. Zəhmət olmasa, yenidən daxil olun.
+ Bunu dostlarınızla paylaşın!
+ Davam et
+ Düzgün Cavab
+ Yanlış Cavab
+ Bu skrinşotu yükləmək olar?
+ Proqramı paylaş
+ Fırlat
+ Yaxınlıqdakı yerləri yükləmək mümkün olmadı
+ Bu ərazidə şəkillər yoxdur
+ Yaxınlıqda yer tapılmadı
+ Yaxınlıqdakı abidələri əldə edərkən xəta baş verdi.
+ Son axtarışlar yoxdur
+ Axtarış tarixçəsini silmək istədiyindən əminsiniz?
+ Bu yükləməni ləğv etmək istədiyinizə əminsiniz?
+ Bu axtarışı silmək istəyirsiniz?
+ Axtarış tarixçəsi silindi
+ Silinməyə namizəd göstər
+ Sil
+ Nailiyyətlər
+ Profil
+ Rozetlər
+ Statistika
+ Qəbul edilən təşəkkürlər
+ Seçilmiş şəkillər
+ \"Yaxınlıqdakı yerlər\" vasitəsilə şəkillər
+ Səviyyə %d
+ %s (Səviyyə %s)
+ Yüklənən şəkillər
+ Geri qaytarılan şəkillər
+ İstifadə olunan şəkillər
+ Nailiyyətlərinizi dostlarınızla paylaşın!
+ Bu tələbləri yerinə yetirdikcə səviyyəniz yüksəlir. \"Statistika\" bölməsindəki elementlər sizin səviyyənizi nəzərə almır.
+ minimum tələb olunur:
+ İstənilən yükləmə proqramı vasitəsilə Vikianbara yüklədiyiniz şəkillərin sayı
+ Vikianbara yüklədiyiniz və silinməyən şəkillərin faizi
+ Vikianbara yüklədiyiniz və Vikimedia məqalələrində istifadə olunan şəkillərin sayı
+ Xəta baş verdi!
+ Vikianbar bildirişi
+ Fərdi müəllif adından istifadə et
+ Fotoşəkilləri yükləyərkən istifadəçi adınız əvəzinə fərdi müəllif adından istifadə edin
+ Fərdi müəllif adı
+ Töhfələr
+ Yaxınlıqdakılar
+ Bildirişlər
+ Bildirişlər (oxunmuş)
+ Yaxınlıqdakı bildirişini göstər
+ Şəkillərə ehtiyacı olan ən yaxın yerlər üçün tətbiqdaxili bildiriş göstərin
+ Siyahı
+ Yaddaş icazəsi
+ Şəkilləri yükləmək üçün cihazınızın xarici yaddaşına giriş icazəsi lazımdır.
+ Artıq şəkillərə ehtiyacı olan ən yaxın yeri görməyəcəksiniz. Bununla belə, istəyirsinizsə, bu bildirişi Parametrlərdə yenidən aktivləşdirə bilərsiniz.
Təşəkkür uğurla göndərildi
Təşəkkür göndərilə bilmədi %1$s
Təşəkkür göndərilir: Xəta
diff --git a/app/src/main/res/values-b+sr+Latn/strings.xml b/app/src/main/res/values-b+sr+Latn/strings.xml
index 47e1cb0bdb..459f75852a 100644
--- a/app/src/main/res/values-b+sr+Latn/strings.xml
+++ b/app/src/main/res/values-b+sr+Latn/strings.xml
@@ -394,7 +394,7 @@
Shvatio/la sam da je loša za moju privatnost
Promenio/la sam mišljenje, ne želim da više javno budu vidljive
Ova slika nije zanimljiva za enciklopediju
- Otpremio/la sam na %1$s, koristi se u članku/cima — %2$d.
+ Otpremio/la sam na %1$s, koristi se u članku/cima — %2$d.
Dobro došli na Ostavu!\n\nOtpremite prve medije dodirom dugmeta za dodavanje.
Kategorije nisu izabrane
Slike bez kategorija retko su upotrebljive. Zaista želite da nastavite bez izbora kategorija?
diff --git a/app/src/main/res/values-b+tg+Cyrl/strings.xml b/app/src/main/res/values-b+tg+Cyrl/strings.xml
index 5ab1ab282b..3404183dcc 100644
--- a/app/src/main/res/values-b+tg+Cyrl/strings.xml
+++ b/app/src/main/res/values-b+tg+Cyrl/strings.xml
@@ -1,5 +1,6 @@
Page Facebook de Commons
- Code source Github de Commons
+ Code source de Commons sur Github
Logo de Commons
Site web de Commons
Sélecteur d\'emplacement de sortie
@@ -256,6 +258,7 @@
Description
Discussion
Auteur
+ Téléverseur
Date de téléversement
Licence
Coordonnées
@@ -414,7 +417,8 @@
Images remarquables
Images par « Lieux à proximité »
Niveau %d
- %s (niveau %s)
+ %s (niveau %s)
+ %s (%s)
Images téléversées
Images non annulées
Images utilisées
@@ -457,7 +461,7 @@
J’ai pensé que ce n’était pas bon pour ma vie privée
J’ai changé d\'avis, je ne désire plus que cel soit visible publiquement
Désolé mais cette image n’est pas intéressante pour une encyclopédie
- Téléversé par moi-même le %1$s, utilisé dans %2$d article(s).
+ Téléversé par moi-même le %1$s, utilisé dans au moins %2$d article(s).
Bienvenue sur Commons !\n\nTéléversez votre premier média en tapant sur le bouton Ajouter.
Aucune catégorie sélectionnée
Les images sans catégories sont rarement utilisables. Voulez-vous vraiment continuer sans sélectionner des catégories appropriées ?
@@ -622,6 +626,8 @@
MÉDIA
CLASSES ENFANTS
CLASSES PARENTES
+ SOUS-CATÉGORIES
+ CATÉGORIES PARENTES
Lieu à proximité trouvé
Ces images sont-elles de %1$s ?
Est-ce une image de %1$s ?
@@ -799,7 +805,7 @@
Des autorisations sont nécessaires pour la fonctionnalité
Apprendre à écrire une description utile
Apprendre à écrire une légende utile
- Voir vos réalisations
+ Consultez vos réalisations
Modifier l’image
Modifier l’emplacement
Emplacement mis à jour !
@@ -869,4 +875,5 @@
Afficher à proximité
Crée et publiée par: %1$s
Créé par %1$s et publiée par %2$s
+ Nommé pour suppression
diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml
index 0cf3aed91c..75cb51542f 100644
--- a/app/src/main/res/values-gl/strings.xml
+++ b/app/src/main/res/values-gl/strings.xml
@@ -232,8 +232,9 @@
Acerca de
Configuración
Comentarios
+ Comentarios a través de GitHub
Saír
- Titorial
+ Guía
Notificacións
Revisar
non se atopou descrición
@@ -272,12 +273,12 @@
Precísase permiso para amosar unha lista de lugares preto de aquí
COMO CHEGAR
WIKIDATA
- WIKIPEDIA
+ Wikipedia
COMMONS
Avalíenos
FAQ
Guía de uso
- Saltar titorial
+ Saltar a guía
Internet non dispoñible
Erro ó recuperar as notificacións
Houbo un erro ó recuperar a imaxe a revisar. Prema en refrescar para tentalo de novo.
@@ -319,7 +320,7 @@
Pregunta
Resultado
Se continúa cargando imaxes que requiran ser eliminadas, a súa conta probablemente sexa bloqueada. Está seguro de que quere rematar o cuestionario?
- Máis do %1$s das imaxes que cargou foron eliminadas. Se continúa cargando imaxes que requiran ser borradas, probablemente a súa conta sexa bloqueada.\n\nGustaríalle ver de novo o titorial e facer un pequeno cuestionario que axuda a entender que tipo de imaxes se deben ou non se deben cargar?
+ Máis do %1$s das imaxes que cargou foron eliminadas. Se continúa cargando imaxes que requiran ser borradas, probablemente a súa conta sexa bloqueada.\n\nGustaríalle ver de novo a guía e facer un pequeno cuestionario que axuda a entender que tipo de imaxes se deben ou non se deben cargar?
Os autorretratos non teñen valor enciclopédico abondo. Por favor no cargue imaxes de vostede mesmo salvo que haxa un artigo de Wikipedia sobre vostede.
As fotografías de monumentos e paisaxes de exterior poden ser cargadas na maioría dos países. Teña en conta, por favor, que as instalacións de arte temporais en exteriores normalmente teñen dereitos de autor protexidos e non poden ser cargadas.
As capturas de pantalla de sitios web son consideradas traballos derivados e están suxeitas a dereitos de autor. Poden ser usadas despois de obter a autorización do autor. Sen ese permiso, calquera obra que cree baseada no seu traballo está considerada legalmente como unha copia sen licenza, cuxa propiedade é mantida polo autor orixinal.
@@ -394,7 +395,7 @@
Decateime de que prexudica a miña privacidade
Cambiei de idea, non quero que siga sendo visible de forma pública
Desculpas, esta imaxe non é interesante para unha enciclopedia
- Cargada por min o %1$s, usada en %2$d artigo(s).
+ Cargada por min o %1$s, usada polo menos en %2$d artigo(s).
Dámoslle a benvida ó Commonsǃ\n\nCargue o seu primeiro ficheiro premendo no botón Engadir.
Non hai categorías seleccionadas
As imaxes sen categorías só son utilizables en contadas ocasións. Está seguro de que quere continuar sen seleccionar categorías?
diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml
index dcb769ca10..70e767a518 100644
--- a/app/src/main/res/values-hi/strings.xml
+++ b/app/src/main/res/values-hi/strings.xml
@@ -322,7 +322,7 @@
धन्यवाद प्राप्त किया
निर्वाचित चित्र
स्तर %d
- %s (स्तर %s )
+ %s (स्तर %s )
चित्र अपलोड हुआ
चित्रों को वापस नहीं किया गया
उपयोग हुए चित्र
diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml
index 629312a579..a528058739 100644
--- a/app/src/main/res/values-hu/strings.xml
+++ b/app/src/main/res/values-hu/strings.xml
@@ -362,7 +362,7 @@
Észleltem, hogy ez rossz a magánszférámnak
Meggondoltam magam, nem szeretném, hogy nyilvánosan látható legyen
Sajnos ez a fénykép nem érdekes egy enciklopédia számára
- Feltöltve ekkorː %1$s, %2$d cikkben használva.
+ Feltöltve ekkorː %1$s, %2$d cikkben használva.
Üdvözlünk a Commons-ban. \n\nA hozzáadás gombra koppintva feltöltheted első képedet.
Nincs kiválasztott kategória
A kategória nélküli képek ritkán használhatóak. Biztos, hogy kategória kiválasztása nélkül akarsz továbblépni?
diff --git a/app/src/main/res/values-ia/strings.xml b/app/src/main/res/values-ia/strings.xml
index 386bddf2ed..9c631ec288 100644
--- a/app/src/main/res/values-ia/strings.xml
+++ b/app/src/main/res/values-ia/strings.xml
@@ -206,6 +206,7 @@
Description
Discussion
Autor
+ Incargator
Data de incargamento
Licentia
Coordinatas
@@ -364,7 +365,8 @@
Imagines eminente
Imagines via “Locos a proximitate”
Nivello %d
- %s (Nivello %s)
+ %s (Nivello %s)
+ %s (%s)
Imagines incargate
Imagines non revertite
Imagines usate
@@ -407,7 +409,7 @@
Io ha comprendite que es mal pro mi vita private
Io ha cambiate de idea, io non vole plus que illo sia publicamente visibile
Iste imagine non es interessante pro un encyclopedia
- Incargate per me mesme le %1$s, usate in %2$d articulo(s).
+ Incargate per me mesme le %1$s, usate al minus in %2$d articulo(s).
Benvenite a Commons!\n\nPro incargar tu prime file multimedial, tocca le button Adder.
Necun categoria seligite
Imagines sin categorias es rarmente usabile. Es tu secur de voler continuar sin seliger categorias?
@@ -572,6 +574,8 @@
MULTIMEDIA
CLASSES FILIO
CLASSES GENITOR
+ SUBCATEGORIAS
+ CATEGORIAS GENITOR
Loco a proximitate trovate
Es istes imagines de %1$s?
Es isto un imagine de %1$s?
@@ -747,7 +751,7 @@
Permissiones es necessari pro functionalitate
Apprende como scriber un description utile
Appende como scriber un legenda utile
- Vider tu realisationes
+ Examinar tu realisationes
Modificar imagine
Modificar position
Position actualisate!
@@ -816,4 +820,5 @@
Monstrar in A proximitate
Create e incargate per: %1$s
Create per %1$s e incargate per %2$s
+ Nominate pro deletion
diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml
index e977e56d5b..33ee73b096 100644
--- a/app/src/main/res/values-in/strings.xml
+++ b/app/src/main/res/values-in/strings.xml
@@ -110,17 +110,20 @@
Ambil foto
Sekitar
Unggahan saya
+ Salin pranala
+ Pranala telah disalin ke papan klip
Bagikan
Lihat halaman berkas
Takarir (Wajib)
Berikan takarir untuk berkas ini
Deskripsi
Takarir
- Tidak dapat login - kesalahan pada jaringan
+ Tidak dapat login - kesalahan pada jaringan
Terlalu banyak percobaan masuk yang gagal. Harap coba lagi dalam beberapa menit
Maaf, pengguna ini telah diblokir di Commons
Anda harus memberikan kode otentikasi dua faktor milik Anda
- Gagal masuk log
+ Kode verifikasi masuk log telah dikirim ke alamat surel Anda. Mohon berikan kode untuk masuk log.
+ Gagal masuk log
Unggah
Beri nama set ini
Modifikasi
@@ -179,7 +182,7 @@
Mohon JANGAN diunggah:
Foto narsis atau foto teman Anda
Gambar yang Anda unduh dari Internet
- Cuplikan layar suatu aplikasi
+ Cuplikan layar suatu aplikasi tidak bebas
Contoh unggahan:
Judul: Gedung Opera Sydney
Deskripsi: Gedung Opera Sydney yang dilihat dari seberang teluk
@@ -200,7 +203,7 @@
Lisensi tidak diketahui
Segarkan
Meminta Izin Akses Penyimpanan
- Perlu Izin: Membaca penyimpanan eksternal. Tanpa hal ini aplikasi tidak dapat mengakses galeri Anda.
+ Perlu Izin: Membaca penyimpanan eksternal. Tanpa izin ini aplikasi tidak dapat mengakses galeri Anda.
Perlu Izin: Menulis penyimpanan eksternal. Tanpa hal ini aplikasi tidak dapat mengakses kamera/galeri.
Meminta Izin Lokasi
OKE
@@ -351,7 +354,7 @@
Apakah tangkapan layar ini OKE untuk diunggah?
Bagikan Aplikasi
Putar
- Galat saat mengambil tempat terdekat.
+ Tidak dapat memuat tempat-tempat terdekat.
Tidak ada gambar di area ini
Tidak ditemukan tempat yang dekat
Galat saat mengambil monumen terdekat.
@@ -368,7 +371,7 @@
Ucapan terima kasih diterima
Gambar Pilihan
Gambar via \"Tempat di Sekitar\"
- Tingkat
+ Tingkat %d
Gambar Diunggah
Gambar Tidak Dikembalikan
Gambar Digunakan
@@ -410,7 +413,7 @@
Saya menyadari itu buruk untuk privasi saya
Saya berubah pikiran, saya tidak ingin itu terlihat publik lagi
Maaf gambar ini tidak menarik untuk ensiklopedia
- Diunggah saya sendiri pada %1$s, digunakan dalam %2$d artikel.
+ Diunggah saya sendiri pada %1$s, digunakan paling tidak dalam %2$d artikel.
Unggah media pertama Anda dengan mengetuk tombol.
Tidak ada Kategori yang Dipilih
Gambar tanpa kategori jarang dapat digunakan. Apakah Anda yakin ingin mengirim tanpa memilih kategori?
@@ -427,7 +430,7 @@
Jangan pernah menanyakan ini lagi
Tanya untuk izin lokasi
Minta izin lokasi ketika diperlukan untuk fitur tampilan kartu pemberitahuan sekitar.
- Terjadi kesalahan. Kami tidak dapat mengambil pencapaian Anda
+ Terjadi kesalahan. Kami tidak dapat mengambil pencapaian Anda
Anda telah membuat begitu banyak kontribusi sehingga sistem perhitungan pencapaian kami tidak dapat menanggulanginya. Ini adalah pencapaian yang tertinggi.
Berakhir pada:
Sinahang penyobyahan
@@ -435,7 +438,7 @@
Izinkan
Tutup
Anda tidak akan melihat kampanye ini lagi. Namun, Anda bisa mengaktifkan kembali pemberitahuan ini di Pengaturan jika diinginkan.
- Fungsi ini membutuhkan hubungan jaringan, tolong periksa pengaturan jaringan Anda.
+ Fungsi ini memerlukan hubungan jaringan. Tolong periksa pengaturan jaringan Anda.
Terjadi masalah saat memproses gambar. Harap mengulang lagi.
Dapatkan token untuk menyunting
Menambahkan templat untuk pemeriksaan kategori
@@ -543,7 +546,7 @@
Tidak bisa menambahkan koordinat.
Tidak bisa menambahkan deskripsi.
Tidak bisa menambahkan takarir.
- Tidak bisa mendapatkan koordinat.
+ Koordinat gambar tidak diperbarui
Tidak bisa memperoleh deskripsi.
Sunting deskripsi dan takarir
Bagikan gambar melalui
@@ -558,7 +561,7 @@
Perlu foto
Jenis tempat:
Jembatas, museum, hotel, dll.
- Terjadi kesalahan ketika masuk log, Anda perlu mengubah kata sandi Anda !!
+ Terjadi kesalahan ketika masuk log. Anda perlu mengatur ulang kata sandi Anda !!
MEDIA
KELAS ANAK
KELAS INDUK
@@ -578,7 +581,7 @@
Untuk hasil terbaik, pilih mode Akurasi Tinggi.
Nyalakan lokasi?
Tempat sekitar perlu lokasi yang diaktifkan agar bekerja dengan benar
- Anda perlu memberikan akses ke lokasi Anda saat ini untuk mengatur lokasi secara otomatis.
+ Anda perlu memberikan izin lokasi Anda saat ini untuk mengatur lokasi secara otomatis.
Apakah Anda menangkap kedua gambar ini di tempat yang sama? Apakah Anda ingin menggunakan lintang/bujur dari gambar yang di kanan?
Muat Lebih Banyak
Tempat tidak ditemukan, coba ubah kriteria pencarian Anda.
@@ -681,7 +684,7 @@
Peta Sekitar harus membaca STATUS TELEPON untuk berfungsi dengan benar
Kontribusi Pengguna: %s
Pencapaian Pengguna: %s
- Lihat halaman pengguna
+ Lihat profil pengguna
Sunting penggambaran
Sunting kategori
Opsi Lanjutan
diff --git a/app/src/main/res/values-io/strings.xml b/app/src/main/res/values-io/strings.xml
index f4802280dd..7c93e4f855 100644
--- a/app/src/main/res/values-io/strings.xml
+++ b/app/src/main/res/values-io/strings.xml
@@ -209,6 +209,7 @@
Deskripto
Diskuto
Autoro
+ Adkarganto
Dato sendita
Licenco
Koordinati
@@ -367,7 +368,8 @@
Remarkinda imaji
Imaji tra \"Loki Vicina\"
Nivelo %d
- %s (Nivelo %s)
+ %s (Nivelo %s)
+ %s (%s)
Imaji sendita
Imaji ne reversionita
Imaji uzita
@@ -410,7 +412,7 @@
Me konstatis ke ol esas mala por mea privateso
Me chanjis mea ideo: me ne pluse deziras ke ol esos publike videbla
Pardonez! Ca imajo ne esas interesanta por ula enciklopedio
- Adjuntita da me, che %1$s, uzita en %2$d artiklo/artikli.
+ Adjuntita da me che %1$s; uzita en adminime %2$d artiklo/artikli.
Bonveno a Commons!\n\nSendez vua unesma arkivo kliktanta sur butono \"adjuntez\" (\'\'add\'\').
Nula kategorio selektita
Imaji sen kategorii rare esas uzebla. Ka vu fakte deziras sendar ol sen selektar irga kategorio?
@@ -436,11 +438,13 @@
Permisar
Eskartar
Voluntez kapabligar registrago di lokizo en \'\'Settings\'\', e probez itere.\n\nNoto: l\'arkivo sendanta povas ne havar informo pri lokizo, se l\'\'\'app\'\' ne povas rekuperar l\'informo pri lokizo en kurta intervalo.
+ La kamero en l\'utensilo bezonas permiso por adjuntar ca informo en imaji, se l\'informo ne esas disponebla che EXIF. Voluntez permisar ke l\'\'\'app\'\' acesez vua lokizo, e probez itere.\n\nAtencez: Esas posibla ke l\'imajo sendonta ne havos informo rekuperebla pri lokizo, se l\'\'\'app\'\' ne povos rekuperor ol pos kurta intervalo.
La programo \'\'app\'\' ne enrejistros informo pri lokizo en la fotografuri pro manko di permisi
Sen kapabligar GPS, l\'enrejistro di la lokizo en la fotografuri ne facesas.
Uzez selektilo di fotografuri segun dokumenti
La nova funciono \'\'Android photo picker\'\' povas perdar informo pri lokizo. Kapabligez ol, se vu semblas uzar ol.
Deskapabliganta ol povos deskuplar la nova funciono \'\'Android photo picker\'\'. Posible perdos informo pri lokizo.
+ Vu ne pluse vidos ta kampanii. Tamen, vu povos itere kapabligar ca avizo en Ajusti (\'\'Settings\'\'), se vu deziros.
Ca funciono bezonas ligilo ad interreto. Verifikez vua ajusti pri konekti.
Eventis eroro dum procesado dil imajo. Voluntez probar ol itere!
Kaptanta \'\'token\'\' por redaktar.
@@ -573,6 +577,8 @@
\'\'MEDIA\'\'
SUBKLASI
KLASI PLU ABSTRAKTA
+ SUB-KATEGORII
+ PRECIPUA KATEGORII
Loko proxima trovesis
Ka ca imaji apartenas a %1$s?
Ka to esas imajo di %1$s?
@@ -636,8 +642,10 @@
Uzita
Mea rango
Kapabligesis por uzar kun limitizita konekti!
+ Posibleso pri uzo kun limitizita konekto deskapabligita. La sendo di arkivi rikomencos nun.
Modo por limitizita retoligilo
Imaji di qualeso
+ Imaji kun qualeso esas diagrami o fotografuri qui havas qualesi (maxim-multa-kaze teknikala) ed esas valoroza por projeti de Wikimedia
Duriganta sendajo...
Pauzanta sendajo...
Nuliganta sendajo...
@@ -670,6 +678,8 @@
Retroirar
Bonveno a personalizita selektilo di imaji
Ecelanta
+ Ca imajo ja sendesis a Commons.
+ Por teknikala motivi, l\'utensilo \'\'app\'\' ne povas fidinde sendar plua kam %1$d pikturi samatempe. La limito %1$d superesis per %2$d.
Eskartar
Maximo: %1$d
Eroro: Limito pri sendajo transpasita
@@ -686,12 +696,17 @@
Redaktar deskripturi
Redaktar kategorii
Progresiva selektaji (advanced options)
+ Vu povas ajustar la demando \"Vicini\" (\'\'Nearby\'\'). Se erori aparos, riadjustez ed aplikez.
Aplikar
Restaurar
Nula lokizo trovita
Ka vu deziras informar la loko de ube vu obtenis ca imajo?\nInformo pri la lokizo helpos editeri trovar vua imajo, do ol divenos plu utila.\nDanko!
Adjuntez lokizo
+ Voluntez removar de ca e-postala mesajo irga informo quan vu ne deziras divenar publika. Anke konciez ke vua e-postal-adreso quan vu esas uzanta, vua nomo e l\'imajo asociita a vu divenos publike videbla.
Detali
+ Sucesi nur esas disponebla en la definitiva versiono. Voluntez verifikar la dokumentigo pri developo.
+ La klasifiko-tabelo nur esas disponebla en la definitiva versiono. Voluntez verifikar la dokumentigo pri developo.
+ Voluntez sendar nur pikturi facita da vu. Senderi di imaji kun autoroyuro ne libera blokusesos. To aplikesas anke por probi \'\'beta\'\'. Danko por probar l\'utensilo \'\'app\'\'!
nivelo di API
versiono di Android
Fabrikanto dil aparato
@@ -705,6 +720,9 @@
Indikez por ne sendar ol
Itere indikez por sendar ol
Indikanta ke ol ne sendesos
+ Indikita kom por ne sendar
+ Montrar imaji ja traktita
+ Celanta imaji ja traktita
Ne trovesis plusa imaji
Ca imajo ja sendesis
Ne povis selektar ca imajo por sendar (\'\'upload\'\')
@@ -719,11 +737,15 @@
Demandar blokuso di ca uzero
Bonveno a selekto di Modo \"tota-skreno\"
Uzez du fingri por augmentar o diminutar \'\'zoom\'\'.
+ Glitez rapide e longe por facar lo sequanta: \n- Sinistre/Dextre: Irar al antea/nexta\n- Adsupre: Selektar\n- Adinfre: Indikez kom ne por sendar.
+ Por establisar l\'avataro di vua klasifiko-tabelo, kliktez \"Uzar kom avataro\" en la 3-punti menuo de irga imajo.
Koordinati ne esas l\'exakta, tamen l\'individuo qua sendis ca imajo kredas ke la koordinati quin lu informis esas suficante proxima.
+ Permiso pri enmagazinigado neaceptata
Ne povis partigar ca arkivo
+ Uzar la funcionado bezonas permisi
Savez quale skribar utila deskripto
Savez quale skribar utila etiketo
- Videz vua sucesi
+ Vidar vua sucesi
Modifikar imajo
Aktualigar lokizo
Lokizo aktualigita!
@@ -734,6 +756,7 @@
Dankar l\'autoro
Eroro sendanta danki al autoro.
La tempo-quanto por vua \'\'log in\'\' finis. Voluntez itere enirar.
+ Nula utensilo \'\'app\'\' disponebla por apertar arkivi GPX
Konservo sucesoza di arkivo
Ka vu deziras apertar arkivo GPX?
Ka vu deziras apartar l\'arkivo KML?
@@ -747,6 +770,7 @@
Diskuto
Dicez irgu pri l\'arkivo \'%1$s\'. Ol esos videbla publike.
+ \'%1$s\' esas en diferanta loko.
Extinganta la tota sendaji...
Arkivi sendita
Vartanta
diff --git a/app/src/main/res/values-is/strings.xml b/app/src/main/res/values-is/strings.xml
index ad215efbce..427e3cec38 100644
--- a/app/src/main/res/values-is/strings.xml
+++ b/app/src/main/res/values-is/strings.xml
@@ -392,7 +392,7 @@
Ég áttaði mig á að þetta væri slæmt fyrir gagnaleynd mína
Ég skipti um skoðun, ég vil ekki að hún sé lengur öllum sýnileg
Afsakið, þessi mynd hefur ekkert gildi fyrir alfræðirit
- Sent inn af mér sjálfum %1$s, notað í %2$d grein(um).
+ Sent inn af mér sjálfum %1$s, notað í %2$d grein(um).
Velkomin í Commons!\n\nSendu inn fyrstu margmiðlunargögnin þín með því að ýta á viðbætingarhnappinn.
Engir flokkar valdir
Myndir án flokka eru sjaldnast nýtilegar. Ertu viss um að þú viljir halda áfram án þess að velja flokka?
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index b923b699b3..db63f7ee84 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -143,6 +143,7 @@
Cerca categorie
Cerca elementi che il tuo file rappresenta (montagna, Taj Mahal, ecc.)
Salva
+ Menu overflow
Aggiorna
Elenco
(Non è stato ancora caricato niente)
@@ -289,7 +290,7 @@
Invia il file di registro agli sviluppatori tramite email per aiutarli a risolvere i problemi con l\'applicazione. Nota: i log potrebbero contenere informazioni di identificazione
Nessun browser web trovato per aprire l\'URL
Errore! URL non trovato
- Proponi per la Cancellazione
+ Proponi per la cancellazione
Questa immagine è stata proposta per la cancellazione.
Vedi la pagina web per i dettagli
Salta
@@ -390,7 +391,8 @@
Immagini in evidenza
Immagini tramite \"Luoghi nelle vicinanze\"
Livello %d
- %s (Livello %s)
+ %s (Livello %s)
+ %s (%s)
Immagini caricate
Immagini non ripristinate
Immagini utilizzate
@@ -433,7 +435,7 @@
Mi sono reso conto che viola la mia privacy
Ho cambiato idea, non voglio più che sia pubblicamente visibile
Spiacente, questa immagine non è interessante per un\'enciclopedia
- Caricato da me stesso il %1$s, utilizzato in %2$d voce/i.
+ Caricato da me stesso il %1$s, utilizzato almeno in %2$d voce/i.
Benvenuto su Commons!\n\nCarica il tuo primo file multimediale toccando il pulsante aggiungi.
Nessuna categoria selezionata
Le immagini senza categorie sono raramente utilizzabili. Sei sicuro di voler continuare senza selezionare le categorie?
@@ -594,6 +596,7 @@
MEDIA
CLASSI FIGLIE
CLASSI SUPERIORI
+ Sottocategorie
Rinvenuto luogo nei pressi
Queste sono immagini di %1$s?
Questa è un\'immagine di %1$s?
@@ -771,7 +774,7 @@
Impara come scrivere una didascalia utile
Vedi i tuoi risultati
Modifica Immagine
- Modifica Posizione
+ Modifica posizione
Posizione aggiornata!
Rimuovi posizione
Rimuovi avviso di posizione
@@ -836,4 +839,5 @@
Mostra nelle vicinanze
Creato e caricato da: %1$s
Creato da %1$s e caricato da %2$s
+ Proposto per la cancellazione
diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml
index 6aecaf1e8b..05f309500b 100644
--- a/app/src/main/res/values-iw/strings.xml
+++ b/app/src/main/res/values-iw/strings.xml
@@ -243,6 +243,7 @@
תיאור
דיון
יוצר
+ מעלה
תאריך העלאה
רישיון
נקודות ציון
@@ -401,7 +402,8 @@
תמונות מומלצות
תמונות דרך \"מקומות בסביבה\"
רמה %d
- %s (רמה %s)
+ %s (רמה %s)
+ %s (%s)
תמונות שהועלו
תמונות שלא שוחזרו
תמונות בשימוש
@@ -444,7 +446,7 @@
הבנתי שזה לא טוב לפרטיות שלי
התחרטתי, אינני רוצה עוד שהיא תוצג לציבור
סליחה, התמונה אינה מעניינת עבור אנציקלופדיה
- הועלה על ידי ב-%1$s, בשימוש ב-%2$d מאמר(ים)
+ הועלה על ידי ב־%1$s, בשימוש ב־%2$d ערכים לפחות.
ברוך בואך לוויקישיתוף!\n\nכדי להעלות את המדיה הראשונה שלך, נא להקיש על כפתור ההוספה.
לא נבחרו קטגוריות
תמונות ללא קטגוריות בדרך כלל אינן שימושיות. להמשיך ללא בחירת קטגוריות?
@@ -613,6 +615,8 @@
מדיה
מחלקות יורשות
מחלקות מורישות
+ תת־קטגוריות
+ קטגוריות הורה
נמצא מקום בסביבה
האם אלו תמונות של %1$s?
האם זאת תמונה של %1$s?
@@ -862,4 +866,5 @@
בתצוגת בסביבה
נוצר והועלה על־ידי: %1$s
נוצר על־ידי %1$s והועלה על־ידי %2$s
+ הועמדה למחיקה
diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml
index 662ddcd238..f051ba27a5 100644
--- a/app/src/main/res/values-ja/strings.xml
+++ b/app/src/main/res/values-ja/strings.xml
@@ -408,7 +408,7 @@
自分のプライバシーを傷つけると気がつきました
以前とは考えが変わりました。今後は公衆の場で自分の画像を公開したくありません
残念ですがこの画像は百科事典の目的に合いません
- 私本人が%1$sにアップロードし、%2$d件の記事で使用されました。
+ 私本人が%1$sにアップロードし、%2$d件の記事で使用されました。
コモンズへようこそ!\n\n追加ボタンを押して、ぜひご自分のメディアの初投稿をしましょう。
カテゴリが選択されていません
カテゴリを指定しない画像は使用されることがほとんどありません。ほんとうにカテゴリを選択しないまま作業を続けますか?
diff --git a/app/src/main/res/values-ka/strings.xml b/app/src/main/res/values-ka/strings.xml
index e560298dd5..e6142ec347 100644
--- a/app/src/main/res/values-ka/strings.xml
+++ b/app/src/main/res/values-ka/strings.xml
@@ -1,5 +1,6 @@
+ ويکيخونديځ خرابشوی
+ اوو. يو څه ناسم پېښ شول!
+ موږ ته ووایاست چې تاسو څه کول غواړئ، بيايې له موږ سره د برېښليک له لارې شريک کړئ. دا به موږ سره د هغې سمولو کې مرسته وکړي!
مننه!
diff --git a/app/src/main/res/values-ps/strings.xml b/app/src/main/res/values-ps/strings.xml
index 6b3970c888..7afe73e953 100644
--- a/app/src/main/res/values-ps/strings.xml
+++ b/app/src/main/res/values-ps/strings.xml
@@ -111,7 +111,7 @@
د دوتنې مخ کتل
نيونگ (اړين دی)
مهرباني وکړئ، د دې دوتنې لپاره نيونگ ورکړئ
- څرگندونه
+ څرگنداوی
نيونگ
غونډال ته ننوتنه ناشونې ده - د جال پاتې راتلنه
ډیری ناکامه هڅې. لطفا څو دقیقې وروسته بیا هڅه وکړئ.
@@ -136,14 +136,14 @@
%1$s مور ټولگې نه لري
ويکياوتوک خونديځ کې د خپلو انځورونو موندلو لپاره وېشنيزې ورگډې کړئ.\nوېشنيزو ورگډولو لپاره ټاپل پيل کړئ.
وېشنيزې
- امستنې
+ اوڼنې
نومليکنه
ټاکلی انځور
دوديز ټاکونکی
وېشنيزه
ملگرو بياکتنه
په اړه
- د ويکيرسنۍ خونديځ کاريال يو پرانيستې سرچينې کاريال دی چې د ويکيرسنۍ ټولنې د بسپنه ورکوونکو او خپلخوښو کارنانو له خوا جوړ شوی او ساتل کېږي. د ويکيرسنۍ بنسټ د دې کاريال په جوړولو، پراختيا او ساتنه کې ښکېل نه دی.
+ د ويکيرسنۍ خونديځ کاريال يو پرانېستې سرچينې کاريال دی چې د ويکيرسنۍ ټولنې د بسپنه ورکوونکو او خپلخوښو کارنانو له خوا جوړ شوی او ساتل کېږي. د ويکيرسنۍ بنسټ د دې کاريال په جوړولو، پراختيا او ساتنه کې ښکېل نه دی.
د بگ راپور او وړانديزونو لپاره يوه <a href=\"%1$s\">گيټ هاب ستونزه</a> جوړه کړئ.
د پټنتيا تگلار
په اړه
@@ -195,32 +195,44 @@
اړينه پرېښولی: بهرنۍ زېرمه ولولئ. کاريال ستاسو انځورتونه ته پرته له دې لاسرسی نشي موندلی.
ښه
گواښنه
+ د دوتنې غبرگونی نوم وموندل شو
راپورتهکول
هو
نه
نيونگ
سرليک
- څرگندونه
+ وييانځوريز استازيتوب
+ څرگنداوی
شننه
ليکوال
+ راپورتهکوونکی
راپورتهکېدلو نېټه
منښتليک
کورډيناټونه
هېڅ نه دي چمتو شوي
ازمېښتي ازمايښتگر شئ
ويکيپېډياښه راغلئ
+ لمېسلرښتې هرکلی
ناگارل
- پرانيستل
+ پرانېستل
تړل
کور
پورته کول
نژدې
په اړه
- امستنې
+ اوڼنې
غبرگون
+ گيټهاب له لارې غبرگون
وتل
+ لارښوونيزې
+ خبرتياوې
بياکتنه
هيڅ څرگنداوی ونهٔ موندل شو
+ ټولگړې دوتنې مخ
+ ويکياومتوک توکی
+ ويکيپېډيا ليکنه
+ انځور ډېر تياره دی.
+ انځور ډېر جړ دی.
تگلوري
ويکياومتوک
ويکيپېډيا
diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml
index 947d65ace6..7f45ab484b 100644
--- a/app/src/main/res/values-pt-rBR/strings.xml
+++ b/app/src/main/res/values-pt-rBR/strings.xml
@@ -427,7 +427,7 @@
Eu percebi que é ruim para minha privacidade
Eu mudei de ideia, eu não quero mais que seja publicamente visível
Desculpe, esta foto não é interessante para uma enciclopédia
- Carregada por mim em %1$s, usada em %2$d artigo(s).
+ Carregada por mim em %1$s, usada em %2$d artigo(s).
Bem-vindo ao Commons!\n\nCarregue sua primeira mídia tocando no botão Adicionar.
Nenhuma categoria selecionada
Imagens sem categorias raramente são utilizáveis. Tem certeza de que deseja continuar sem selecionar categorias?
diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml
index 15c5983c4d..721528c7bf 100644
--- a/app/src/main/res/values-pt/strings.xml
+++ b/app/src/main/res/values-pt/strings.xml
@@ -22,26 +22,26 @@
* Vitorvicentevalente
-->
- Página da wiki Commons no Facebook
- Código-fonte da wiki Commons no Github
- Logótipo da wiki Commons
- Sítio da wiki Commons
+ Página do Facebook da Commons
+ Código Fonte do Github da Commons
+ Logótipo da Commons
+ Site da Web da Commons
Sair do selecionador de localização
- Enviar
+ Submeter
Adicionar outra descrição
Adicionar nova contribuição
Adicionar contribuição da câmara
Adicionar contribuição de fotos
Adicionar contribuição da galeria de contribuições anteriores
Legendas
- Descrição da língua
+ Descrição do Idioma
Legenda
Descrição
Imagem
Todas
Alternar para cima
- Vista de pesquisa
- Estado do local
+ Visualização da Pesquisa
+ Estado do Local
Imagem do Dia
- a carregar %1$d ficheiro
@@ -78,9 +78,9 @@
Comentários
Privacidade
Commons
- Configurações
- Carregar na wiki Commons
- Carregamento em progresso
+ Definições
+ Enviar para a Commons
+ Envio em progresso
Nome de utilizador
Palavra-passe
Entrar na sua conta da wiki Commons Beta
@@ -88,9 +88,9 @@
Esqueceu-se da palavra-passe?
Registar-se
A iniciar sessão
- Aguarde, por favor…
- A atualizar legendas e descrições
- Aguarde, por favor…
+ Por favor, aguarde…
+ A atualizar as legendas e descrições
+ Por favor, aguarde…
Sessão iniciada!
O início de sessão falhou!
O ficheiro não foi encontrado. Tente outro, por favor.
@@ -130,6 +130,7 @@
Demasiadas tentativas mal sucedidas. Por favor, tente novamente dentro de minutos.
Desculpe, este utilizador foi bloqueado na wiki Commons
Tem de fornecer o seu código de autenticação de dois fatores.
+ Foi enviado um código de verificação de autenticação para o seu endereço de correio eletrónico. Por favor, forneça o código para iniciar a sessão.
O início de sessão falhou
Carregar
Dê um nome a este conjunto
@@ -380,7 +381,7 @@
Imagens destacadas
Imagens via \"Locais próximos\"
Nível %d
- %s (Nível %s)
+ %s (Nível %s)
Imagens carregadas
Imagens não revertidas
Imagens usadas
@@ -423,7 +424,7 @@
Apercebi-me de que prejudica a minha privacidade
Mudei de ideias, já não quero possa ser vista pelo público
Desculpe, esta fotografia não tem interesse para uma enciclopédia
- Carregada por mim em %1$s, usada em %2$d artigo(s).
+ Enviada por mim em %1$s, usada pelo menos em %2$d artigo(s).
Bem-vindo(a) à wiki Commons!\n\nCarregue o seu primeiro ficheiro multimédia tocando no botão Adicionar.
Não foi selecionada nenhuma categoria
As imagens sem categorias só raramente são utilizáveis. Tem a certeza de que deseja continuar sem selecionar categorias?
@@ -588,6 +589,8 @@
MULTIMÉDIA
CLASSES DESCENDENTES
CLASSES PROGENITORAS
+ SUBCATEGORIAS
+ CATEGORIAS FONTE
Foi encontrado um local próximo
Estas imagens são de %1$s?
Isto é uma imagem de %1$s?
@@ -761,15 +764,29 @@
São necessárias permissões para a funcionalidade
Aprenda a escrever uma descrição útil
Aprenda a escrever uma legenda útil
- Ver as suas realizações
+ Ver as suas realizações
Editar imagem
Editar localização
Localização actualizada!
Remover Localização
+ Localização removida!
Agradecer ao autor
Erro no envio de agradecimento ao autor.
+ A sua autenticação expirou. Por favor, inicie a sessão novamente.
+ Nenhuma aplicação disponível para abrir os ficheiros GPX
+ Ficheiro guardado com sucesso
+ Deseja abrir o ficheiro GPX?
+ Deseja abrir o ficheiro KML?
+ Guardar Ficheiro KML
+ Guardar Ficheiro GPX
- %d imagem selecionada
- %d imagens selecionadas
+ Reporte um problema sobre este item na Wikidados
+ Por favor, insira alguns comentários
+ Discussão
+ Outro problema ou informação (por favor, explique em baixo).
+ O seu comentário é publicado na seguinte página da wiki: <a href=\"https://commons.wikimedia.org/wiki/Commons:Mobile_app/Feedback\">Commons:Mobile app/Feedback</a>
+ Nomeada para Eliminação
diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml
index 550f09968f..602fb69bf3 100644
--- a/app/src/main/res/values-ro/strings.xml
+++ b/app/src/main/res/values-ro/strings.xml
@@ -394,7 +394,7 @@
Mi-am dat seama că este rău pentru intimitatea mea
M-am răzgândit, nu mai vreau să fie publică
Ne pare rău că această imagine nu este interesantă pentru o enciclopedie
- Încărcat de mine pe %1$s, folosit în %2$d articol(e).
+ Încărcat de mine pe %1$s, folosit în %2$d articol(e).
Bine ați venit la Commons!\n\nÎncărcați primul dvs. media apăsând pe butonul de adăugare.
Nici o categorie selectată
Imaginile fără categorii sunt rareori utilizabile. Sigur doriți să continuați fără a selecta categorii?
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index ecef5c74b8..b3a5102d87 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -9,6 +9,7 @@
* Deltaspace
* Dirruw'o
* Eleferen
+* Elzav
* Envlh
* Facenapalm
* Fenixs-ru
@@ -269,6 +270,7 @@
Описание
Обсуждение
Автор
+ Загружающий
Дата загрузки
Лицензия
Координаты
@@ -427,7 +429,8 @@
Избранные изображения
Изображения мест поблизости
Уровень %d
- %s (Уровень %s)
+ %s (Уровень %s)
+ %s (%s)
Загружено изображений
Изображения, которые не откатывались
Использовано изображений
@@ -470,7 +473,7 @@
Я осознал, что это разглашает мою приватность
Я передумал. Больше не хочу чтобы было доступным публично
Извините, это изображение не для энциклопедии
- Загружено мною на сайт «%1$s» и использовано в {{PLURAL:%2$d|one=одной статье|%2$d статьях}}.
+ Загружено мною на сайт «%1$s» и использовано по крайней мере в {{PLURAL:%2$d|one=одной статье|%2$d статьях}}.
Добро пожаловать на Викисклад!\n\nЗагрузите ваш первый файл, нажав на кнопку добавления.
Не выбраны категории
Изображения без категорий используются редко. Вы уверены, что хотите продолжить, не выбрав категории?
@@ -639,6 +642,8 @@
МЕДИА
ДЕТСКИЕ КЛАССЫ
РОДИТЕЛЬСКИЕ КЛАССЫ
+ ПОДКАТЕГОРИИ
+ РОДИТЕЛЬСКИЕ КАТЕГОРИИ
Место поблизости найдено
На этих изображениях %1$s?
На этом изображении %1$s?
@@ -887,4 +892,5 @@
Показать в Nearby
Создано и загружено: %1$s
Создано %1$s и загружено %2$s
+ Номинировано на удаление
diff --git a/app/src/main/res/values-se/strings.xml b/app/src/main/res/values-se/strings.xml
index c4b3d534bb..386c5e6f45 100644
--- a/app/src/main/res/values-se/strings.xml
+++ b/app/src/main/res/values-se/strings.xml
@@ -256,7 +256,7 @@
Geavat
Lassedieđut
Android-veršuvdna
- Geahča iežat olahusaid
+ Geahča iežat olahusaid
Rievdat gova
Giite almmuheaddji
diff --git a/app/src/main/res/values-sh/error.xml b/app/src/main/res/values-sh/error.xml
index ea9b8e6707..28d2bbe0a2 100644
--- a/app/src/main/res/values-sh/error.xml
+++ b/app/src/main/res/values-sh/error.xml
@@ -1,9 +1,10 @@
- Commons se srušio
+ Aplikacija Ostava prestala je raditi
Ups. Nešto nije u redu!
Recite nam šta radite, pa podijelite s nama putem e-pošte. Pomoći će nam da ih popravimo!
Hvala vam!
diff --git a/app/src/main/res/values-sh/strings.xml b/app/src/main/res/values-sh/strings.xml
index 5997677efd..9fbec8e17c 100644
--- a/app/src/main/res/values-sh/strings.xml
+++ b/app/src/main/res/values-sh/strings.xml
@@ -1,6 +1,8 @@
Ostava na Facebooku
@@ -93,12 +95,12 @@
Pretraži kategorije
Snimi
Preučitaj
- Popis
+ Lista
(Još uvijek nema postavljenih datoteka)
Nema kategorija što odgovoraju %1$s
Stavite kategorije slikama kako biste olakšali korisnicima njihovo pronalaženje na Ostavi.\n\nDa biste stavili kategoriju, počnite sa pisanjem njenog imena.
Kategorije
- Podešavanja
+ Postavke
Registracija
Izabrane slike
Kategorija
@@ -125,7 +127,7 @@
Autorstvo 4.0
Autorstvo-Dijeliti pod istim uslovima 3.0
Autorstvo 3.0
- Ostava je udomljač najvećine slika što se koriste na Wikipediji.
+ Wikimedijina ostava skladišti većinu slika koje se upotrebljavaju na Wikipediji.
Vaše slike pomažu u obrazovanju ljudi širom svijeta!
Postavljajte slike koje su u potpunosti Vaše djelo:
Objekti iz prirode (cvijeće, životinje, planine)
@@ -153,8 +155,8 @@
Nema razgovora
Nepoznata licenca
Preučitaj
- Potrebna dozvola: Treba da se pročita iz spoljašnje memorije. Privitak bez ovoga nema pristupa Vašoj galeriji.
- Potrebna dozvola: Treba da se zapiše na spoljašnju memoriju. Privitak bez ovoga nema pristupa Vašoj kameri/galeriji.
+ Potrebno ovlaštenje: Čitanje vanjske memorije. Aplikacija ne može pristupiti galeriji bez toga.
+ Potrebno ovlaštenje: Pisanje u vanjskoj memoriji. Aplikacija ne može pristupiti kameri/galeriji bez toga.
U redu
Upozorenje
Postavi
@@ -188,7 +190,7 @@
Postavi
U blizini
O privitku
- Podešavanja
+ Postavke
Povratna informacija
Odjava
Uputstva
@@ -214,7 +216,7 @@
U budućnosti ćete se morati prijaviti kako biste postavili slike.
Prijavite se da biste koristili ovu funkciju
„U blizini“ možda ne radi kako treba. Lokacija nije dostupna.
- Potrebna je dozvola za popis liste lokacija u blizini
+ Potrebno je ovlaštenje za prikaz liste lokacija u blizini
Upute
Wikidata
Wikipedia
@@ -226,7 +228,7 @@
Nema obavijesti
Prevedi
Jezici
- Odaberite za koji jezik bi želeli da pravite prijevode
+ Izaberite jezik na koji biste željeli prevoditi
Nastavi
Otkaži
Pokušaj ponovo
@@ -261,6 +263,6 @@
Poništi
Podaci o lokaciji pomažu Wikiurednicima da pronađu vašu sliku, čineći je mnogo korisnijom.\nVaše nedavne postavljene slike nemaju lokaciju.\nPredlažemo da uključite lokaciju u postavkama priloga kamere.\nHvala vam na učitavanju!
Detalji
- Razina priložnika
+ Nivo API-ja
Android verzija
diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml
index 5def2523d4..c777c5e562 100644
--- a/app/src/main/res/values-sk/strings.xml
+++ b/app/src/main/res/values-sk/strings.xml
@@ -423,7 +423,7 @@
Uvedomil/a som si, že je to zlé pre moje súkromie
Zmenil/a som názor, nechcem byť viac verejne viditeľný/á
Prepáčte, tento obrázok pre encyklopédiu nie je zaujímavý
- Nahrané mnou %1$s, použité v %2$d článku/článkoch.
+ Nahrané mnou %1$s, použité v %2$d článku/článkoch.
Vitajte na Commons!\n\nNahrajte svoje prvé súbory ťuknutím na tlačidlo pridať.
Neboli vybrané žiadne kategórie
Obrázky bez kategórií sú zriedka použiteľné. Naozaj chcete nahrať obrázok bez výberu kategórií?
@@ -757,7 +757,7 @@
Povolenia sú potrebné pre zabezpečenie funkčnosti
Zistite, ako napísať užitočný popis
Zistite, ako napísať užitočný titulok
- Pozrite si svoje úspechy
+ Pozrite si svoje úspechy
Upraviť obrázok
Upraviť polohu
Poloha aktualizovaná!
diff --git a/app/src/main/res/values-sl/strings.xml b/app/src/main/res/values-sl/strings.xml
index d81d5ba73e..38dd9be1c9 100644
--- a/app/src/main/res/values-sl/strings.xml
+++ b/app/src/main/res/values-sl/strings.xml
@@ -294,7 +294,7 @@
V prihodnosti se boste morali za nalaganje slik prijaviti.
Za uporabo te možnosti se prijavite
Kopiraj vikibesedilo v odložišče
- Vikibesedilo je skopirano v odložišče
+ Vikibesedilo je kopirano v odložišče
Bližnje mogoče ne bo pravilno delovalo. Kraj ni na voljo.
Internet ni na voljo. Prikazani so samo predpomnjeni kraji.
Dostop do lokacije je bil zavrnjen. Za uporabo te možnosti ročno nastavite svojo lokacijo.
@@ -312,7 +312,7 @@
Napaka pri pridobivanju obvestil
Napaka pri pridobivanju slike za pregled. Pritisnite Osveži za ponovni poskus.
Ni najdenih obvestil
- Prevedi
+ Prevodi
Jeziki
Izberite jezike, za katere želite pošiljati prevode
Nadaljuj
@@ -386,13 +386,14 @@
Izbrane slike
Slike iz »Bližnji kraji«
Raven %d
- %s (raven %s)
+ %s (raven %s)
+ %s (%s)
Naložene slike
Nevrnjene slike
Uporabljene slike
Delite svoje dosežke s prijatelji!
Ko boste izpolnili te zahteve, se bo vaša raven zvišala. Predmeti v razdelku »Statistika« ne štejejo.
- Potrebno najmanj:
+ Potrebno najmanj:
Število slik, ki ste jih naložili v Zbirko, ne glede na program za nalaganje
Delež slik v Zbirki, ki niso bile izbrisane
Število slik, ki ste jih naložili v Zbirko in se uporabljajo v člankih Wikipedije
@@ -424,12 +425,12 @@
Dodali niste nobenega zaznamka
Zaznamki
Dnevniško beleženje se je začelo. Prosimo, ZNOVA ZAŽENITE aplikacijo, opravite dejanje, ki ga želite zabeležiti, in nato znova tapnite »Pošlji dnevniški zapis«.
- Naložil sem jo pomotoma
+ Naložil_a sem jo pomotoma
Nisem vedel_a, da bo javno vidna
Spoznal sem, da je to slabo za mojo zasebnost
- Premislil_a sem si, ne želim več biti javno viden_na
+ Premislil_a sem si, ne želim več, da je javno vidna
Ta slika žal ni zanimiva za enciklopedijo.
- Sliko sem naložil/a sam/a dne %1$s. Uporablja se v %2$d članku(ih).
+ Sliko sem naložil/a sam/a dne %1$s. Uporablja se v %2$d članku(ih).
Pozdravljeni v Wikimedijini zbirki!\n\nTapnite gumb dodaj in naložite svojo prvo predstavnostno datoteko.
Izbrana ni nobena kategorija
Slike brez kategorij so le redko uporabne. Ali res želite nadaljevati brez izbire kategorij?
@@ -478,7 +479,7 @@
Zahvala uporabniku_ci %1$s je bila uspešno poslana
Pošiljanje zahvale uporabniku_ci %1$s ni uspelo
Pošiljanje zahvale: neuspešno
- Pošiljam zahvalo uporabniku/ici %1$s
+ Pošiljam zahvalo za %1$s
Ali so tu upoštevane avtorske pravice?
Ali je to pravilno kategorizirano?
Ali to spada v okvir projekta?
@@ -509,7 +510,7 @@
Slike, naložene z Bližnjimi kraji, so slike, ki so naložene z odkrivanjem krajev na zemljevidu.
Ta možnost vam omogoča, da urejevalcem, ki so opravili koristno urejanje, pošljete zahvalo – z uporabo kratke povezave na strani zgodovine ali strani primerjave.
Kopiraj na naslednjo predstavnostno datoteko
- Skopirano
+ Kopirano
Zgledi dobrih slik za nalaganje v Zbirko
Zgledi slik, ki niso primerne za nalaganje
Preskoči to sliko
@@ -664,7 +665,7 @@
Način omejene povezanosti izklopljen. Nalaganje se bo zdaj nadaljevalo.
Način omejene povezanosti
Kakovostne slike
- Kakovostne slike so ponazoritve ali fotografije, ki ustrezajo nekaterim merilom kakovosti (ta so predvsem tehnična) in so dragocene za projekte Wikimedie
+ Kakovostne slike so ilustracije ali fotografije, ki ustrezajo nekaterim merilom kakovosti (ta so predvsem tehnična) in so dragocene za projekte Wikimedie
Nalaganje se nadaljuje ...
Zaustavljam nalaganje ...
Preklicujem nalaganje ...
@@ -761,7 +762,7 @@
Prijava
Nastavitev belega ozadja
Nastavitev črnega ozadja
- Prijavi kršitev
+ Prijava kršitve
Prijavi uporabnika
Prijavi to vsebino
Zahtevaj blokiranje uporabnika
@@ -775,7 +776,7 @@
Za delovanje so potrebna dovoljenja
Naučite se napisati koristen opis
Naučite se napisati koristen napis
- Oglejte si svoje dosežke
+ Oglejte si svoje dosežke
Uredi sliko
Uredi lokacijo
Lokacija posodobljena!
@@ -833,7 +834,7 @@
Zbirka
Drugi vikiji
•
- Uporabe datotek
+ Uporabe datoteke
SingleWebViewActivity
Račun
Izgini račun
diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml
index cb09f2f802..a41096306d 100644
--- a/app/src/main/res/values-sr/strings.xml
+++ b/app/src/main/res/values-sr/strings.xml
@@ -409,7 +409,7 @@
Схватио/ла сам да је лоша за моју приватност
Променио/ла сам мишљење, не желим да више јавно буду видљиве
Ова слика није занимљива за енциклопедију
- Отпремио/ла сам на %1$s, користи се у чланку/цима — %2$d.
+ Отпремио/ла сам на %1$s, користи се у чланку/цима — %2$d (бар).
Добро дошли на Оставу!\n\nОтпремите прве медије додиром дугмета за додавање.
Категорије нису изабране
Слике без категорија ретко су употребљиве. Заиста желите да наставите без избора категорија?
@@ -751,4 +751,5 @@
Поднапис копиран
Направио и отпремио: %1$s
Направио %1$s а отпремио %2$s
+ Номинуј за брисање
diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml
index 28a0896c09..c5f361ee12 100644
--- a/app/src/main/res/values-sv/strings.xml
+++ b/app/src/main/res/values-sv/strings.xml
@@ -377,7 +377,8 @@
Utvalda bilder
Bilder via \"Platser i närheten\"
Nivå %d
- %s (Nivå %s)
+ %s (Nivå %s)
+ %s (%s)
Uppladdade bilder
Bilder som inte har återställts
Bilder som används
@@ -420,7 +421,7 @@
Jag insåg att den är dålig för mitt privatliv
Jag ändrade mig, jag vill inte längre att den ska vara synlig offentligt
Tyvärr, denna bild är inte intressant för en encyklopedi
- Laddades upp av mig den %1$s och används i %2$d artiklar.
+ Laddades upp av mig den %1$s och används i %2$d artiklar.
Välkommen till Commons!\n\nLadda upp din första mediafil genom att trycka på knappen för att lägga till.
Inga kategorier har valts
Bilder utan kategorier används sällan. Är du säker på att du vill fortsätta utan att välja kategorier?
@@ -760,7 +761,7 @@
Behörigheter krävs för funktionalitet
Lär dig hur du skriver en användbar beskrivning
Lär dig hur du skriver en användbar bildtext
- Se dina prestationer
+ Se dina prestationer
Redigera bild
Redigera plats
Plats uppdaterades!
diff --git a/app/src/main/res/values-te/strings.xml b/app/src/main/res/values-te/strings.xml
index ba67a7b350..0d4b323f4a 100644
--- a/app/src/main/res/values-te/strings.xml
+++ b/app/src/main/res/values-te/strings.xml
@@ -377,7 +377,7 @@
నా అంతరంగికతకు ఇది చేటు అని గుర్తించాను
నేణు మనసు మార్చుకున్నాను, బహిరంగంగా అందరికీ కనబడేలా ఉండను
సారీ, ఈ బొమ్మ విజ్ఞాన సర్వస్వానికి ఆసక్తి కలిగేలా లేదు
- %1$s న నేనే ఎక్కించాను, %2$d వ్యాసాల్లో వాడారు.
+ %1$s న నేనే ఎక్కించాను, %2$d వ్యాసాల్లో వాడారు.
కామన్స్కు స్వాగతం!\n\nచేర్చు బొత్తాన్ని నొక్కి మీ మొదటి మీడియాను ఎక్కించండి.
వర్గాలేమీ ఎంచుకోలేదు
వర్గాల్లేని బొమ్మలను అరుదుగా వాడగలం. వర్గాలేమీ ఎంచుకోకుండానే కొనసాగాలని అనుకుంటున్నారా?
diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml
index 2e2b132168..0809c24e07 100644
--- a/app/src/main/res/values-tr/strings.xml
+++ b/app/src/main/res/values-tr/strings.xml
@@ -241,6 +241,7 @@
Açıklama
Tartışma
Yazar
+ Yükleyici
Yükleme tarihi
Lisans
Koordinatlar
@@ -399,7 +400,8 @@
Seçkin Resimler
\"Yakındaki Yerler\"den Resimler
Seviye %d
- %s (Seviye %s)
+ %s (Seviye %s)
+ %s (%s)
Resimler Yüklendi
Resimler Geri Alınmadı
Resimler Kullanıldı
@@ -442,7 +444,7 @@
Gizliliğimin kötü olduğunu fark ettim
Fikrimi değiştirdim, artık herkese görünür olmasını istemiyorum
Üzgünüz, bu resim bir ansiklopedi için ilginç değil
- Kendi başıma %1$s üzerine yüklendi, %2$d makalede kullanıldı.
+ %1$s tarafımdan yüklendi, en az %2$d madde de kullanıldı.
Commons\'a Hoş Geldiniz!\n\nEkle düğmesine dokunarak ilk medyanızı yükleyin.
Kategori Seçilmedi
Kategorisiz görüntüler nadiren kullanılabilir. Kategori seçmeden devam etmek istediğinizden emin misiniz?
@@ -607,6 +609,8 @@
MEDYA
ALT SINIFLAR
ÜST SINIFLAR
+ ALT KATEGORİLER
+ ÜST KATEGORİLER
Yakındaki Yer Bulundu
Bunlar %1$s resimleri mi?
Bu bir %1$s resmi mi?
@@ -782,7 +786,7 @@
İşlevsellik için izinler gereklidir
Yararlı bir açıklamanın nasıl yazılacağını öğrenin
Nasıl faydalı bir alt yazı yazılacağını öğrenin
- Başarılarınızı görün
+ Başarılarınızı görüntüleyin
Görseli düzenle
Konumu Düzenle
Konum güncellendi!
@@ -851,4 +855,5 @@
Yakınlarda Göster
Oluşturan ve yükleyen: %1$s
%1$s tarafından oluşturuldu ve %2$s tarafından yüklendi
+ Silinmeye aday gösterildi
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index c457636ff2..ce7963b60a 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -132,6 +132,8 @@
Оберіть фото
Поблизу
Мої завантаження
+ Скопіювати посилання
+ Посилання скопійовано в буфер обміну
Поширити
Переглянути сторінку файлу
Підпис (обов\'язково)
@@ -142,6 +144,7 @@
Надто багато невдалих спроб. Будь ласка, спробуйте знову через кілька хвилин.
Вибачте, цього користувача було заблоковано на Вікісховищі
Ви повинні надати код двофакторної автентифікації.
+ Код підтвердження входу надіслано на вашу адресу електронної пошти. Будь ласка, введіть код для входу.
Не вдалося увійти
Завантажити
Назвіть цю серію
@@ -150,6 +153,7 @@
Пошук категорій
Пошук об\'єктів, що зображено у цьому медіа (напр. гори, Тадж-Махал, тощо)
Зберегти
+ Додаткове меню
Оновити
Список
(Ще нема завантажень)
@@ -246,6 +250,7 @@
Станьте бета-тестером
Підпишіться на наш бета-канал на Google Play і отримайте ранній доступ до нових функцій та виправлень баґів
Код 2FA
+ Код підтвердження електронної пошти
Ви справді хочете вийти із системи?
Помилка медіазображення
Підкатегорій не знайдено
@@ -306,6 +311,7 @@
Скопіювати вікі-текст у буфер обміну
Вікі-текст скопійовано у буфер обміну
Функція «Поблизу» може працювати некоректно, «Розташування» недоступне.
+ Інтернет недоступний. Показуються лише кешовані місця.
Доступ до місцезнаходження заборонено. Щоб скористатися цією функцією, будь ласка, вкажіть своє місцезнаходження вручну.
Потрібний дозвіл для показу списку місць поблизу
Потрібний дозвіл для показу зображень місць поблизу
@@ -389,11 +395,13 @@
Вилучити
Досягнення
Профіль
+ Значки
Статистика
Отримані подяки
Вибрані зображення
Зображення місць поблизу
- Рівень
+ Рівень %d
+ %s (Рівень %s)
Завантажені зображення
Не відхилені зображення
Використані зображення
@@ -425,6 +433,7 @@
На Вашому пристрої не знайдено сумісного додатка з картами. Будь ласка, встановіть додаток з картами, якщо хочете скористатись цією функцією.
Зображення
Місця
+ Категорії
Додати/вилучити закладки
Закладки
Ви не додали жодної закладки
@@ -435,7 +444,7 @@
Мені стало зрозуміло, що це шкодить моїй приватності
Моя думка змінилась, я не хочу, щоб це було доступно публічно
Перепрошую, це зображення нецікаве для енциклопедії
- Завантажено мною на сайт «%1$s» та використано у {{PLURAL:%2$d|one=одній статті|%2$d статтях}}.
+ Завантажено мною на сайт «%1$s» та використано у принаймні {{PLURAL:%2$d|one=одній статті|%2$d статтях}}.
Ласкаво просимо до Вікісховища!\n\nЗавантажте Ваш перший медіафайл, натиснувши кнопку додавання.
Жодної категорії не вибрано
Зображення без категорій рідко використовуються. Ви впевнені, що хочете продовжити без вказаних категорій?
@@ -506,6 +515,7 @@
У вас немає непрочитаних сповіщень
Немає прочитаних сповіщень
Поширення журналів
+ Перевірте свою скриньку електронної пошти
Перегляд прочитаних
Перегляд непрочитаних
Сталася помилка при завантаженні зображень
@@ -603,6 +613,8 @@
МЕДІА
ДОЧІРНІ КЛАСИ
БАТЬКІВСЬКІ КЛАСИ
+ ПІДКАТЕГОРІЇ
+ БАТЬКІВСЬКІ КАТЕГОРІЇ
Знайдено місце поблизу
Тут зображено %1$s?
На цьому зображенні %1$s?
@@ -725,7 +737,7 @@
Для належної роботи мапи поблизу мають відображати стан PHONE STATE
Внесок користувача: %s
Досягнення користувача: %s
- Переглянути сторінку користувача
+ Переглянути профіль користувача
Редагувати описи
Редагувати категорії
Розширені параметри
@@ -780,7 +792,7 @@
Для роботи потрібні дозволи
Дізнайтеся, як написати корисний опис
Дізнайтеся, як написати корисний підпис
- Перегляньте свої досягнення
+ Перегляньте свої досягнення
Редагувати зображення
Редагувати розташування
Розташування оновлено
@@ -810,8 +822,45 @@
Повідомити у Вікідані про проблему з цим елементом
Будь ласка, додайте якийсь коментар
Обговорення
+ Напишіть щось про елемент «%1$s». Це буде видно публічно.
«%1$s» більше не існує, фотографій цього більше не можливо зробити.
+ «%1$s» розташовано в іншому місці.
«%1$s» — це інше місце. Будь ласка, вкажіть правильне місце нижче і, якщо можна, напишіть правильні широту і довготу.
Інша проблема або інформація (поясніть нижче).
Ваші відгуки розміщуються на ось цій вікісторінці: <a href=\"https://commons.wikimedia.org/wiki/Commons:Mobile_app/Feedback\">Commons:Mobile app/Feedback</a>
+ Ви впевнені, що бажаєте скасувати усі вивантаження?
+ Скасування усіх вивантажень…
+ Вивантаження
+ В очікуванні
+ Не вдалося
+ Не вдалося завантажити дані про місце
+ Вилучити папку
+ Підтвердити вилучення
+ Ви впевнені, що бажаєте вилучити папку %1$s, у якій міститься %2$d елемент(ів)?
+ Вилучити
+ Скасувати
+ Папку %1$s успішно вилучено
+ Не вдалося вилучити папку %1$s
+ Помилка при вилученні вмісту папки: %1$s
+ Не вдалося отримати шлях до папки для ID контейнера: %1$d
+ У цього місця ще немає зображень, сфотографуйте його!
+ Це місце уже має зображення.
+ Перевіряємо, чи це місце має зображення.
+ Помилка при завантаженні
+ Не знайдено використань
+ Вікісховище
+ Інші вікі
+ Використання файлу
+ Обліковий запис
+ Знищити обліковий запис
+ Попередження про знищення облікового запису
+ Зникнення є <b>останнім заходом</b>, і <b>його слід використовувати лише тоді, коли ви хочете назавжди припинити редагування</b>, а також щоб приховати якомога більше своїх минулих зв\'язків.<br/><br/>Вилучення облікового запису у Вікісховищі робиться шляхом зміни імені вашого облікового запису, щоб інші не могли розпізнати ваші внески; це називають зникненням облікового запису. <b>Зникнення не гарантує повної анонімності і не вилучає внесок у проєкти</b>.
+ Підпис
+ Підпис скопійовано до буферу обміну
+ Вітаємо, всі зображення в цьому альбомі або завантажені, або позначені як не для завантаження.
+ Показати в розділі «Дослідити»
+ Показати в розділі «Поблизу»
+ Створено та завантажено: %1$s
+ Створено %1$s та завантажено %2$s
+ Номіновано на вилучення
diff --git a/app/src/main/res/values-vec/strings.xml b/app/src/main/res/values-vec/strings.xml
index 66905e7580..17765b3cee 100644
--- a/app/src/main/res/values-vec/strings.xml
+++ b/app/src/main/res/values-vec/strings.xml
@@ -344,7 +344,7 @@
Me so rendesto conto che ła xé on małe par ła me privacy
Go canbià idea, no vujo pì che ła sia vardabiłe da tuti
Ne despiaxe, sta imajine no ła xé intaresante par na ençiclopedia.
- Cargà da mi el %1$s, doparà in %2$d voxe.
+ Cargà da mi el %1$s, doparà in %2$d voxe.
Benvegnuo so Commons!\n\nCarga el to primo file multimediałe strucando el boton \"xonta\".
Nisuna categoria sełesionà
Le imajini sensa categorie łe xé poche olte doparabiłi. Sito seguro de vołer enviar sensa sełesionar categorie?
diff --git a/app/src/main/res/values-x-invalidLanguageCode/error.xml b/app/src/main/res/values-x-invalidLanguageCode/error.xml
deleted file mode 100644
index f4e2fe125e..0000000000
--- a/app/src/main/res/values-x-invalidLanguageCode/error.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
- کامَنٕز گوو رُکِتھ
- Oops. کیہہ تام گوو غلط!
- ؤنِیوٚ اَسہِ توٚہہِ کیاہ ٲسِیوٚ کران، تہٕ کٕریٚو تہِ اَسہِ سٕتی شیر بذریعہِ برقی خط. یُس مَدَتھ کَرِ اَسہِ اَتھ شہَرنَس منٛز!
- شُکریہ!
-
diff --git a/app/src/main/res/values-xmf/strings.xml b/app/src/main/res/values-xmf/strings.xml
index 153d88b543..649cab67de 100644
--- a/app/src/main/res/values-xmf/strings.xml
+++ b/app/src/main/res/values-xmf/strings.xml
@@ -99,6 +99,8 @@
ფოტოშ გინოღალა
გოხოლუას
ჩქიმი ეხარგუეფი
+ რსხილიშ კოპირება
+ რსხილი კოპირებული რე ბუფერულ შვენას
გობჟინაფა
ფაილიშ ხასჷლაშ ძირაფა
მუკნაჭარა (უციო)
@@ -109,6 +111,7 @@
ძალამ მიარე უმწუძინუ ცადება. ქორთხინ, მუხირენ წუთშა ხოლო ქოცადით.
მორდება, თე მახვარებუ ბლოკირი რე ვიკიოწკარუეს
თქვა გემშიონათ ოკო ჟირფაქტორიანი ავტორიზაციაშ კოდი.
+ დოდასურაფაშ კოდი ჯღონელი რე თქვანი ელექტრონული ფოშტაშა. ქორთხიინთ, მოჯღონელი კოდი გენშეჸონათ მიშაულარო.
მიშულაქ ვემიხუჯინუ
ეხარგუა
სერიაშ ჯოხო
@@ -117,6 +120,7 @@
კატეგორიაშ გიშაგორუა
დოგორით ელემენტეფი, ნამუეფით მოჩამილი რე თქვანი სურათის (გვალა, ტაჯ-მაჰალი დო თ.უ.)
ჩუალა
+ გეძინელი მენიუ
გოახალაფა
ერკებული
(ეხარგუეფი ვა რე)
@@ -213,6 +217,7 @@
ბეტა ტესტირებას კათაფი
ჩართით Beta-შა ჭირინაფა Google Play-ს დო მიღით ორდოიანი ჭირინაფა ახალ ფუნქციეფშა დო ჩილათეფიშ ეშახინტკალო
2ფა კოდი
+ ელექტრონული ფოშტაშ დოდასურაფაშ კოდი
გოკონანო გიშულა?
მედიაფაილიშ ჩილათა
გიმენკატეგორიეფქ ვეგორინუ
@@ -360,6 +365,7 @@
კინოხიანი
სურათეფი
ორენეფი
+ კატეგორიეფი
მიკოწონებულეფშა გეძინა/ლასუა
მიკოწონებულეფი
თქვა ვეგეიძინჷნა აკა მიკოწონებული
@@ -369,12 +375,14 @@
მიბრჩქინე, ნამჷდა თენა ჩქიმი კონფიდენციალურალაშო გლახა იჸუაფუდჷნ
არზი დიბთირე დო შუროთ ვამოკო თინა ოჯარალეთ იძირებედასჷნ
მორდება, ათე სურათი ენციკლოპედიაშო ვა რე საინტერესო
- ჩქიმი ენახარგა რე %1$s-შა, გჷმორინაფილი რე %2$d სტატიას.
+ ჩქიმი ენახარგა რე %1$s-შა, გჷმორინაფილი რე %2$d სტატიას.
მოზოჯით ვიკიოწკარუეშა!\n\nგეხარგეთ თქვანი პირველი ფაილი, ქეგუნჭირით კონჭის გეძინა.
კატეგორია ვა რე გიშაგორილი
სურათეფი კატეგორიზაციაშ უმუშო შხირას მერკეთ გჷმორინაფონი რე. დასურო გონებჷნანო კატეგორიეფიშ მეწურაფაშ უმუშო გაგჷნძარაფა?
+ ეჭარუა ვა რე გიშაგორილი
ეხარგუაშ გოუქვაფა
ეხარგუაშ გოგჷნძორაფა
+ (ნაკორობაშ არძა სურათიშო)
ათე არანს გორუა
ალობაშ მოთხირი
თენა კჷნ დღას ვაბკითხა
@@ -383,9 +391,20 @@
ალობაშ მეჩამა
გოუქვაფა
ღოლამირჷ რე
+ თინას რენო კათეგორიეფი მეწურაფილი?
+ გეჸვენჯი სურათი
+ ქო, მუშენ ვარი
ვა რე გუმორინაფილი სურათეფი
+ ვა რე ეხარგილი სურათეფი
+ თქვა ვაიღჷნა უკითხირუ გინაფეფი
+ ვა ვაიღჷნა კითხირებული გინაფეფი
+ ქოძირით თქვანი ელექტრონული ფოსტა
+ კითხირებულიშ ძირაფა
+ უკითხირებუშ ძირაფა
სურათიშ ეხარგუაშ ბორჯის ჩილათაქ მოხვადჷ
ქორთხინთ ქჷმიცადით …
+ კოპირაფილი რე
+ თე სურათიშ გიშატება
ავტორი
ორენი
კამერაშ მოდელი
@@ -393,9 +412,32 @@
სერიული ნომერი
პროგრამული უნარღელჸუა
სურათიშ ინფორმაცია
- სელფი
- ჸურილი
+ კატეგორიეფქ ვეგორინუ
+ ეჭარუეფქ ვეორინუ
+ ეხარგუაქ გეუქვუ
+ მუშენ დილასას ოკო %1$s-ქ?
+ %1$s ეხარგილი რე: %2$s
+ წუმოძინელო
+ %1$s ნომინირებული რე ოლასარო.
+ ვემიხუჯინუ
+ ვეშილებე ლასუაშ მოთხირი.
+ სელფი, ნამუთ ვეგჷმირინუაფუ ნამთინ სტატიას
+ ედომუშამო გინობარდილი რე
+ უაზრობა, აბსოლუტურო უმუხუჯუ რე ირნერი სტატიას
+ ახალი ამბეეფიშ სურათი
+ ნამდგა სურათი ინტერნეტიშე
+ ლოგო
+ პანორამაშ დუდიშულაშ აკორცუაფა
თიშენ ნამჷ-და თინა რე
+ კატეგორიეფიშ მოახალებაშ ცადება.
+ კატეგორიაშ მოახალება
+ წუმოძინელო
+
+ - კატეგორია %1$s გეძინელი რე.
+ - კატეგორიეფი %1$sგეძინელი რე.
+
+ ვემიხუჯინუ კატეგორიეფიშ გეძინაქ.
+ კატეგორიეფიშ მოახალება
მიკოწონებეფი
პარამეტრეფი
რუმე
diff --git a/app/src/main/res/values-yue/error.xml b/app/src/main/res/values-yue/error.xml
index 68579e4a05..b8834d48c9 100644
--- a/app/src/main/res/values-yue/error.xml
+++ b/app/src/main/res/values-yue/error.xml
@@ -1,9 +1,11 @@
同享壞咗
哎呀。出咗錯!
+ 話畀我哋知你做緊啲咩,然後透過電郵分享畀我哋,我哋會幫我哋解決問題!
多謝你!
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
index 0434441c8a..0bcc201e49 100644
--- a/app/src/main/res/values-zh-rTW/strings.xml
+++ b/app/src/main/res/values-zh-rTW/strings.xml
@@ -152,7 +152,7 @@
沒有發現與 %1$s 相符的分類
找不到符合%1$s的維基數據項目
%1$s沒有子類別
- %1$s沒有父類別
+ %1$s沒有父級類別
為您的圖片添加分類,使別人在維基共享資源更容易找到。\n\n開始輸入以添加分類。
分類
設定
@@ -235,6 +235,7 @@
描述
討論
作者
+ 上傳者
上傳日期
授權協議
座標
@@ -246,7 +247,7 @@
您確定要登出嗎?
媒體圖片失敗
找不到子分類
- 找不到母分類
+ 找不到父級分類
藏王山
美洲駝
彩虹橋
@@ -393,7 +394,8 @@
特色圖片
圖片來自“附近的地方”
等級 %d
- %s(%s級)
+ %s(%s級)
+ %s(%s)
已上傳的圖片
沒有被還原回復的圖片
有被使用到的圖片
@@ -436,7 +438,7 @@
我認為這會干擾到我的隱私
我改變我的主意,我不想要公開可見
真抱歉,此圖片對於百科全書沒有意義
- 我自己在%1$s上上傳,在%2$d篇條目中使用。
+ 我自己在%1$s上傳,至少用在 %2$d 篇條目。
歡迎來到維基共享資源!\n\n透過觸碰添加按鈕來上傳您的首個多媒體內容。
未選擇分類
不帶分類的圖片很難有機會被利用到,您確定您要不選擇分類來繼續嗎?
@@ -590,7 +592,7 @@
%s還沒有做出任何貢獻
已建立帳號!
文字已複製至剪貼簿
- 通知標記為已讀
+ 將通知標記為已讀
發生一些錯誤!
地點狀態:
存在
@@ -600,7 +602,9 @@
登入時發生一些問題,您必須重新設定您的密碼!!
媒體
子類別
- 父類別
+ 父級類別
+ 子分類
+ 父級分類
找到附近地點
這些是%1$s的圖片嗎?
這是%1$s的圖片嗎?
@@ -735,7 +739,7 @@
是否要加入這張圖片拍攝的地點?\n位置資料有助於 Wiki 編輯者找到您的圖片,使其更有用。\n謝謝!
新增位置
請移除來自此電子郵件中您無意公開分享的任何資訊。另外請注意,您用來發布訊息的電子郵件地址、關聯名稱、與個人資料圖片會公開顯示。
- 詳細資料
+ 詳細資訊
成果僅在生產版本可用,請查閱開發者文件。
排行榜僅在生產版本可用,請查閱開發者文件。
請僅上傳您自己拍攝的照片。受版權保護的圖像的上傳者將被阻止。這也適用於 beta 版本。感謝您測試該應用程序!
@@ -808,6 +812,7 @@
討論
填寫一些關於「%1$s」項目的事物。內容將公開可見。
「%1$s」已不存在,無法拍攝它的照片。
+ 「%1$s」位於不同的地方。
「%1$s」位於不同的位置。請在下面指定正確的位置,可以的話請填寫正確的經緯度。
其他問題或資訊(請在下方解釋)。
您的回饋已發布到以下 wiki 頁面:<a href=\"https://commons.wikimedia.org/wiki/Commons:Mobile_app/Feedback\">Commons:Mobile app/Feedback</a>
@@ -846,4 +851,5 @@
顯示在附近
由%1$s建立與上傳
由%1$s建立,%2$s上傳
+ 提名刪除
diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml
index 930c9e4fb2..3fca731d4b 100644
--- a/app/src/main/res/values-zh/strings.xml
+++ b/app/src/main/res/values-zh/strings.xml
@@ -269,6 +269,7 @@
描述
讨论
作者
+ 上传者
上传日期
许可协议
坐标
@@ -427,7 +428,8 @@
特色图片
来自“附近地点”的图片
等级%d
- %s(%s級)
+ %s(%s級)
+ %s(%s)
已上传图片
未还原图片
使用过的图片
@@ -470,7 +472,7 @@
我意识到这对我的隐私不利
我改变了主意,我不想再让公众看到它了
对不起,这幅图对百科全书没什么意思
- 由我自己上传在%1$s,使用于%2$d个条目。
+ 由我自己上传在%1$s,使用于至少%2$d个条目。
欢迎使用共享资源!\n\n通过点击添加按钮以上传您的首个媒体。
未提交分类
不带分类的图片很难有机会被利用到,您确定您要不选择分类来继续吗?
@@ -635,6 +637,8 @@
媒体
子类别
父类别
+ 子分类
+ 父级分类
找到附近地点
这些是%1$s的图片吗?
这是%1$s的图片吗?
@@ -811,7 +815,7 @@
功能需要权限
了解如何写出有用的描述
了解如何写出有用的注释
- 查看您的成果
+ 查看您的成就
编辑图片
编辑位置
位置已更新!
@@ -880,4 +884,5 @@
显示在附近
创建并上传者: %1$s
由%1$s创建并由%2$s上传
+ 已提名删除
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 68dba88bec..f58a0fd3ca 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -211,6 +211,7 @@
Description
Discussion
Author
+ Uploader
Uploaded date
License
Coordinates
@@ -382,7 +383,8 @@
Featured Images
Images via \"Nearby Places\"
Level %d
- %s (Level %s)
+ %s (Level %s)
+ %s (%s)
Images Uploaded
Images Not Reverted
Images Used
@@ -431,7 +433,7 @@
I changed my mind, I don\'t want it to be publicly visible anymore
Sorry this picture is not interesting for an encyclopedia
- Uploaded by myself on %1$s, used in %2$d article(s).
+ Uploaded by myself on %1$s, used in %2$d article(s) at least.
Welcome to Commons!\n
Upload your first media by tapping on the add button.
@@ -617,6 +619,8 @@ Upload your first media by tapping on the add button.
MEDIA
CHILD CLASSES
PARENT CLASSES
+ SUBCATEGORIES
+ PARENT CATEGORIES
Nearby Place Found
Are these pictures of %1$s?
@@ -802,7 +806,7 @@ Upload your first media by tapping on the add button.