Merge branch 'main' into kn/bug/6330-NPE-LatLng-getLatitude

This commit is contained in:
Ritika Pahwa 2025-07-07 19:29:57 +05:30 committed by GitHub
commit a47a9e7960
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
135 changed files with 1786 additions and 1408 deletions

View file

@ -1,7 +1,7 @@
name: "\U0001F41E Bug report"
description: Create a report to help us improve.
title: "[Bug]: "
labels: ["bug"]
type: Bug # Retained to categorize the issue as per organization-level type
body:
- type: markdown
attributes:

View file

@ -16,6 +16,7 @@
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="999" />
<option name="IMPORT_LAYOUT_TABLE">
<value>
<package name="" withSubpackages="true" static="false" module="true" />
<package name="" withSubpackages="true" static="true" />
<emptyLine />
<package name="" withSubpackages="true" static="false" />

View file

@ -1,5 +1,15 @@
# Wikimedia Commons for Android
## v5.5.0
### What's changed
* Explore images will now be shown based on the map location and not at your current location
* Enhanced Wikidata feedback message
* Green labels in Explore map will no longer be hidden by other pins thumbnails
* Upload wizard's language drop-down now reflects the language used in the pin label
* Users can now pick only one image at a time while using the custom selector
* Bug fixes and stability improvements
## v5.4.1
### What's changed

View file

@ -24,8 +24,8 @@ android {
applicationId = "fr.free.nrw.commons"
minSdk = 21
targetSdk = 34
versionCode = 1052
versionName = "5.4.1"
versionCode = 1053
versionName = "5.5.0"
setProperty("archivesBaseName", "app-commons-v$versionName-" + getBranchName())
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
@ -347,6 +347,7 @@ dependencies {
// Kotlin + coroutines
implementation(libs.androidx.work.runtime.ktx)
implementation(libs.androidx.work.runtime)
implementation(libs.kotlinx.coroutines.rx2)
testImplementation(libs.androidx.work.testing)
//Glide

View file

@ -66,6 +66,9 @@
# Application classes that will be serialized/deserialized over Gson
-keep class com.google.gson.examples.android.model.** { *; }
# Prevent R8 from obfuscating project classes used by Gson for parsing
-keep class fr.free.nrw.commons.fileusages.** { *; }
# Prevent proguard from stripping interface information from TypeAdapterFactory,
# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
-keep class * implements com.google.gson.TypeAdapterFactory

View file

@ -1,7 +1,9 @@
package fr.free.nrw.commons
import android.annotation.SuppressLint
import android.content.ActivityNotFoundException
import android.content.Intent
import android.content.Intent.ACTION_VIEW
import android.net.Uri
import android.os.Bundle
import android.view.Menu
@ -16,6 +18,9 @@ import fr.free.nrw.commons.theme.BaseActivity
import fr.free.nrw.commons.utils.ConfigUtils.getVersionNameWithSha
import fr.free.nrw.commons.utils.DialogUtil.showAlertDialog
import java.util.Collections
import androidx.core.net.toUri
import fr.free.nrw.commons.utils.handleWebUrl
import fr.free.nrw.commons.utils.setUnderlinedText
/**
* Represents about screen of this app
@ -59,30 +64,12 @@ class AboutActivity : BaseActivity() {
binding!!.aboutImprove.setHtmlText(improveText)
binding!!.aboutVersion.text = applicationContext.getVersionNameWithSha()
Utils.setUnderlinedText(
binding!!.aboutFaq, R.string.about_faq,
applicationContext
)
Utils.setUnderlinedText(
binding!!.aboutRateUs, R.string.about_rate_us,
applicationContext
)
Utils.setUnderlinedText(
binding!!.aboutUserGuide, R.string.user_guide,
applicationContext
)
Utils.setUnderlinedText(
binding!!.aboutPrivacyPolicy, R.string.about_privacy_policy,
applicationContext
)
Utils.setUnderlinedText(
binding!!.aboutTranslate, R.string.about_translate,
applicationContext
)
Utils.setUnderlinedText(
binding!!.aboutCredits, R.string.about_credits,
applicationContext
)
binding!!.aboutFaq.setUnderlinedText(R.string.about_faq)
binding!!.aboutRateUs.setUnderlinedText(R.string.about_rate_us)
binding!!.aboutUserGuide.setUnderlinedText(R.string.user_guide)
binding!!.aboutPrivacyPolicy.setUnderlinedText(R.string.about_privacy_policy)
binding!!.aboutTranslate.setUnderlinedText(R.string.about_translate)
binding!!.aboutCredits.setUnderlinedText(R.string.about_credits)
/*
To set listeners, we can create a separate method and use lambda syntax.
@ -106,47 +93,56 @@ class AboutActivity : BaseActivity() {
fun launchFacebook(view: View?) {
val intent: Intent
try {
intent = Intent(Intent.ACTION_VIEW, Uri.parse(Urls.FACEBOOK_APP_URL))
intent = Intent(ACTION_VIEW, Urls.FACEBOOK_APP_URL.toUri())
intent.setPackage(Urls.FACEBOOK_PACKAGE_NAME)
startActivity(intent)
} catch (e: Exception) {
Utils.handleWebUrl(this, Uri.parse(Urls.FACEBOOK_WEB_URL))
handleWebUrl(this, Urls.FACEBOOK_WEB_URL.toUri())
}
}
fun launchGithub(view: View?) {
val intent: Intent
try {
intent = Intent(Intent.ACTION_VIEW, Uri.parse(Urls.GITHUB_REPO_URL))
intent = Intent(ACTION_VIEW, Urls.GITHUB_REPO_URL.toUri())
intent.setPackage(Urls.GITHUB_PACKAGE_NAME)
startActivity(intent)
} catch (e: Exception) {
Utils.handleWebUrl(this, Uri.parse(Urls.GITHUB_REPO_URL))
handleWebUrl(this, Urls.GITHUB_REPO_URL.toUri())
}
}
fun launchWebsite(view: View?) {
Utils.handleWebUrl(this, Uri.parse(Urls.WEBSITE_URL))
handleWebUrl(this, Urls.WEBSITE_URL.toUri())
}
fun launchRatings(view: View?) {
Utils.rateApp(this)
try {
startActivity(
Intent(
ACTION_VIEW,
(Urls.PLAY_STORE_PREFIX + packageName).toUri()
)
)
} catch (_: ActivityNotFoundException) {
handleWebUrl(this, (Urls.PLAY_STORE_URL_PREFIX + packageName).toUri())
}
}
fun launchCredits(view: View?) {
Utils.handleWebUrl(this, Uri.parse(Urls.CREDITS_URL))
handleWebUrl(this, Urls.CREDITS_URL.toUri())
}
fun launchUserGuide(view: View?) {
Utils.handleWebUrl(this, Uri.parse(Urls.USER_GUIDE_URL))
handleWebUrl(this, Urls.USER_GUIDE_URL.toUri())
}
fun launchPrivacyPolicy(view: View?) {
Utils.handleWebUrl(this, Uri.parse(BuildConfig.PRIVACY_POLICY_URL))
handleWebUrl(this, BuildConfig.PRIVACY_POLICY_URL.toUri())
}
fun launchFrequentlyAskedQuesions(view: View?) {
Utils.handleWebUrl(this, Uri.parse(Urls.FAQ_URL))
handleWebUrl(this, Urls.FAQ_URL.toUri())
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
@ -193,7 +189,7 @@ class AboutActivity : BaseActivity() {
val positiveButtonRunnable = Runnable {
val langCode = instance.languageLookUpTable!!.getCodes()[spinner.selectedItemPosition]
Utils.handleWebUrl(this@AboutActivity, Uri.parse(Urls.TRANSLATE_WIKI_URL + langCode))
handleWebUrl(this@AboutActivity, (Urls.TRANSLATE_WIKI_URL + langCode).toUri())
}
showAlertDialog(
this,

View file

@ -1,18 +0,0 @@
package fr.free.nrw.commons;
import androidx.annotation.NonNull;
/**
* Base presenter, enforcing contracts to atach and detach view
*/
public interface BasePresenter<T> {
/**
* Until a view is attached, it is open to listen events from the presenter
*/
void onAttachView(@NonNull T view);
/**
* Detaching a view makes sure that the view no more receives events from the presenter
*/
void onDetachView();
}

View file

@ -0,0 +1,10 @@
package fr.free.nrw.commons
/**
* Base presenter, enforcing contracts to attach and detach view
*/
interface BasePresenter<T> {
fun onAttachView(view: T)
fun onDetachView()
}

View file

@ -1,79 +0,0 @@
package fr.free.nrw.commons;
import androidx.annotation.Nullable;
/**
* represents Licence object
*/
public class License {
private String key;
private String template;
private String url;
private String name;
/**
* Constructs a new instance of License.
*
* @param key license key
* @param template license template
* @param url license URL
* @param name licence name
*
* @throws RuntimeException if License.key or Licence.template is null
*/
public License(String key, String template, String url, String name) {
if (key == null) {
throw new RuntimeException("License.key must not be null");
}
if (template == null) {
throw new RuntimeException("License.template must not be null");
}
this.key = key;
this.template = template;
this.url = url;
this.name = name;
}
/**
* Gets the license key.
* @return license key as a String.
*/
public String getKey() {
return key;
}
/**
* Gets the license template.
* @return license template as a String.
*/
public String getTemplate() {
return template;
}
/**
* Gets the license name. If name is null, return license key.
* @return license name as string. if name null, license key as String
*/
public String getName() {
if (name == null) {
// hack
return getKey();
} else {
return name;
}
}
/**
* Gets the license URL
*
* @param language license language
* @return URL
*/
public @Nullable String getUrl(String language) {
if (url == null) {
return null;
} else {
return url.replace("$lang", language);
}
}
}

View file

@ -1,30 +0,0 @@
package fr.free.nrw.commons;
import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.nearby.Place;
import java.util.List;
public abstract class MapController {
/**
* We pass this variable as a group of placeList and boundaryCoordinates
*/
public class NearbyPlacesInfo {
public List<Place> placeList; // List of nearby places
public LatLng[] boundaryCoordinates; // Corners of nearby area
public LatLng currentLatLng; // Current location when this places are populated
public LatLng searchLatLng; // Search location for finding this places
public List<Media> mediaList; // Search location for finding this places
}
/**
* We pass this variable as a group of placeList and boundaryCoordinates
*/
public class ExplorePlacesInfo {
public List<Place> explorePlaceList; // List of nearby places
public LatLng[] boundaryCoordinates; // Corners of nearby area
public LatLng currentLatLng; // Current location when this places are populated
public LatLng searchLatLng; // Search location for finding this places
public List<Media> mediaList; // Search location for finding this places
}
}

View file

@ -0,0 +1,46 @@
package fr.free.nrw.commons
import fr.free.nrw.commons.location.LatLng
import fr.free.nrw.commons.nearby.Place
abstract class MapController {
/**
* We pass this variable as a group of placeList and boundaryCoordinates
*/
inner class NearbyPlacesInfo {
@JvmField
var placeList: List<Place> = emptyList() // List of nearby places
@JvmField
var boundaryCoordinates: Array<LatLng> = emptyArray() // Corners of nearby area
@JvmField
var currentLatLng: LatLng? = null // Current location when this places are populated
@JvmField
var searchLatLng: LatLng? = null // Search location for finding this places
@JvmField
var mediaList: List<Media>? = null // Search location for finding this places
}
/**
* We pass this variable as a group of placeList and boundaryCoordinates
*/
inner class ExplorePlacesInfo {
@JvmField
var explorePlaceList: List<Place> = emptyList() // List of nearby places
@JvmField
var boundaryCoordinates: Array<LatLng> = emptyArray() // Corners of nearby area
@JvmField
var currentLatLng: LatLng? = null // Current location when this places are populated
@JvmField
var searchLatLng: LatLng? = null // Search location for finding this places
@JvmField
var mediaList: List<Media> = emptyList() // Search location for finding this places
}
}

View file

@ -1,7 +1,9 @@
package fr.free.nrw.commons
import android.os.Parcelable
import fr.free.nrw.commons.BuildConfig.COMMONS_URL
import fr.free.nrw.commons.location.LatLng
import fr.free.nrw.commons.wikidata.model.WikiSite
import fr.free.nrw.commons.wikidata.model.page.PageTitle
import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize
@ -173,7 +175,8 @@ class Media constructor(
* Gets file page title
* @return New media page title
*/
val pageTitle: PageTitle get() = Utils.getPageTitle(filename!!)
val pageTitle: PageTitle
get() = PageTitle(filename!!, WikiSite(COMMONS_URL))
/**
* Returns wikicode to use the media file on a MediaWiki site

View file

@ -1,8 +0,0 @@
package fr.free.nrw.commons;
/**
* Base interface for all the views
*/
public interface MvpView {
void showMessage(String message);
}

View file

@ -1,154 +0,0 @@
package fr.free.nrw.commons;
import androidx.annotation.NonNull;
import fr.free.nrw.commons.wikidata.cookies.CommonsCookieJar;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import okhttp3.Cache;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.logging.HttpLoggingInterceptor;
import okhttp3.logging.HttpLoggingInterceptor.Level;
import timber.log.Timber;
public final class OkHttpConnectionFactory {
private static final String CACHE_DIR_NAME = "okhttp-cache";
private static final long NET_CACHE_SIZE = 64 * 1024 * 1024;
public static OkHttpClient CLIENT;
@NonNull public static OkHttpClient getClient(final CommonsCookieJar cookieJar) {
if (CLIENT == null) {
CLIENT = createClient(cookieJar);
}
return CLIENT;
}
@NonNull
private static OkHttpClient createClient(final CommonsCookieJar cookieJar) {
return new OkHttpClient.Builder()
.cookieJar(cookieJar)
.cache((CommonsApplication.getInstance()!=null) ? new Cache(new File(CommonsApplication.getInstance().getCacheDir(), CACHE_DIR_NAME), NET_CACHE_SIZE) : null)
.connectTimeout(120, TimeUnit.SECONDS)
.writeTimeout(120, TimeUnit.SECONDS)
.readTimeout(120, TimeUnit.SECONDS)
.addInterceptor(getLoggingInterceptor())
.addInterceptor(new UnsuccessfulResponseInterceptor())
.addInterceptor(new CommonHeaderRequestInterceptor())
.build();
}
private static HttpLoggingInterceptor getLoggingInterceptor() {
final HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor()
.setLevel(Level.BASIC);
httpLoggingInterceptor.redactHeader("Authorization");
httpLoggingInterceptor.redactHeader("Cookie");
return httpLoggingInterceptor;
}
private static class CommonHeaderRequestInterceptor implements Interceptor {
@Override
@NonNull
public Response intercept(@NonNull final Chain chain) throws IOException {
final Request request = chain.request().newBuilder()
.header("User-Agent", CommonsApplication.getInstance().getUserAgent())
.build();
return chain.proceed(request);
}
}
public static class UnsuccessfulResponseInterceptor implements Interceptor {
private static final String SUPPRESS_ERROR_LOG = "x-commons-suppress-error-log";
public static final String SUPPRESS_ERROR_LOG_HEADER = SUPPRESS_ERROR_LOG+": true";
private static final List<String> DO_NOT_INTERCEPT = Collections.singletonList(
"api.php?format=json&formatversion=2&errorformat=plaintext&action=upload&ignorewarnings=1");
private static final String ERRORS_PREFIX = "{\"error";
@Override
@NonNull
public Response intercept(@NonNull final Chain chain) throws IOException {
final Request rq = chain.request();
// If the request contains our special "suppress errors" header, make note of it
// but don't pass that on to the server.
final boolean suppressErrors = rq.headers().names().contains(SUPPRESS_ERROR_LOG);
final Request request = rq.newBuilder()
.removeHeader(SUPPRESS_ERROR_LOG)
.build();
final Response rsp = chain.proceed(request);
// Do not intercept certain requests and let the caller handle the errors
if(isExcludedUrl(chain.request())) {
return rsp;
}
if (rsp.isSuccessful()) {
try (final ResponseBody responseBody = rsp.peekBody(ERRORS_PREFIX.length())) {
if (ERRORS_PREFIX.equals(responseBody.string())) {
try (final ResponseBody body = rsp.body()) {
throw new IOException(body.string());
}
}
} catch (final IOException e) {
// Log the error as debug (and therefore, "expected") or at error level
if (suppressErrors) {
Timber.d(e, "Suppressed (known / expected) error");
} else {
Timber.e(e);
}
}
return rsp;
}
throw new HttpStatusException(rsp);
}
private boolean isExcludedUrl(final Request request) {
final String requestUrl = request.url().toString();
for(final String url: DO_NOT_INTERCEPT) {
if(requestUrl.contains(url)) {
return true;
}
}
return false;
}
}
private OkHttpConnectionFactory() {
}
public static class HttpStatusException extends IOException {
private final int code;
private final String url;
public HttpStatusException(@NonNull Response rsp) {
this.code = rsp.code();
this.url = rsp.request().url().uri().toString();
try {
if (rsp.body() != null && rsp.body().contentType() != null
&& rsp.body().contentType().toString().contains("json")) {
}
} catch (Exception e) {
// Log?
}
}
public int code() {
return code;
}
@Override
public String getMessage() {
String str = "Code: " + code + ", URL: " + url;
return str;
}
}
}

View file

@ -0,0 +1,122 @@
package fr.free.nrw.commons
import androidx.annotation.VisibleForTesting
import fr.free.nrw.commons.wikidata.cookies.CommonsCookieJar
import okhttp3.Cache
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import okhttp3.logging.HttpLoggingInterceptor
import timber.log.Timber
import java.io.File
import java.io.IOException
import java.util.concurrent.TimeUnit
object OkHttpConnectionFactory {
private const val CACHE_DIR_NAME = "okhttp-cache"
private const val NET_CACHE_SIZE = (64 * 1024 * 1024).toLong()
@VisibleForTesting
var CLIENT: OkHttpClient? = null
fun getClient(cookieJar: CommonsCookieJar): OkHttpClient {
if (CLIENT == null) {
CLIENT = createClient(cookieJar)
}
return CLIENT!!
}
private fun createClient(cookieJar: CommonsCookieJar): OkHttpClient {
return OkHttpClient.Builder()
.cookieJar(cookieJar)
.cache(
if (CommonsApplication.instance != null) Cache(
File(CommonsApplication.instance.cacheDir, CACHE_DIR_NAME),
NET_CACHE_SIZE
) else null
)
.connectTimeout(120, TimeUnit.SECONDS)
.writeTimeout(120, TimeUnit.SECONDS)
.readTimeout(120, TimeUnit.SECONDS)
.addInterceptor(HttpLoggingInterceptor().apply {
setLevel(HttpLoggingInterceptor.Level.BASIC)
redactHeader("Authorization")
redactHeader("Cookie")
})
.addInterceptor(UnsuccessfulResponseInterceptor())
.addInterceptor(CommonHeaderRequestInterceptor())
.build()
}
}
private class CommonHeaderRequestInterceptor : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request().newBuilder()
.header("User-Agent", CommonsApplication.instance.userAgent)
.build()
return chain.proceed(request)
}
}
private const val SUPPRESS_ERROR_LOG = "x-commons-suppress-error-log"
const val SUPPRESS_ERROR_LOG_HEADER: String = "$SUPPRESS_ERROR_LOG: true"
private class UnsuccessfulResponseInterceptor : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val rq = chain.request()
// If the request contains our special "suppress errors" header, make note of it
// but don't pass that on to the server.
val suppressErrors = rq.headers.names().contains(SUPPRESS_ERROR_LOG)
val request = rq.newBuilder()
.removeHeader(SUPPRESS_ERROR_LOG)
.build()
val rsp = chain.proceed(request)
// Do not intercept certain requests and let the caller handle the errors
if (isExcludedUrl(chain.request())) {
return rsp
}
if (rsp.isSuccessful) {
try {
rsp.peekBody(ERRORS_PREFIX.length.toLong()).use { responseBody ->
if (ERRORS_PREFIX == responseBody.string()) {
rsp.body.use { body ->
throw IOException(body!!.string())
}
}
}
} catch (e: IOException) {
// Log the error as debug (and therefore, "expected") or at error level
if (suppressErrors) {
Timber.d(e, "Suppressed (known / expected) error")
} else {
Timber.e(e)
}
}
return rsp
}
throw IOException("Unsuccessful response")
}
private fun isExcludedUrl(request: Request): Boolean {
val requestUrl = request.url.toString()
for (url in DO_NOT_INTERCEPT) {
if (requestUrl.contains(url)) {
return true
}
}
return false
}
companion object {
val DO_NOT_INTERCEPT = listOf(
"api.php?format=json&formatversion=2&errorformat=plaintext&action=upload&ignorewarnings=1"
)
const val ERRORS_PREFIX = "{\"error"
}
}

View file

@ -1,264 +0,0 @@
package fr.free.nrw.commons;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.text.SpannableString;
import android.text.style.UnderlineSpan;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.browser.customtabs.CustomTabColorSchemeParams;
import androidx.browser.customtabs.CustomTabsIntent;
import androidx.core.content.ContextCompat;
import java.util.Calendar;
import java.util.Date;
import fr.free.nrw.commons.wikidata.model.WikiSite;
import fr.free.nrw.commons.wikidata.model.page.PageTitle;
import java.util.Locale;
import java.util.regex.Pattern;
import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.settings.Prefs;
import fr.free.nrw.commons.utils.ViewUtil;
import timber.log.Timber;
public class Utils {
public static PageTitle getPageTitle(@NonNull String title) {
return new PageTitle(title, new WikiSite(BuildConfig.COMMONS_URL));
}
/**
* Generates licence name with given ID
* @param license License ID
* @return Name of license
*/
public static int licenseNameFor(String license) {
switch (license) {
case Prefs.Licenses.CC_BY_3:
return R.string.license_name_cc_by;
case Prefs.Licenses.CC_BY_4:
return R.string.license_name_cc_by_four;
case Prefs.Licenses.CC_BY_SA_3:
return R.string.license_name_cc_by_sa;
case Prefs.Licenses.CC_BY_SA_4:
return R.string.license_name_cc_by_sa_four;
case Prefs.Licenses.CC0:
return R.string.license_name_cc0;
}
throw new IllegalStateException("Unrecognized license value: " + license);
}
/**
* Generates license url with given ID
* @param license License ID
* @return Url of license
*/
@NonNull
public static String licenseUrlFor(String license) {
switch (license) {
case Prefs.Licenses.CC_BY_3:
return "https://creativecommons.org/licenses/by/3.0/";
case Prefs.Licenses.CC_BY_4:
return "https://creativecommons.org/licenses/by/4.0/";
case Prefs.Licenses.CC_BY_SA_3:
return "https://creativecommons.org/licenses/by-sa/3.0/";
case Prefs.Licenses.CC_BY_SA_4:
return "https://creativecommons.org/licenses/by-sa/4.0/";
case Prefs.Licenses.CC0:
return "https://creativecommons.org/publicdomain/zero/1.0/";
default:
throw new IllegalStateException("Unrecognized license value: " + license);
}
}
/**
* Adds extension to filename. Converts to .jpg if system provides .jpeg, adds .jpg if no extension detected
* @param title File name
* @param extension Correct extension
* @return File with correct extension
*/
public static String fixExtension(String title, String extension) {
Pattern jpegPattern = Pattern.compile("\\.jpeg$", Pattern.CASE_INSENSITIVE);
// People are used to ".jpg" more than ".jpeg" which the system gives us.
if (extension != null && extension.toLowerCase(Locale.ENGLISH).equals("jpeg")) {
extension = "jpg";
}
title = jpegPattern.matcher(title).replaceFirst(".jpg");
if (extension != null && !title.toLowerCase(Locale.getDefault())
.endsWith("." + extension.toLowerCase(Locale.ENGLISH))) {
title += "." + 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 && title.lastIndexOf(".")<=0) {
extension = "jpg";
title += "." + extension;
}
return title;
}
/**
* Launches intent to rate app
* @param context
*/
public static void rateApp(Context context) {
final String appPackageName = context.getPackageName();
try {
context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(Urls.PLAY_STORE_PREFIX + appPackageName)));
}
catch (android.content.ActivityNotFoundException anfe) {
handleWebUrl(context, Uri.parse(Urls.PLAY_STORE_URL_PREFIX + appPackageName));
}
}
/**
* Opens Custom Tab Activity with in-app browser for the specified URL.
* Launches intent for web URL
* @param context
* @param url
*/
public static void handleWebUrl(Context context, Uri url) {
Timber.d("Launching web url %s", url.toString());
final CustomTabColorSchemeParams color = new CustomTabColorSchemeParams.Builder()
.setToolbarColor(ContextCompat.getColor(context, R.color.primaryColor))
.setSecondaryToolbarColor(ContextCompat.getColor(context, R.color.primaryDarkColor))
.build();
CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
builder.setDefaultColorSchemeParams(color);
builder.setExitAnimations(context, android.R.anim.slide_in_left, android.R.anim.slide_out_right);
CustomTabsIntent customTabsIntent = builder.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);
}
/**
* Util function to handle geo coordinates. 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
*/
public static void handleGeoCoordinates(final Context context, final LatLng latLng) {
handleGeoCoordinates(context, latLng, 16);
}
/**
* 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
*/
public static void handleGeoCoordinates(final Context context, final LatLng latLng,
final double zoomLevel) {
final Intent mapIntent = new Intent(Intent.ACTION_VIEW, latLng.getGmmIntentUri(zoomLevel));
if (mapIntent.resolveActivity(context.getPackageManager()) != null) {
context.startActivity(mapIntent);
} else {
ViewUtil.showShortToast(context, context.getString(R.string.map_application_missing));
}
}
/**
* To take screenshot of the screen and return it in Bitmap format
*
* @param view
* @return
*/
public static Bitmap getScreenShot(View view) {
View screenView = view.getRootView();
screenView.setDrawingCacheEnabled(true);
Bitmap drawingCache = screenView.getDrawingCache();
if (drawingCache != null) {
Bitmap bitmap = Bitmap.createBitmap(drawingCache);
screenView.setDrawingCacheEnabled(false);
return bitmap;
}
return null;
}
/*
*Copies the content to the clipboard
*
*/
public static void copy(String label,String text, Context context){
ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText(label, text);
clipboard.setPrimaryClip(clip);
}
/**
* This method sets underlined string text to a TextView
*
* @param textView TextView associated with string resource
* @param stringResourceName string resource name
* @param context
*/
public static void setUnderlinedText(TextView textView, int stringResourceName, Context context) {
SpannableString content = new SpannableString(context.getString(stringResourceName));
content.setSpan(new UnderlineSpan(), 0, content.length(), 0);
textView.setText(content);
}
/**
* For now we are enabling the monuments only when the date lies between 1 Sept & 31 OCt
* @param date
* @return
*/
public static boolean isMonumentsEnabled(final Date date) {
if (date.getMonth() == 8) {
return true;
}
return false;
}
/**
* Util function to get the start date of wlm monument
* For this release we are hardcoding it to be 1st September
* @return
*/
public static String getWLMStartDate() {
return "1 Sep";
}
/***
* Util function to get the end date of wlm monument
* For this release we are hardcoding it to be 31st October
* @return
*/
public static String getWLMEndDate() {
return "30 Sep";
}
/***
* 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
*/
public static int getWikiLovesMonumentsYear(Calendar calendar) {
int year = calendar.get(Calendar.YEAR);
if (calendar.get(Calendar.MONTH) < Calendar.SEPTEMBER) {
year -= 1;
}
return year;
}
}

View file

@ -1,7 +0,0 @@
package fr.free.nrw.commons;
import android.content.Context;
public interface ViewHolder<T> {
void bindModel(Context context, T model);
}

View file

@ -1,68 +0,0 @@
package fr.free.nrw.commons;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import java.util.ArrayList;
import java.util.List;
/**
* This adapter will be used to display fragments in a ViewPager
*/
public class ViewPagerAdapter extends FragmentPagerAdapter {
private List<Fragment> fragmentList = new ArrayList<>();
private List<String> fragmentTitleList = new ArrayList<>();
public ViewPagerAdapter(FragmentManager manager) {
super(manager);
}
/**
* Constructs a ViewPagerAdapter with a specified Fragment Manager and Fragment resume behavior.
*
* @param manager The FragmentManager
* @param behavior An integer which represents the behavior of non visible fragments. See
* FragmentPagerAdapter.java for options.
*/
public ViewPagerAdapter(FragmentManager manager, int behavior) {
super(manager, behavior);
}
/**
* This method returns the fragment of the viewpager at a particular position
* @param position
*/
@Override
public Fragment getItem(int position) {
return fragmentList.get(position);
}
/**
* This method returns the total number of fragments in the viewpager.
* @return size
*/
@Override
public int getCount() {
return fragmentList.size();
}
/**
* This method sets the fragment and title list in the viewpager
* @param fragmentList List of all fragments to be displayed in the viewpager
* @param fragmentTitleList List of all titles of the fragments
*/
public void setTabData(List<Fragment> fragmentList, List<String> fragmentTitleList) {
this.fragmentList = fragmentList;
this.fragmentTitleList = fragmentTitleList;
}
/**
* This method returns the title of the page at a particular position
* @param position
*/
@Override
public CharSequence getPageTitle(int position) {
return fragmentTitleList.get(position);
}
}

View file

@ -0,0 +1,44 @@
package fr.free.nrw.commons
import android.content.Context
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentPagerAdapter
import java.util.Locale
/**
* This adapter will be used to display fragments in a ViewPager
*/
class ViewPagerAdapter : FragmentPagerAdapter {
private val context: Context
private var fragmentList: List<Fragment> = emptyList()
private var fragmentTitleList: List<String> = emptyList()
constructor(context: Context, manager: FragmentManager) : super(manager) {
this.context = context
}
constructor(context: Context, manager: FragmentManager, behavior: Int) : super(manager, behavior) {
this.context = context
}
override fun getItem(position: Int): Fragment = fragmentList[position]
override fun getPageTitle(position: Int): CharSequence = fragmentTitleList[position]
override fun getCount(): Int = fragmentList.size
fun setTabs(vararg titlesToFragments: Pair<Int, Fragment>) {
// Enforce that every title must come from strings.xml and all will consistently be uppercase
fragmentTitleList = titlesToFragments.map {
context.getString(it.first).uppercase(Locale.ROOT)
}
fragmentList = titlesToFragments.map { it.second }
}
companion object {
// Convenience method for Java callers, can be removed when everything is migrated
@JvmStatic
fun pairOf(first: Int, second: Fragment) = first to second
}
}

View file

@ -1,109 +0,0 @@
package fr.free.nrw.commons;
import android.app.AlertDialog;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import fr.free.nrw.commons.databinding.ActivityWelcomeBinding;
import fr.free.nrw.commons.databinding.PopupForCopyrightBinding;
import fr.free.nrw.commons.quiz.QuizActivity;
import fr.free.nrw.commons.theme.BaseActivity;
import fr.free.nrw.commons.utils.ConfigUtils;
public class WelcomeActivity extends BaseActivity {
private ActivityWelcomeBinding binding;
private PopupForCopyrightBinding copyrightBinding;
private final WelcomePagerAdapter adapter = new WelcomePagerAdapter();
private boolean isQuiz;
private AlertDialog.Builder dialogBuilder;
private AlertDialog dialog;
/**
* Initialises exiting fields and dependencies
*
* @param savedInstanceState WelcomeActivity bundled data
*/
@Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityWelcomeBinding.inflate(getLayoutInflater());
final View view = binding.getRoot();
setContentView(view);
if (getIntent() != null) {
final Bundle bundle = getIntent().getExtras();
if (bundle != null) {
isQuiz = bundle.getBoolean("isQuiz");
}
} else {
isQuiz = false;
}
// Enable skip button if beta flavor
if (ConfigUtils.isBetaFlavour()) {
binding.finishTutorialButton.setVisibility(View.VISIBLE);
dialogBuilder = new AlertDialog.Builder(this);
copyrightBinding = PopupForCopyrightBinding.inflate(getLayoutInflater());
final View contactPopupView = copyrightBinding.getRoot();
dialogBuilder.setView(contactPopupView);
dialogBuilder.setCancelable(false);
dialog = dialogBuilder.create();
dialog.show();
copyrightBinding.buttonOk.setOnClickListener(v -> dialog.dismiss());
}
binding.welcomePager.setAdapter(adapter);
binding.welcomePagerIndicator.setViewPager(binding.welcomePager);
binding.finishTutorialButton.setOnClickListener(v -> finishTutorial());
}
/**
* References WelcomePageAdapter to null before the activity is destroyed
*/
@Override
public void onDestroy() {
if (isQuiz) {
final Intent i = new Intent(this, QuizActivity.class);
startActivity(i);
}
super.onDestroy();
}
/**
* Creates a way to change current activity to WelcomeActivity
*
* @param context Activity context
*/
public static void startYourself(final Context context) {
final Intent welcomeIntent = new Intent(context, WelcomeActivity.class);
context.startActivity(welcomeIntent);
}
/**
* Override onBackPressed() to go to previous tutorial 'pages' if not on first page
*/
@Override
public void onBackPressed() {
if (binding.welcomePager.getCurrentItem() != 0) {
binding.welcomePager.setCurrentItem(binding.welcomePager.getCurrentItem() - 1, true);
} else {
if (defaultKvStore.getBoolean("firstrun", true)) {
finishAffinity();
} else {
super.onBackPressed();
}
}
}
public void finishTutorial() {
defaultKvStore.putBoolean("firstrun", false);
finish();
}
}

View file

@ -0,0 +1,78 @@
package fr.free.nrw.commons
import android.app.AlertDialog
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.View
import fr.free.nrw.commons.databinding.ActivityWelcomeBinding
import fr.free.nrw.commons.databinding.PopupForCopyrightBinding
import fr.free.nrw.commons.quiz.QuizActivity
import fr.free.nrw.commons.theme.BaseActivity
import fr.free.nrw.commons.utils.ConfigUtils.isBetaFlavour
class WelcomeActivity : BaseActivity() {
private var binding: ActivityWelcomeBinding? = null
private var isQuiz = false
/**
* Initialises exiting fields and dependencies
*
* @param savedInstanceState WelcomeActivity bundled data
*/
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityWelcomeBinding.inflate(layoutInflater)
setContentView(binding!!.root)
isQuiz = intent?.extras?.getBoolean("isQuiz", false) ?: false
// Enable skip button if beta flavor
if (isBetaFlavour) {
binding!!.finishTutorialButton.visibility = View.VISIBLE
val copyrightBinding = PopupForCopyrightBinding.inflate(layoutInflater)
val dialog = AlertDialog.Builder(this)
.setView(copyrightBinding.root)
.setCancelable(false)
.create()
dialog.show()
copyrightBinding.buttonOk.setOnClickListener { v: View? -> dialog.dismiss() }
}
val adapter = WelcomePagerAdapter()
binding!!.welcomePager.adapter = adapter
binding!!.welcomePagerIndicator.setViewPager(binding!!.welcomePager)
binding!!.finishTutorialButton.setOnClickListener { v: View? -> finishTutorial() }
}
public override fun onDestroy() {
if (isQuiz) {
startActivity(Intent(this, QuizActivity::class.java))
}
super.onDestroy()
}
override fun onBackPressed() {
if (binding!!.welcomePager.currentItem != 0) {
binding!!.welcomePager.setCurrentItem(binding!!.welcomePager.currentItem - 1, true)
} else {
if (defaultKvStore.getBoolean("firstrun", true)) {
finishAffinity()
} else {
super.onBackPressed()
}
}
}
fun finishTutorial() {
defaultKvStore.putBoolean("firstrun", false)
finish()
}
}
fun Context.startWelcome() {
startActivity(Intent(this, WelcomeActivity::class.java))
}

View file

@ -1,74 +0,0 @@
package fr.free.nrw.commons;
import android.net.Uri;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.viewpager.widget.PagerAdapter;
public class WelcomePagerAdapter extends PagerAdapter {
private static final int[] PAGE_LAYOUTS = new int[]{
R.layout.welcome_wikipedia,
R.layout.welcome_do_upload,
R.layout.welcome_dont_upload,
R.layout.welcome_image_example,
R.layout.welcome_final
};
/**
* Gets total number of layouts
* @return Number of layouts
*/
@Override
public int getCount() {
return PAGE_LAYOUTS.length;
}
/**
* Compares given view with provided object
* @param view Adapter view
* @param object Adapter object
* @return Equality between view and object
*/
@Override
public boolean isViewFromObject(View view, Object object) {
return (view == object);
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
LayoutInflater inflater = LayoutInflater.from(container.getContext());
ViewGroup layout = (ViewGroup) inflater.inflate(PAGE_LAYOUTS[position], container, false);
// If final page
if (position == PAGE_LAYOUTS.length - 1) {
// Add link to more information
TextView moreInfo = layout.findViewById(R.id.welcomeInfo);
Utils.setUnderlinedText(moreInfo, R.string.welcome_help_button_text, container.getContext());
moreInfo.setOnClickListener(view -> Utils.handleWebUrl(
container.getContext(),
Uri.parse("https://commons.wikimedia.org/wiki/Help:Contents")
));
// Handle click of finishTutorialButton ("YES!" button) inside layout
layout.findViewById(R.id.finishTutorialButton)
.setOnClickListener(view -> ((WelcomeActivity) container.getContext()).finishTutorial());
}
container.addView(layout);
return layout;
}
/**
* Provides a way to remove an item from container
* @param container Adapter view group container
* @param position Index of item
* @param obj Adapter object
*/
@Override
public void destroyItem(ViewGroup container, int position, Object obj) {
container.removeView((View) obj);
}
}

View file

@ -0,0 +1,70 @@
package fr.free.nrw.commons
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.core.net.toUri
import androidx.viewpager.widget.PagerAdapter
import fr.free.nrw.commons.utils.UnderlineUtils.setUnderlinedText
import fr.free.nrw.commons.utils.handleWebUrl
class WelcomePagerAdapter : PagerAdapter() {
/**
* Gets total number of layouts
* @return Number of layouts
*/
override fun getCount(): Int = PAGE_LAYOUTS.size
/**
* Compares given view with provided object
* @param view Adapter view
* @param obj Adapter object
* @return Equality between view and object
*/
override fun isViewFromObject(view: View, obj: Any): Boolean = (view === obj)
/**
* Provides a way to remove an item from container
* @param container Adapter view group container
* @param position Index of item
* @param obj Adapter object
*/
override fun destroyItem(container: ViewGroup, position: Int, obj: Any) =
container.removeView(obj as View)
override fun instantiateItem(container: ViewGroup, position: Int): Any {
val inflater = LayoutInflater.from(container.context)
val layout = inflater.inflate(PAGE_LAYOUTS[position], container, false) as ViewGroup
// If final page
if (position == PAGE_LAYOUTS.size - 1) {
// Add link to more information
val moreInfo = layout.findViewById<TextView>(R.id.welcomeInfo)
setUnderlinedText(moreInfo, R.string.welcome_help_button_text)
moreInfo.setOnClickListener {
handleWebUrl(
container.context,
"https://commons.wikimedia.org/wiki/Help:Contents".toUri()
)
}
// Handle click of finishTutorialButton ("YES!" button) inside layout
layout.findViewById<View>(R.id.finishTutorialButton)
.setOnClickListener { view: View? -> (container.context as WelcomeActivity).finishTutorial() }
}
container.addView(layout)
return layout
}
companion object {
private val PAGE_LAYOUTS = intArrayOf(
R.layout.welcome_wikipedia,
R.layout.welcome_do_upload,
R.layout.welcome_dont_upload,
R.layout.welcome_image_example,
R.layout.welcome_final
)
}
}

View file

@ -25,7 +25,6 @@ import androidx.core.content.ContextCompat
import fr.free.nrw.commons.BuildConfig
import fr.free.nrw.commons.CommonsApplication
import fr.free.nrw.commons.R
import fr.free.nrw.commons.Utils
import fr.free.nrw.commons.auth.login.LoginCallback
import fr.free.nrw.commons.auth.login.LoginClient
import fr.free.nrw.commons.auth.login.LoginResult
@ -38,6 +37,7 @@ import fr.free.nrw.commons.utils.ActivityUtils.startActivityWithFlags
import fr.free.nrw.commons.utils.ConfigUtils.isBetaFlavour
import fr.free.nrw.commons.utils.SystemThemeUtils
import fr.free.nrw.commons.utils.ViewUtil.hideKeyboard
import fr.free.nrw.commons.utils.handleWebUrl
import io.reactivex.disposables.CompositeDisposable
import timber.log.Timber
import java.util.Locale
@ -254,10 +254,10 @@ class LoginActivity : AccountAuthenticatorActivity() {
}
private fun forgotPassword() =
Utils.handleWebUrl(this, Uri.parse(BuildConfig.FORGOT_PASSWORD_URL))
handleWebUrl(this, Uri.parse(BuildConfig.FORGOT_PASSWORD_URL))
private fun onPrivacyPolicyClicked() =
Utils.handleWebUrl(this, Uri.parse(BuildConfig.PRIVACY_POLICY_URL))
handleWebUrl(this, Uri.parse(BuildConfig.PRIVACY_POLICY_URL))
private fun signUp() =
startActivity(Intent(this, SignupActivity::class.java))

View file

@ -7,7 +7,6 @@ import android.view.LayoutInflater
import android.view.View
import androidx.core.content.ContextCompat
import fr.free.nrw.commons.R
import fr.free.nrw.commons.Utils
import fr.free.nrw.commons.campaigns.models.Campaign
import fr.free.nrw.commons.contributions.MainActivity
import fr.free.nrw.commons.databinding.LayoutCampaginBinding
@ -16,6 +15,7 @@ import fr.free.nrw.commons.utils.CommonsDateUtil.getIso8601DateFormatShort
import fr.free.nrw.commons.utils.DateUtil.getExtraShortDateString
import fr.free.nrw.commons.utils.SwipableCardView
import fr.free.nrw.commons.utils.ViewUtil.showLongToast
import fr.free.nrw.commons.utils.handleWebUrl
import timber.log.Timber
import java.text.ParseException
@ -74,7 +74,7 @@ class CampaignView : SwipableCardView {
if (it.isWLMCampaign) {
((context) as MainActivity).showNearby()
} else {
Utils.handleWebUrl(context, Uri.parse(it.link))
handleWebUrl(context, Uri.parse(it.link))
}
}
}

View file

@ -26,7 +26,7 @@ class CampaignsPresenter @Inject constructor(
private val okHttpJsonApiClient: OkHttpJsonApiClient?,
@param:Named(IO_THREAD) private val ioScheduler: Scheduler,
@param:Named(MAIN_THREAD) private val mainThreadScheduler: Scheduler
) : BasePresenter<ICampaignsView?> {
) : BasePresenter<ICampaignsView> {
private var view: ICampaignsView? = null
private var disposable: Disposable? = null
private var campaign: Campaign? = null

View file

@ -1,11 +1,10 @@
package fr.free.nrw.commons.campaigns
import fr.free.nrw.commons.MvpView
import fr.free.nrw.commons.campaigns.models.Campaign
/**
* Interface which defines the view contracts of the campaign view
*/
interface ICampaignsView : MvpView {
interface ICampaignsView {
fun showCampaigns(campaign: Campaign?)
}

View file

@ -13,9 +13,9 @@ import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import fr.free.nrw.commons.BuildConfig.COMMONS_URL
import fr.free.nrw.commons.Media
import fr.free.nrw.commons.R
import fr.free.nrw.commons.Utils
import fr.free.nrw.commons.ViewPagerAdapter
import fr.free.nrw.commons.databinding.ActivityCategoryDetailsBinding
import fr.free.nrw.commons.explore.categories.media.CategoriesMediaFragment
@ -23,6 +23,9 @@ import fr.free.nrw.commons.explore.categories.parent.ParentCategoriesFragment
import fr.free.nrw.commons.explore.categories.sub.SubCategoriesFragment
import fr.free.nrw.commons.media.MediaDetailPagerFragment
import fr.free.nrw.commons.theme.BaseActivity
import fr.free.nrw.commons.utils.handleWebUrl
import fr.free.nrw.commons.wikidata.model.WikiSite
import fr.free.nrw.commons.wikidata.model.page.PageTitle
import kotlinx.coroutines.launch
import javax.inject.Inject
@ -56,7 +59,7 @@ class CategoryDetailsActivity : BaseActivity(),
val view = binding.root
setContentView(view)
supportFragmentManager = getSupportFragmentManager()
viewPagerAdapter = ViewPagerAdapter(supportFragmentManager)
viewPagerAdapter = ViewPagerAdapter(this, supportFragmentManager)
binding.viewPager.adapter = viewPagerAdapter
binding.viewPager.offscreenPageLimit = 2
binding.tabLayout.setupWithViewPager(binding.viewPager)
@ -80,8 +83,6 @@ class CategoryDetailsActivity : BaseActivity(),
* Set the fragments according to the tab selected in the viewPager.
*/
private fun setTabs() {
val fragmentList = mutableListOf<Fragment>()
val titleList = mutableListOf<String>()
categoriesMediaFragment = CategoriesMediaFragment()
val subCategoryListFragment = SubCategoriesFragment()
val parentCategoriesFragment = ParentCategoriesFragment()
@ -96,13 +97,12 @@ class CategoryDetailsActivity : BaseActivity(),
viewModel.onCheckIfBookmarked(categoryName!!)
}
fragmentList.add(categoriesMediaFragment)
titleList.add("MEDIA")
fragmentList.add(subCategoryListFragment)
titleList.add("SUBCATEGORIES")
fragmentList.add(parentCategoriesFragment)
titleList.add("PARENT CATEGORIES")
viewPagerAdapter.setTabData(fragmentList, titleList)
viewPagerAdapter.setTabs(
R.string.title_for_media to categoriesMediaFragment,
R.string.title_for_subcategories to subCategoryListFragment,
R.string.title_for_parent_categories to parentCategoriesFragment
)
viewPagerAdapter.notifyDataSetChanged()
}
@ -199,8 +199,9 @@ class CategoryDetailsActivity : BaseActivity(),
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.menu_browser_current_category -> {
val title = Utils.getPageTitle(CATEGORY_PREFIX + categoryName)
Utils.handleWebUrl(this, Uri.parse(title.canonicalUri))
val title = PageTitle(CATEGORY_PREFIX + categoryName, WikiSite(COMMONS_URL))
handleWebUrl(this, Uri.parse(title.canonicalUri))
true
}

View file

@ -9,7 +9,6 @@ import fr.free.nrw.commons.BasePresenter
interface ContributionsContract {
interface View {
fun showMessage(localizedMessage: String)
fun getContext(): Context?
}

View file

@ -30,7 +30,6 @@ import androidx.work.WorkManager
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.auth.SessionManager
import fr.free.nrw.commons.campaigns.CampaignView
import fr.free.nrw.commons.campaigns.CampaignsPresenter
@ -64,6 +63,9 @@ import fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween
import fr.free.nrw.commons.utils.NetworkUtils.isInternetConnectionEstablished
import fr.free.nrw.commons.utils.PermissionUtils.hasPermission
import fr.free.nrw.commons.utils.ViewUtil.showLongToast
import fr.free.nrw.commons.utils.isMonumentsEnabled
import fr.free.nrw.commons.utils.wLMEndDate
import fr.free.nrw.commons.utils.wLMStartDate
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
@ -242,8 +244,8 @@ class ContributionsFragment : CommonsDaggerSupportFragment(), FragmentManager.On
private fun initWLMCampaign() {
wlmCampaign = Campaign(
getString(R.string.wlm_campaign_title),
getString(R.string.wlm_campaign_description), Utils.getWLMStartDate().toString(),
Utils.getWLMEndDate().toString(), NearbyParentFragment.WLM_URL, true
getString(R.string.wlm_campaign_description), wLMStartDate,
wLMEndDate, NearbyParentFragment.WLM_URL, true
)
}
@ -729,7 +731,7 @@ class ContributionsFragment : CommonsDaggerSupportFragment(), FragmentManager.On
* of campaigns on the campaigns card
*/
private fun fetchCampaigns() {
if (Utils.isMonumentsEnabled(Date())) {
if (isMonumentsEnabled) {
if (binding != null) {
binding!!.campaignsView.setCampaign(wlmCampaign)
binding!!.campaignsView.visibility = View.VISIBLE
@ -743,10 +745,6 @@ class ContributionsFragment : CommonsDaggerSupportFragment(), FragmentManager.On
}
}
override fun showMessage(message: String) {
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
}
override fun showCampaigns(campaign: Campaign?) {
if (campaign != null && !isUserProfile) {
if (binding != null) {

View file

@ -15,7 +15,7 @@ class ContributionsListContract {
fun showNoContributionsUI(shouldShow: Boolean)
}
interface UserActionListener : BasePresenter<View?> {
interface UserActionListener : BasePresenter<View> {
fun refreshList(swipeRefreshLayout: SwipeRefreshLayout?)
}
}

View file

@ -29,7 +29,6 @@ import androidx.recyclerview.widget.SimpleItemAnimator
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.auth.SessionManager
import fr.free.nrw.commons.contributions.WikipediaInstructionsDialogFragment.Companion.newInstance
import fr.free.nrw.commons.databinding.FragmentContributionsListBinding
@ -41,6 +40,8 @@ import fr.free.nrw.commons.profile.ProfileActivity
import fr.free.nrw.commons.utils.DialogUtil.showAlertDialog
import fr.free.nrw.commons.utils.SystemThemeUtils
import fr.free.nrw.commons.utils.ViewUtil.showShortToast
import fr.free.nrw.commons.utils.copyToClipboard
import fr.free.nrw.commons.utils.handleWebUrl
import fr.free.nrw.commons.wikidata.model.WikiSite
import org.apache.commons.lang3.StringUtils
import javax.inject.Inject
@ -527,14 +528,13 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL
*/
override fun onConfirmClicked(contribution: Contribution?, copyWikicode: Boolean) {
if (copyWikicode) {
val wikicode = contribution!!.media.wikiCode
Utils.copy("wikicode", wikicode, context)
requireContext().copyToClipboard("wikicode", contribution!!.media.wikiCode)
}
val url =
languageWikipediaSite!!.mobileUrl() + "/wiki/" + (contribution!!.wikidataPlace
?.getWikipediaPageTitle())
Utils.handleWebUrl(context, Uri.parse(url))
handleWebUrl(requireContext(), Uri.parse(url))
}
fun getContributionStateAt(position: Int): Int {

View file

@ -12,7 +12,6 @@ import androidx.fragment.app.FragmentManager
import androidx.work.ExistingWorkPolicy
import com.google.android.material.bottomnavigation.BottomNavigationView
import fr.free.nrw.commons.R
import fr.free.nrw.commons.WelcomeActivity
import fr.free.nrw.commons.auth.SessionManager
import fr.free.nrw.commons.bookmarks.BookmarkFragment
import fr.free.nrw.commons.contributions.ContributionsFragment.Companion.newInstance
@ -33,6 +32,7 @@ import fr.free.nrw.commons.notification.NotificationActivity.Companion.startYour
import fr.free.nrw.commons.notification.NotificationController
import fr.free.nrw.commons.quiz.QuizChecker
import fr.free.nrw.commons.settings.SettingsFragment
import fr.free.nrw.commons.startWelcome
import fr.free.nrw.commons.theme.BaseActivity
import fr.free.nrw.commons.upload.UploadProgressActivity
import fr.free.nrw.commons.upload.worker.WorkRequestHelper.Companion.makeOneTimeWorkRequest
@ -517,7 +517,7 @@ after opening the app.
(!applicationKvStore!!.getBoolean("login_skipped"))
) {
defaultKvStore.putBoolean("inAppCameraFirstRun", true)
WelcomeActivity.startYourself(this)
startWelcome()
}
retryAllFailedUploads()

View file

@ -53,7 +53,6 @@ class DeleteHelper @Inject constructor(
media: Media?,
reason: String?
): Single<Boolean>? {
if(context == null && media == null) {
return null
}
@ -86,7 +85,6 @@ class DeleteHelper @Inject constructor(
* @return
*/
private fun delete(media: Media, reason: String): Observable<Boolean> {
Timber.d("thread is delete %s", Thread.currentThread().name)
val summary = "Nominating ${media.filename} for deletion."
val calendar = Calendar.getInstance()
val fileDeleteString = """

View file

@ -2,21 +2,19 @@ package fr.free.nrw.commons.delete
import android.annotation.SuppressLint
import android.content.Context
import fr.free.nrw.commons.utils.DateUtil
import java.util.Locale
import javax.inject.Inject
import javax.inject.Singleton
import fr.free.nrw.commons.Media
import fr.free.nrw.commons.R
import fr.free.nrw.commons.profile.achievements.FeedbackResponse
import fr.free.nrw.commons.auth.SessionManager
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient
import fr.free.nrw.commons.utils.DateUtil
import fr.free.nrw.commons.utils.ViewUtilWrapper
import io.reactivex.Single
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.rx2.rxSingle
import timber.log.Timber
import java.util.Locale
import javax.inject.Inject
import javax.inject.Singleton
/**
* This class handles the reason for deleting a Media object
@ -29,6 +27,8 @@ class ReasonBuilder @Inject constructor(
private val viewUtilWrapper: ViewUtilWrapper
) {
private val defaultFileUsagePageSize = 10
/**
* To process the reason and append the media's upload date and uploaded_by_me string
* @param media
@ -39,7 +39,7 @@ class ReasonBuilder @Inject constructor(
if (media == null || reason == null) {
return Single.just("Not known")
}
return fetchArticleNumber(media, reason)
return getAndAppendFileUsage(media, reason)
}
/**
@ -54,27 +54,36 @@ class ReasonBuilder @Inject constructor(
}
}
private fun fetchArticleNumber(media: Media, reason: String): Single<String> {
return if (checkAccount()) {
okHttpJsonApiClient
.getAchievements(sessionManager.userName)
.map { feedbackResponse -> appendArticlesUsed(feedbackResponse, media, reason) }
} else {
Single.just("")
private fun getAndAppendFileUsage(media: Media, reason: String): Single<String> {
return rxSingle(context = Dispatchers.IO) {
if (!checkAccount()) return@rxSingle ""
try {
val globalFileUsage = okHttpJsonApiClient.getGlobalFileUsages(
fileName = media.filename,
pageSize = defaultFileUsagePageSize
)
val globalUsages = globalFileUsage?.query?.pages?.sumOf { it.fileUsage.size } ?: 0
appendArticlesUsed(globalUsages, media, reason)
} catch (e: Exception) {
Timber.e(e, "Error fetching file usage")
throw e
}
}
}
/**
* Takes the uploaded_by_me string, the upload date, name of articles using images
* Takes the uploaded_by_me string, the upload date, no. of articles using images
* and appends it to the received reason
* @param feedBack object
* @param fileUsages No. of files/articles using this image
* @param media whose upload data is to be fetched
* @param reason
* @param reason string to be appended
*/
@SuppressLint("StringFormatInvalid")
private fun appendArticlesUsed(feedBack: FeedbackResponse, media: Media, reason: String): String {
private fun appendArticlesUsed(fileUsages: Int, media: Media, reason: String): String {
val reason1Template = context.getString(R.string.uploaded_by_myself)
return reason + String.format(Locale.getDefault(), reason1Template, prettyUploadedDate(media), feedBack.articlesUsingImages)
return reason + String.format(Locale.getDefault(), reason1Template, prettyUploadedDate(media), fileUsages)
.also { Timber.i("New Reason %s", it) }
}

View file

@ -1,6 +1,7 @@
package fr.free.nrw.commons.explore;
import static androidx.viewpager.widget.ViewPager.SCROLL_STATE_IDLE;
import static fr.free.nrw.commons.ViewPagerAdapter.pairOf;
import android.os.Bundle;
import android.view.LayoutInflater;
@ -23,10 +24,12 @@ import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.theme.BaseActivity;
import fr.free.nrw.commons.utils.ActivityUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import javax.inject.Inject;
import javax.inject.Named;
import kotlin.Pair;
public class ExploreFragment extends CommonsDaggerSupportFragment {
@ -70,7 +73,7 @@ public class ExploreFragment extends CommonsDaggerSupportFragment {
loadNearbyMapData();
binding = FragmentExploreBinding.inflate(inflater, container, false);
viewPagerAdapter = new ViewPagerAdapter(getChildFragmentManager(),
viewPagerAdapter = new ViewPagerAdapter(requireContext(), getChildFragmentManager(),
FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
binding.viewPager.setAdapter(viewPagerAdapter);
@ -111,9 +114,6 @@ public class ExploreFragment extends CommonsDaggerSupportFragment {
* Sets the titles in the tabLayout and fragments in the viewPager
*/
public void setTabs() {
List<Fragment> fragmentList = new ArrayList<>();
List<String> titleList = new ArrayList<>();
Bundle featuredArguments = new Bundle();
featuredArguments.putString("categoryName", FEATURED_IMAGES_CATEGORY);
@ -133,19 +133,15 @@ public class ExploreFragment extends CommonsDaggerSupportFragment {
featuredRootFragment = new ExploreListRootFragment(featuredArguments);
mobileRootFragment = new ExploreListRootFragment(mobileArguments);
mapRootFragment = new ExploreMapRootFragment(mapArguments);
fragmentList.add(featuredRootFragment);
titleList.add(getString(R.string.explore_tab_title_featured).toUpperCase(Locale.ROOT));
fragmentList.add(mobileRootFragment);
titleList.add(getString(R.string.explore_tab_title_mobile).toUpperCase(Locale.ROOT));
fragmentList.add(mapRootFragment);
titleList.add(getString(R.string.explore_tab_title_map).toUpperCase(Locale.ROOT));
((MainActivity) getActivity()).showTabs();
((BaseActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(false);
viewPagerAdapter.setTabData(fragmentList, titleList);
viewPagerAdapter.setTabs(
pairOf(R.string.explore_tab_title_featured, featuredRootFragment),
pairOf(R.string.explore_tab_title_mobile, mobileRootFragment),
pairOf(R.string.explore_tab_title_map, mapRootFragment)
);
viewPagerAdapter.notifyDataSetChanged();
}

View file

@ -1,5 +1,7 @@
package fr.free.nrw.commons.explore;
import static fr.free.nrw.commons.ViewPagerAdapter.pairOf;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
@ -26,11 +28,13 @@ import fr.free.nrw.commons.utils.FragmentUtils;
import fr.free.nrw.commons.utils.ViewUtil;
import io.reactivex.android.schedulers.AndroidSchedulers;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import kotlin.Pair;
import timber.log.Timber;
/**
@ -65,7 +69,7 @@ public class SearchActivity extends BaseActivity
binding.toolbarSearch.setNavigationOnClickListener(v->onBackPressed());
supportFragmentManager = getSupportFragmentManager();
setSearchHistoryFragment();
viewPagerAdapter = new ViewPagerAdapter(getSupportFragmentManager());
viewPagerAdapter = new ViewPagerAdapter(this, getSupportFragmentManager());
binding.viewPager.setAdapter(viewPagerAdapter);
binding.viewPager.setOffscreenPageLimit(2); // Because we want all the fragments to be alive
binding.tabLayout.setupWithViewPager(binding.viewPager);
@ -90,19 +94,15 @@ public class SearchActivity extends BaseActivity
* Sets the titles in the tabLayout and fragments in the viewPager
*/
public void setTabs() {
List<Fragment> fragmentList = new ArrayList<>();
List<String> titleList = new ArrayList<>();
searchMediaFragment = new SearchMediaFragment();
searchDepictionsFragment = new SearchDepictionsFragment();
searchCategoryFragment= new SearchCategoryFragment();
fragmentList.add(searchMediaFragment);
titleList.add(getResources().getString(R.string.search_tab_title_media).toUpperCase(Locale.ROOT));
fragmentList.add(searchCategoryFragment);
titleList.add(getResources().getString(R.string.search_tab_title_categories).toUpperCase(Locale.ROOT));
fragmentList.add(searchDepictionsFragment);
titleList.add(getResources().getString(R.string.search_tab_title_depictions).toUpperCase(Locale.ROOT));
viewPagerAdapter.setTabData(fragmentList, titleList);
viewPagerAdapter.setTabs(
pairOf(R.string.search_tab_title_media, searchMediaFragment),
pairOf(R.string.search_tab_title_categories, searchCategoryFragment),
pairOf(R.string.search_tab_title_depictions, searchDepictionsFragment)
);
viewPagerAdapter.notifyDataSetChanged();
getCompositeDisposable().add(RxSearchView.queryTextChanges(binding.searchBox)
.takeUntil(RxView.detaches(binding.searchBox))

View file

@ -1,5 +1,8 @@
package fr.free.nrw.commons.explore.depictions;
import static fr.free.nrw.commons.ViewPagerAdapter.pairOf;
import static fr.free.nrw.commons.utils.UrlUtilsKt.handleWebUrl;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
@ -8,16 +11,11 @@ import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.FrameLayout;
import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.viewpager.widget.ViewPager;
import com.google.android.material.snackbar.Snackbar;
import com.google.android.material.tabs.TabLayout;
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.ViewPagerAdapter;
import fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao;
import fr.free.nrw.commons.category.CategoryImagesCallback;
@ -34,8 +32,10 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.inject.Inject;
import kotlin.Pair;
/**
* Activity to show depiction media, parent classes and child classes of depicted items in Explore
@ -69,7 +69,7 @@ public class WikidataItemDetailsActivity extends BaseActivity implements MediaDe
setContentView(binding.getRoot());
compositeDisposable = new CompositeDisposable();
supportFragmentManager = getSupportFragmentManager();
viewPagerAdapter = new ViewPagerAdapter(getSupportFragmentManager());
viewPagerAdapter = new ViewPagerAdapter(this, getSupportFragmentManager());
binding.viewPager.setAdapter(viewPagerAdapter);
binding.viewPager.setOffscreenPageLimit(2);
binding.tabLayout.setupWithViewPager(binding.viewPager);
@ -108,8 +108,6 @@ public class WikidataItemDetailsActivity extends BaseActivity implements MediaDe
* Set the fragments according to the tab selected in the viewPager.
*/
private void setTabs() {
List<Fragment> fragmentList = new ArrayList<>();
List<String> titleList = new ArrayList<>();
depictionImagesListFragment = new DepictedImagesFragment();
ChildDepictionsFragment childDepictionsFragment = new ChildDepictionsFragment();
ParentDepictionsFragment parentDepictionsFragment = new ParentDepictionsFragment();
@ -123,13 +121,12 @@ public class WikidataItemDetailsActivity extends BaseActivity implements MediaDe
parentDepictionsFragment.setArguments(arguments);
childDepictionsFragment.setArguments(arguments);
}
fragmentList.add(depictionImagesListFragment);
titleList.add(getResources().getString(R.string.title_for_media));
fragmentList.add(childDepictionsFragment);
titleList.add(getResources().getString(R.string.title_for_child_classes));
fragmentList.add(parentDepictionsFragment);
titleList.add(getResources().getString(R.string.title_for_parent_classes));
viewPagerAdapter.setTabData(fragmentList, titleList);
viewPagerAdapter.setTabs(
pairOf(R.string.title_for_media, depictionImagesListFragment),
pairOf(R.string.title_for_subcategories, childDepictionsFragment),
pairOf(R.string.title_for_parent_categories, parentDepictionsFragment)
);
binding.viewPager.setOffscreenPageLimit(2);
viewPagerAdapter.notifyDataSetChanged();
@ -249,7 +246,7 @@ public class WikidataItemDetailsActivity extends BaseActivity implements MediaDe
case R.id.browser_actions_menu_items:
String entityId=getIntent().getStringExtra("entityId");
Uri uri = Uri.parse("https://www.wikidata.org/wiki/" + entityId);
Utils.handleWebUrl(this, uri);
handleWebUrl(this, uri);
return true;
case R.id.menu_bookmark_current_item:

View file

@ -1,8 +1,10 @@
package fr.free.nrw.commons.explore.map;
import androidx.annotation.NonNull;
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.media.MediaClient;
import java.util.Collections;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
@ -23,6 +25,7 @@ public class ExploreMapCalls {
* @param currentLatLng coordinates of search location
* @return list of places obtained
*/
@NonNull
List<Media> callCommonsQuery(final LatLng currentLatLng) {
String coordinates = currentLatLng.getLatitude() + "|" + currentLatLng.getLongitude();
return mediaClient.getMediaListFromGeoSearch(coordinates).blockingGet();

View file

@ -2,7 +2,9 @@ package fr.free.nrw.commons.explore.map;
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED;
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.LOCATION_SLIGHTLY_CHANGED;
import static fr.free.nrw.commons.utils.GeoCoordinatesKt.handleGeoCoordinates;
import static fr.free.nrw.commons.utils.MapUtils.ZOOM_LEVEL;
import static fr.free.nrw.commons.utils.UrlUtilsKt.handleWebUrl;
import android.Manifest.permission;
import android.annotation.SuppressLint;
@ -36,7 +38,6 @@ import fr.free.nrw.commons.BaseMarker;
import fr.free.nrw.commons.MapController;
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.MainActivity;
import fr.free.nrw.commons.databinding.FragmentExploreMapBinding;
@ -639,13 +640,13 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment
*/
private void passInfoToSheet(final Place place) {
binding.bottomSheetDetailsBinding.directionsButton.setOnClickListener(
view -> Utils.handleGeoCoordinates(getActivity(),
view -> handleGeoCoordinates(requireActivity(),
place.getLocation(), binding.mapView.getZoomLevelDouble()));
binding.bottomSheetDetailsBinding.commonsButton.setVisibility(
place.hasCommonsLink() ? View.VISIBLE : View.GONE);
binding.bottomSheetDetailsBinding.commonsButton.setOnClickListener(
view -> Utils.handleWebUrl(getContext(), place.siteLinks.getCommonsLink()));
view -> handleWebUrl(getContext(), place.siteLinks.getCommonsLink()));
int index = 0;
for (Media media : mediaList) {

View file

@ -14,6 +14,7 @@ import fr.free.nrw.commons.explore.map.ExploreMapController.NearbyBaseMarkerThum
import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType;
import fr.free.nrw.commons.nearby.Place;
import io.reactivex.Observable;
import java.lang.reflect.Proxy;
import java.util.List;
@ -182,7 +183,7 @@ public class ExploreMapPresenter
exploreMapController
.loadAttractionsFromLocationToBaseMarkerOptions(explorePlacesInfo.currentLatLng,
// Curlatlang will be used to calculate distances
explorePlacesInfo.explorePlaceList,
(List<Place>) explorePlacesInfo.explorePlaceList,
exploreMapFragmentView.getContext(),
this,
explorePlacesInfo);
@ -230,11 +231,7 @@ public class ExploreMapPresenter
mylocation.setLongitude(exploreMapFragmentView.getLastMapFocus().getLongitude());
Float distance = mylocation.distanceTo(dest_location);
if (distance > 2000.0 * 3 / 4) {
return false;
} else {
return true;
}
return !(distance > 2000.0 * 3 / 4);
}
}

View file

@ -30,7 +30,6 @@ import fr.free.nrw.commons.CameraPosition
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.auth.csrf.CsrfTokenClient
import fr.free.nrw.commons.coordinates.CoordinateEditHelper
@ -45,6 +44,7 @@ import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.Compani
import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.Companion.LAST_ZOOM
import fr.free.nrw.commons.utils.DialogUtil
import fr.free.nrw.commons.utils.MapUtils.ZOOM_LEVEL
import fr.free.nrw.commons.utils.handleGeoCoordinates
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import org.osmdroid.tileprovider.tilesource.TileSourceFactory
@ -432,8 +432,8 @@ class LocationPickerActivity : BaseActivity(), LocationPermissionCallback {
position?.let {
mapView?.zoomLevelDouble?.let { zoomLevel ->
Utils.handleGeoCoordinates(this, it, zoomLevel)
} ?: Utils.handleGeoCoordinates(this, it)
handleGeoCoordinates(this, it, zoomLevel)
} ?: handleGeoCoordinates(this, it)
}
}

View file

@ -74,11 +74,10 @@ 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.utils.UnderlineUtils
import fr.free.nrw.commons.actions.ThanksClient
import fr.free.nrw.commons.auth.SessionManager
import fr.free.nrw.commons.auth.csrf.InvalidLoginTokenException
@ -102,6 +101,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.locationpicker.LocationPicker
import fr.free.nrw.commons.media.MediaDetailPagerFragment.MediaDetailProvider
import fr.free.nrw.commons.profile.ProfileActivity
import fr.free.nrw.commons.review.ReviewHelper
@ -116,8 +116,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 +130,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,8 +320,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())
binding.seeMore.setUnderlinedText(R.string.nominated_see_more)
if (isCategoryImage) {
binding.authorLinearLayout.visibility = View.VISIBLE
@ -907,7 +912,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 +920,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 +1651,7 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C
getString(R.string.cancel),
{
val reason: String = input.text.toString()
onDeleteClickeddialogtext(reason)
onDeleteClickedDialogText(reason)
},
{},
input
@ -1700,26 +1705,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 +1763,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))
}
}

View file

@ -1,6 +1,6 @@
package fr.free.nrw.commons.media;
import static fr.free.nrw.commons.Utils.handleWebUrl;
import static fr.free.nrw.commons.utils.UrlUtilsKt.handleWebUrl;
import android.os.Handler;
import android.os.Looper;
@ -31,7 +31,7 @@ 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.utils.ClipboardUtils;
import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.bookmarks.models.Bookmark;
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesContentProvider;
@ -216,7 +216,7 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
return true;
case R.id.menu_copy_link:
String uri = m.getPageTitle().getCanonicalUri();
Utils.copy("shareLink", uri, requireContext());
ClipboardUtils.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();

View file

@ -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

View file

@ -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, "")
}
}

View file

@ -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
@ -241,7 +241,7 @@ class MoreBottomSheetFragment : BottomSheetDialogFragment() {
}
fun onTutorialClicked() {
WelcomeActivity.startYourself(requireActivity())
requireContext().startWelcome()
}
fun onSettingsClicked() {

View file

@ -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 class NearbyPlaces {
* @param customQuery
* @return list of places obtained
*/
@NonNull
List<Place> radiusExpander(final LatLng currentLatLng, final String lang,
final boolean returnClosestResult, @Nullable final String customQuery) throws Exception {
final int minResults;
final double maxRadius;
List<Place> places = Collections.emptyList();
List<Place> 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 class NearbyPlaces {
* @return A list of places obtained from the Wikidata query.
* @throws Exception If an error occurs during the retrieval process.
*/
@NonNull
public List<Place> getFromWikidataQuery(
final fr.free.nrw.commons.location.LatLng centerPoint,
final fr.free.nrw.commons.location.LatLng screenTopRight,
@ -120,11 +126,11 @@ public class NearbyPlaces {
final boolean shouldQueryForMonuments,
@Nullable final String customQuery) throws Exception {
if (customQuery != null) {
return okHttpJsonApiClient
.getNearbyPlaces(
new NearbyQueryParams.Rectangular(screenTopRight, screenBottomLeft), lang,
final List<Place> 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 class NearbyPlaces {
final int itemCount = okHttpJsonApiClient.getNearbyItemCount(
new NearbyQueryParams.Rectangular(screenTopRight, screenBottomLeft));
if (itemCount < upperLimit) {
return okHttpJsonApiClient.getNearbyPlaces(
new NearbyQueryParams.Rectangular(screenTopRight, screenBottomLeft), lang,
final List<Place> nearbyPlaces = okHttpJsonApiClient.getNearbyPlaces(
new Rectangular(screenTopRight, screenBottomLeft), lang,
shouldQueryForMonuments, null);
return nearbyPlaces != null ? nearbyPlaces : emptyList();
}
}
@ -175,9 +182,10 @@ public class NearbyPlaces {
maxRadius = targetRadius - 1;
}
}
return okHttpJsonApiClient.getNearbyPlaces(
new NearbyQueryParams.Radial(centerPoint, targetRadius / 100f), lang, shouldQueryForMonuments,
final List<Place> nearbyPlaces = okHttpJsonApiClient.getNearbyPlaces(
new Radial(centerPoint, targetRadius / 100f), lang, shouldQueryForMonuments,
null);
return nearbyPlaces != null ? nearbyPlaces : emptyList();
}
/**

View file

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

View file

@ -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,6 @@ 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.navtab.NavTab
import fr.free.nrw.commons.nearby.BottomSheetAdapter
import fr.free.nrw.commons.nearby.BottomSheetAdapter.ItemClickListener
@ -105,6 +102,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 +124,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 +141,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
@ -267,6 +268,9 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
private var dataList: MutableList<BottomSheetItem>? = 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 +467,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 +728,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 +836,7 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
loadAnimations()
setBottomSheetCallbacks()
addActionToTitle()
if (!Utils.isMonumentsEnabled(Date())) {
if (!isMonumentsEnabled) {
NearbyFilterState.setWlmSelected(false)
}
}
@ -1013,11 +1017,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 +1579,7 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
searchLatLng,
false,
true,
Utils.isMonumentsEnabled(Date()),
isMonumentsEnabled,
customQuery
)
}
@ -1629,7 +1632,7 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
searchLatLng,
false,
true,
Utils.isMonumentsEnabled(Date()),
isMonumentsEnabled,
customQuery
)
}
@ -1863,6 +1866,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 +2649,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 +2666,7 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
binding!!.map.overlays.add(scaleBarOverlay)
binding!!.map.overlays.add(mapEventsOverlay)
binding!!.map.setMultiTouchControls(true)
binding!!.map.invalidate()
}
/**
@ -2700,43 +2677,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++
updateUserLocationOverlays(it, true);
}
}
// Add disk overlay
ScaleDiskOverlay(context, it, 2000, UnitOfMeasure.foot).apply {
setCirclePaint2(Paint().apply {
/**
* 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
}
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
})
setCirclePaint1(Paint().apply {
scaleDisk.setCirclePaint1(Paint().apply {
color = Color.argb(40, 128, 128, 128)
style = Paint.Style.FILL_AND_STROKE
})
setDisplaySizeMin(900)
setDisplaySizeMax(1700)
overlays.add(this)
}
// 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)
}
}
return scaleDisk
}
private fun moveCameraToPosition(geoPoint: GeoPoint) {
@ -2772,14 +2853,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 +2878,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)
}
}

View file

@ -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<Notification>?) {

View file

@ -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<Fragment>()
val titleList = mutableListOf<String>()
// 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<View>(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"

View file

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

View file

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

View file

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

View file

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

View file

@ -28,8 +28,6 @@ class UploadProgressActivity : BaseActivity() {
@Inject
lateinit var contributionDao: ContributionDao
val fragmentList: MutableList<Fragment> = ArrayList()
val titleList: MutableList<String> = 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()
}

View file

@ -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 = "<a href='" + Utils.licenseUrlFor(selectedLicense) + "'>" +
getString(Utils.licenseNameFor(selectedLicense)) + "</a><br>"
if (selectedLicense == null) return
val licenseHyperLink = "<a href='" + selectedLicense.toLicenseUrl() + "'>" +
getString(selectedLicense.toLicenseName()) + "</a><br>"
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()

View file

@ -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

View file

@ -54,7 +54,7 @@ interface UploadMediaDetailsContract {
fun showBadImagePopup(errorCode: Int, index: Int, uploadItem: UploadItem)
}
interface UserActionListener : BasePresenter<View?> {
interface UserActionListener : BasePresenter<View> {
fun setupBasicKvStoreFactory(factory: (String) -> BasicKvStore)
fun receiveImage(

View file

@ -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? ->

View file

@ -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,
@ -490,8 +493,10 @@ class UploadWorker(
.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 {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -341,7 +341,7 @@
<string name="deletion_reason_bad_for_my_privacy">Ek het besef dat dit sleg is vir my privaatheid</string>
<string name="deletion_reason_no_longer_want_public">Ek het van plan verander, ek wil nie hê dit moet meer in die openbaar sigbaar wees nie</string>
<string name="deletion_reason_not_interesting">Jammer, hierdie foto is nie interessant vir \'n ensiklopedie nie</string>
<string name="uploaded_by_myself">Opgelaai deur myself op %1$s, gebruik in %2$d artikel(s).</string>
<string name="uploaded_by_myself" fuzzy="true">Opgelaai deur myself op %1$s, gebruik in %2$d artikel(s).</string>
<string name="no_uploads">Welkom by Commons!\n\nLaai u eerste media op deur op die byvoegknoppie te tik.</string>
<string name="no_categories_selected">Geen kategorieë gekies nie</string>
<string name="no_categories_selected_warning_desc" fuzzy="true">Beelde sonder kategorieë is selde bruikbaar. Is u seker dat u wil indien sonder om kategorieë te kies?</string>

View file

@ -469,7 +469,7 @@
<string name="deletion_reason_bad_for_my_privacy">أدركت أنه سيئ لخصوصيتي</string>
<string name="deletion_reason_no_longer_want_public">لقد غيرت رأيي، ولا أريد أن يكون مرئيا للجميع بعد الآن</string>
<string name="deletion_reason_not_interesting">آسف هذه الصورة ليست مثيرة للاهتمام لموسوعة</string>
<string name="uploaded_by_myself">رفعته بنفسي في %1$s، مستخدم في %2$d مقالة(ات).</string>
<string name="uploaded_by_myself">تم تحميله بواسطة نفسي على %1$s، وتم استخدامه في %2$d مقالة على الأقل.</string>
<string name="no_uploads">مرحبا بك في كومنز! \n\nارفع الوسائط الأولى عن طريق النقر على زر الإضافة.</string>
<string name="no_categories_selected">لا توجد تصنيفات مختارة</string>
<string name="no_categories_selected_warning_desc">نادرًا ما تكون الصور الخالية من التصنيفات قابلة للاستخدام، هل أنت متأكد أنك تريد الإرسال دون تحديد التصنيفات؟</string>
@ -893,4 +893,5 @@
<string name="show_in_nearby">عرض في المناطق القريبة</string>
<string name="image_tag_line_created_and_uploaded_by">تم الإنشاء والتحميل بواسطة: %1$s</string>
<string name="image_tag_line_created_by_and_uploaded_by">تم إنشاؤه بواسطة %1$s وتم تحميله بواسطة %2$s</string>
<string name="nominated_for_deletion_btn">مرشح للحذف</string>
</resources>

View file

@ -395,7 +395,7 @@
<string name="deletion_reason_bad_for_my_privacy">Decatéme que ye malo pa la mio privacidá</string>
<string name="deletion_reason_no_longer_want_public">Cambié d\'opinión, nun quiero que siga siendo visible públicamente</string>
<string name="deletion_reason_not_interesting">Sentímoslo, esta imaxe nun ye interesante pa una enciclopedia</string>
<string name="uploaded_by_myself">Xubíla yo mesmu\'l %1$s, usada en %2$d artículu(os).</string>
<string name="uploaded_by_myself" fuzzy="true">Xubíla yo mesmu\'l %1$s, usada en %2$d artículu(os).</string>
<string name="no_uploads">¡Afáyate\'n Commons.\n\nCarga\'l to primer ficheru tocando nel botón amestar.</string>
<string name="no_categories_selected">Nun hai categoríes seleicionaes</string>
<string name="no_categories_selected_warning_desc">Les imáxenes ensin categoríes raramente puen usase. ¿Seguro que quies siguir ensin escoyer categoría dala?</string>

View file

@ -44,30 +44,63 @@
<item quantity="other">(%1$d)</item>
</plurals>
<string name="starting_uploads">Yükləmələrə Başlanılır</string>
<plurals name="starting_multiple_uploads">
<item quantity="one">%d yükləmə emal edilir</item>
<item quantity="other">%d yükləmə emal edilir</item>
</plurals>
<plurals name="multiple_uploads_title">
<item quantity="one">%d yükləmə</item>
<item quantity="other">%d yükləmə</item>
</plurals>
<plurals name="share_license_summary">
<item quantity="one">Bu şəkil %1$s lisenziyası altında yayımlanacaq</item>
<item quantity="other">Bu şəkillər %1$s lisenziyası altında yayımlanacaq</item>
</plurals>
<plurals name="upload_count_title">
<item quantity="one">%1$d yükləmə</item>
<item quantity="other">%1$d yükləmə</item>
</plurals>
<plurals name="receiving_shared_content">
<item quantity="one">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</item>
<item quantity="other">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</item>
</plurals>
<string name="navigation_item_explore">Kəşf et</string>
<string name="preference_category_appearance">Görünüş</string>
<string name="preference_category_general">Ümumi</string>
<string name="preference_category_feedback">Rəy</string>
<string name="preference_category_privacy">Məxfilik</string>
<string name="app_name">Vikianbar</string>
<string name="menu_settings">Tənzimləmələr</string>
<string name="intent_share_upload_label">Vikianbara yüklə</string>
<string name="upload_in_progress">Yükləmə davam edir</string>
<string name="username">İstifadəçi adı</string>
<string name="password">Parol</string>
<string name="login_credential">Commons Beta hesabınıza daxil olun</string>
<string name="login">Daxil ol</string>
<string name="forgot_password">Şifrəni unutmusunuz?</string>
<string name="signup">Qeydiyyatdan keç</string>
<string name="logging_in_title">Giriş edilir</string>
<string name="logging_in_message">Zəhmət olmasa, gözləyin…</string>
<string name="updating_caption_title">Başlıqların və təsvirlərin yenilənməsi</string>
<string name="updating_caption_message">Zəhmət olmasa, gözləyin…</string>
<string name="login_success">Uğurlu giriş!</string>
<string name="login_failed">Giriş baş tutmadı!</string>
<string name="upload_failed">Fayl tapılmadı. Xahiş edirik başqa bir fayl üzərində cəhd edin.</string>
<string name="retry_limit_reached">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</string>
<string name="unrestricted_battery_mode">Batareya optimallaşdırılması söndürülsün?</string>
<string name="authentication_failed" fuzzy="true">Doğrulama alınmadı, xahiş edirəm yenidən daxil olun</string>
<string name="suggest_unrestricted_mode">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.</string>
<string name="authentication_failed">Doğrulama alınmadı. Zəhmət olmasa, yenidən daxil olun</string>
<string name="uploading_started">Yükləmə başladı!</string>
<string name="uploading_queued">Yükləmə növbədə (məhdud əlaqə rejimi aktivdir)</string>
<string name="upload_completed_notification_title">%1$s yükləndi!</string>
<string name="upload_completed_notification_text">Yükləmənizə baxmaq üçün toxunun</string>
<string name="upload_progress_notification_title_start" fuzzy="true">%1$s yüklənməsi başlanır</string>
<string name="upload_progress_notification_title_start">Fayl yüklənir: %s</string>
<string name="upload_progress_notification_title_in_progress">%1$s yüklənir</string>
<string name="upload_progress_notification_title_finishing">%1$s yüklənməsi başa çatdı</string>
<string name="upload_failed_notification_title" fuzzy="true">%1$s faylının yüklənməsi alınmadı</string>
<string name="upload_failed_notification_title">%1$s yükləmək alınmadı</string>
<string name="upload_paused_notification_title">%1$s yüklənməsi dayandırıldı</string>
<string name="upload_failed_notification_subtitle">Baxmaq üçün toxun</string>
<string name="upload_paused_notification_subtitle">Baxmaq üçün toxun</string>
<string name="title_activity_contributions">Son Yükləmələrim</string>
<string name="contribution_state_queued">Növbəyə alındı</string>
<string name="contribution_state_failed">Uğursuz</string>
@ -77,49 +110,291 @@
<string name="menu_from_camera">Foto çək</string>
<string name="menu_nearby">Yaxınlıqdakılar</string>
<string name="provider_contributions">Yükləmələrim</string>
<string name="menu_copy_link">Keçidi kopiyala</string>
<string name="menu_link_copied">Link kopyalandı.</string>
<string name="menu_share">Paylaş</string>
<string name="menu_view_file_page">Faylın səhifəsinə bax</string>
<string name="share_title_hint">Başlıq (Zəruri)</string>
<string name="add_caption_toast">Zəhmət olmasa, bu fayl üçün başlıq təqdim et</string>
<string name="share_description_hint">ıqlama</string>
<string name="login_failed_network" fuzzy="true">Daxil olmaq olmur — şəbəkə xətası</string>
<string name="share_caption_hint">Başlıq</string>
<string name="login_failed_network">Daxil olmaq mümkün deyil - şəbəkə xətası</string>
<string name="login_failed_throttled">Çox sayda uğursuz daxil olma. Xahiş edirik bir neçə dəqiqə sonra yenidən cəhd edin.</string>
<string name="login_failed_blocked">Bağışlayın, bu istifadəçi Vikianbardan bloklanıb</string>
<string name="login_failed_2fa_needed">Siz iki faktorlu autentifikasiya kodunuzu təqdim etməlisiniz.</string>
<string name="login_failed_generic" fuzzy="true">Daxil olma uğursuz oldu</string>
<string name="login_failed_email_auth_needed">E-poçt ünvanınıza giriş doğrulama kodu göndərildi. Daxil olmaq üçün kodu daxil edin.</string>
<string name="login_failed_generic">Daxil olma uğursuz oldu</string>
<string name="share_upload_button">Yüklə</string>
<string name="multiple_share_base_title">Bu dəsti adlandırın</string>
<string name="provider_modifications">Dəyişikliklər</string>
<string name="menu_upload_single">Yüklə</string>
<string name="categories_search_text_hint">Kateqoriyalarda axtar</string>
<string name="depicts_search_text_hint">Medianızın təsvir etdiyi elementləri axtarın (dağ, Tac Mahal və s.)</string>
<string name="menu_save_categories">Yadda saxla</string>
<string name="refresh_button">Yenilə</string>
<string name="display_list_button">Siyahı</string>
<string name="contributions_subtitle_zero">(Hələ yükləmə yoxdur)</string>
<string name="categories_not_found">%1$s axtarışına uyğun kateqoriya tapılmadı</string>
<string name="depictions_not_found">%1$s uyğun heç bir Vikidata elementi tapılmadı</string>
<string name="no_child_classes">%1$s alt sinifləri yoxdur</string>
<string name="no_parent_classes">%1$s üst sinfi yoxdur</string>
<string name="categories_skip_explanation">Şəkillərinizi Vikianbarda daha tapıla bilən etmək üçün kateqoriyalar əlavə edin.\nKateqoriyalar əlavə etmək üçün yazmağa başlayın.</string>
<string name="categories_activity_title">Kateqoriyalar</string>
<string name="title_activity_settings">Nizamlamalar</string>
<string name="title_activity_signup">Qeydiyyatdan keç</string>
<string name="title_activity_featured_images">Seçilmiş şəkillər</string>
<string name="title_activity_custom_selector">Xüsusi Seçici</string>
<string name="title_activity_category_details">Kateqoriya</string>
<string name="title_activity_review">Yoxlanış</string>
<string name="menu_about">Haqqında</string>
<string name="about_license">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.</string>
<string name="about_improve">Xəta hesabatları və təkliflər üçün yeni &lt;a href=\"%1$s\"&gt;GitHub sorğusu&lt;/a&gt; yaradın.</string>
<string name="about_privacy_policy">Məxfilik siyasəti</string>
<string name="about_credits">Töhfə verənlər</string>
<string name="title_activity_about">Haqqında</string>
<string name="menu_feedback">Rəy göndər (e-poçt vasitəsilə)</string>
<string name="no_email_client">Heç bir e-poçt tətbiqi quraşdırılmayıb</string>
<string name="provider_categories">Son istifadə olunan kateqoriyalar</string>
<string name="waiting_first_sync">İlk sinxronizasiya gözlənilir…</string>
<string name="no_uploads_yet">Siz hələ heç bir şəkil yükləməmisiniz.</string>
<string name="menu_retry_upload">Yenidən cəhd edin</string>
<string name="menu_cancel_upload">İmtina</string>
<string name="media_upload_policy">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ə &lt;a href=\"https://commons.wikimedia.org/wiki/Commons:Policies_and_guidelines\"&gt;Vikianbar qaydalarına&lt;/a&gt; riayət edir.</string>
<string name="menu_download">Endir</string>
<string name="preference_license">Defolt lisenziya</string>
<string name="use_previous">Əvvəlki başlıq və təsvirdən istifadə et</string>
<string name="preference_theme">Tema</string>
<string name="license_name_cc_by_3_0">CC BY 3.0</string>
<string name="tutorial_1_text">Vikianbar Vikipediyada istifadə olunan şəkillərin əksəriyyətinə ev sahibliyi edir.</string>
<string name="tutorial_1_subtext">Şəkilləriniz bütün dünyada insanları maarifləndirməyə kömək edir!</string>
<string name="tutorial_2_text">Tamamilə özünüz tərəfindən çəkilmiş və ya yaradılmış şəkilləri yükləyin:</string>
<string name="tutorial_2_subtext_1">Təbii obyektlər (çiçəklər, heyvanlar, dağlar)</string>
<string name="tutorial_2_subtext_2">Faydalı obyektlər (velosipedlər, qatar stansiyaları)</string>
<string name="tutorial_2_subtext_3">Məşhur insanlar (icra başçınız, tanış olduğunuz olimpiya idmançıları)</string>
<string name="tutorial_3_text">Zəhmət olmasa, YÜKLƏMƏYİN:</string>
<string name="tutorial_3_subtext_1">Selfi və ya dostlarınızın şəkilləri</string>
<string name="tutorial_3_subtext_2">İnternetdə tapdığınız şəkillər</string>
<string name="tutorial_3_subtext_3">Özəl proqramların ekran görüntüləri</string>
<string name="tutorial_4_text">Yükləmə nümunəsi:</string>
<string name="tutorial_4_subtext_1">Başlıq: Sidney Opera Evi</string>
<string name="tutorial_4_subtext_2">Təsvir: Sidney Opera Evinin körfəzdən görünüşü</string>
<string name="tutorial_4_subtext_3">Kateqoriyalar: Sidney Opera Evi Qərb istiqamətindən, Sidney Opera Evinin uzaqdan mənzərələri</string>
<string name="welcome_wikipedia_text">Şəkilləriniz ilə töhfə verin. Vikipediya məqalələrinin canlanmasına kömək edin!</string>
<string name="welcome_wikipedia_subtext">Vikipediyadakı şəkillər Vikianbardan götürülüb.</string>
<string name="welcome_copyright_text">Şəkilləriniz bütün dünyada insanları maarifləndirməyə kömək edir.</string>
<string name="welcome_copyright_subtext">İ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.</string>
<string name="welcome_final_text">Sizcə bunu başa düşdünüz?</string>
<string name="welcome_final_button_text">Bəli!</string>
<string name="welcome_help_button_text">Əlavə məlumat</string>
<string name="detail_panel_cats_label">Kateqoriyalar</string>
<string name="detail_panel_cats_loading">Yüklənir...</string>
<string name="detail_panel_cats_none">Heç biri seçilməmişdir</string>
<string name="detail_caption_empty">Başlıq yoxdur</string>
<string name="detail_description_empty">Təsvir yoxdur</string>
<string name="detail_discussion_empty">Müzakirə yoxdur</string>
<string name="detail_license_empty">Naməlum lisenziya</string>
<string name="menu_refresh">Yenilə</string>
<string name="storage_permission_title">Yaddaşa giriş icazəsi tələb olunur</string>
<string name="read_storage_permission_rationale">Tələb olunan icazə: Xarici yaddaşı oxumaq. Bu olmadan proqram qalereyanıza daxil ola bilməz.</string>
<string name="write_storage_permission_rationale">Tələb olunan icazə: Xarici yaddaşa yazmaq. Bu olmadan proqram kameranıza/qalereyanıza daxil ola bilməz.</string>
<string name="location_permission_title">Məkan icazəsi tələb olunur</string>
<string name="in_app_camera_location_permission_title">Tətbiqdaxili çəkilişlər üçün məkanı qeyd et</string>
<string name="in_app_camera_location_switch_pref_summary">Cihaz kamerası onu qeyd etmədiyi halda, məkanı tətbiqdaxili çəkilişlərlə əlavə etmək üçün bunu aktiv edin</string>
<string name="ok">Oldu</string>
<string name="warning">Xəbərdarlıq</string>
<string name="duplicate_file_name">Dublikat fayl adı tapıldı</string>
<string name="upload">Yüklə</string>
<string name="yes">Bəli</string>
<string name="no">Xeyr</string>
<string name="media_detail_caption">Başlıq</string>
<string name="media_detail_title">Başlıq</string>
<string name="media_detail_depiction">Təsvirlər</string>
<string name="media_detail_description">Təsvir</string>
<string name="media_detail_discussion">Müzakirə</string>
<string name="media_detail_author">Müəllif</string>
<string name="media_detail_uploaded_date">Yüklənmə tarixi</string>
<string name="media_detail_license">Lisenziya</string>
<string name="media_detail_coordinates">Koordinatlar</string>
<string name="media_detail_coordinates_empty">Heç biri göstərilməyib</string>
<string name="become_a_tester_title">Beta Tester ol</string>
<string name="become_a_tester_description">Google Play-də beta kanalımıza qoşulun və yeni funksiyalara və xəta həllərinə erkən giriş əldə edin</string>
<string name="_2fa_code">2FA kodu</string>
<string name="email_auth_code">E-poçt doğrulama kodu</string>
<string name="logout_verification">Həqiqətən çıxış etmək istəyirsiniz?</string>
<string name="mediaimage_failed">Media şəkli uğursuz oldu</string>
<string name="no_subcategory_found">Heç bir alt kateqoriya tapılmadı</string>
<string name="no_parentcategory_found">Heç bir üst kateqoriya tapılmadı</string>
<string name="welcome_image_mount_zao">Zao dağı</string>
<string name="welcome_image_llamas">Lamalar</string>
<string name="welcome_image_rainbow_bridge">Göy qurşağı körpüsü</string>
<string name="welcome_image_tulip">Lalə</string>
<string name="welcome_image_welcome_wikipedia">Vikipediyaya xoş gəlmisiniz</string>
<string name="welcome_image_welcome_copyright">Müəlliflik hüquqlarına xoş gəlmisiniz</string>
<string name="welcome_image_sydney_opera_house">Sidney Opera Evi</string>
<string name="cancel">İmtina</string>
<string name="navigation_drawer_open"></string>
<string name="navigation_drawer_close">Bağla</string>
<string name="navigation_item_home">Ana səhifə</string>
<string name="navigation_item_upload">Yüklə</string>
<string name="navigation_item_nearby">Yaxınlıqdakılar</string>
<string name="navigation_item_about">Haqqında</string>
<string name="navigation_item_settings">Parametrlər</string>
<string name="navigation_item_feedback">Rəy</string>
<string name="navigation_item_feedback_github">GitHub vasitəsilə rəy</string>
<string name="navigation_item_logout">Çıxış</string>
<string name="navigation_item_info">Təlimat</string>
<string name="navigation_item_notification">Bildirişlər</string>
<string name="navigation_item_review">Yoxla</string>
<string name="no_description_found">təsvir tapılmadı</string>
<string name="nearby_info_menu_commons_article">Commons fayl səhifəsi</string>
<string name="nearby_info_menu_wikidata_article">Vikidata elementi</string>
<string name="nearby_info_menu_wikipedia_article">Vikipediya məqaləsi</string>
<string name="description_info">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.</string>
<string name="caption_info">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.</string>
<string name="upload_problem_exist">Bu şəkil ilə bağlı potensial problemlər:</string>
<string name="upload_problem_image_dark">Şəkil çox qaranlıqdır.</string>
<string name="upload_problem_image_blurry">Şəkil bulanıqdır.</string>
<string name="upload_problem_image_duplicate">Şəkil artıq Vikianbarda var.</string>
<string name="upload_problem_different_geolocation">Bu şəkil başqa yerdə çəkilib.</string>
<string name="upload_problem_fbmd">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.</string>
<string name="upload_problem_do_you_continue">Hələ də bu şəkli yükləmək istəyirsiniz?</string>
<string name="upload_connection_error_alert_title">Bağlantı xətası</string>
<string name="upload_connection_error_alert_detail">Yükləmə prosesi aktiv internetə çıxış tələb edir. Şəbəkə bağlantınızı yoxlayın.</string>
<string name="upload_problem_image">Şəkildə tapılan problemlər</string>
<string name="internet_downloaded">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.</string>
<string name="use_external_storage">Tətbiqdaxili kadrları yadda saxla</string>
<string name="use_external_storage_summary">Tətbiqdaxili kamera ilə çəkilmiş şəkilləri cihazın yaddaşında saxla</string>
<string name="login_to_your_account">Hesabınıza daxil olun</string>
<string name="send_log_file">Jurnal faylını göndərin</string>
<string name="send_log_file_description">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</string>
<string name="no_web_browser">URL-i açmaq üçün heç bir veb brauzer tapılmadı</string>
<string name="null_url">Xəta! URL tapılmadı</string>
<string name="nominate_deletion">Silinməyə namizəd göstər</string>
<string name="nominated_for_deletion">Bu şəkil silinməyə namizəddir.</string>
<string name="nominated_see_more">Ətraflı məlumat üçün veb səhifəsinə bax</string>
<string name="skip_login">Ötür</string>
<string name="navigation_item_login">Daxil ol</string>
<string name="skip_login_title">Daxil olma mərhələsini keçmək istədiyinizə əminsiniz?</string>
<string name="skip_login_message">Gələcəkdə şəkilləri yükləmək üçün daxil olmalısınız.</string>
<string name="login_alert_message">Bu funksiyadan istifadə etmək üçün zəhmət olmasa daxil olun</string>
<string name="copy_wikicode">Vikimətni mübadilə buferinə köçür</string>
<string name="wikicode_copied">Vikimətn mübadilə buferinə köçürüldü</string>
<string name="nearby_location_not_available">Yaxınlıqdakılar düzgün işləməyə bilər, məkan əlçatan deyil.</string>
<string name="nearby_showing_pins_offline">İnternet əlçatan deyil. Yalnız keşlənmiş yerlər göstərilir.</string>
<string name="upload_location_access_denied">Məkan girişi rədd edildi. Bu funksiyadan istifadə etmək üçün yerinizi manual təyin edin.</string>
<string name="location_permission_rationale_nearby">Yaxınlıqdakı yerlərin siyahısını göstərmək üçün icazə tələb olunur</string>
<string name="location_permission_rationale_explore">Yaxınlıqdakı şəkillərin siyahısını göstərmək üçün icazə tələb olunur</string>
<string name="nearby_directions">İstiqamətlər</string>
<string name="nearby_wikidata">Vikidata</string>
<string name="nearby_wikipedia">Vikipediya</string>
<string name="nearby_commons">Vikianbar</string>
<string name="about_rate_us">Bizi qiymətləndir</string>
<string name="about_faq">TSS</string>
<string name="user_guide">İstifadəçi təlimatı</string>
<string name="welcome_skip_button">Təlimatı ötür</string>
<string name="no_internet">İnternet əlçatan deyil</string>
<string name="error_notifications">Bildirişləri əldə edərkən xəta baş verdi</string>
<string name="error_review">Şə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.</string>
<string name="no_notifications">Heç bir bildiriş tapılmadı</string>
<string name="about_translate">Tərcümə et</string>
<string name="about_translate_title">Dillər</string>
<string name="about_translate_message">Tərcümələri təqdim etmək istədiyiniz dili seçin</string>
<string name="about_translate_proceed">Davam et</string>
<string name="about_translate_cancel">İmtina</string>
<string name="retry">Yenidən cəhd et</string>
<string name="showcase_view_whole_nearby_activity">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.</string>
<string name="showcase_view_needs_photo">Bu yerə şəkil lazımdır.</string>
<string name="showcase_view_has_photo">Bu yerin artıq şəkli var.</string>
<string name="showcase_view_no_longer_exists">Bu yer artıq mövcud deyil.</string>
<string name="no_images_found">Şəkil tapılmadı!</string>
<string name="error_loading_images">Şəkillər yüklənərkən xəta baş verdi.</string>
<string name="image_uploaded_by">%1$s tərəfindən yüklənilib</string>
<string name="block_notification_title">Bloklanıb</string>
<string name="block_notification">Sizə Vikianbarı redaktə etmək qadağan edilib</string>
<string name="app_widget_heading">Günün Şəkli</string>
<string name="menu_search_button">Axtar</string>
<string name="search_commons">Vikianbarda axtar</string>
<string name="title_activity_search">Axtar</string>
<string name="search_recent_header">Son axtarışlar:</string>
<string name="provider_searches">Bu yaxınlarda axtarılanlar</string>
<string name="provider_recent_languages">Son dil sorğuları</string>
<string name="error_loading_categories">Kateqoriyalar yüklənərkən xəta baş verdi.</string>
<string name="error_loading_depictions">Təsvirləri yükləyərkən xəta baş verdi.</string>
<string name="search_tab_title_media">Media</string>
<string name="search_tab_title_categories">Kateqoriyalar</string>
<string name="search_tab_title_depictions">Elementlər</string>
<string name="explore_tab_title_featured">Seçilmiş</string>
<string name="explore_tab_title_mobile">Mobil tətbiq vasitəsilə yüklənib</string>
<string name="explore_tab_title_map">Xəritə</string>
<string name="successful_wikidata_edit">Şəkil Vikidatada %1$s elementinə əlavə edildi</string>
<string name="wikidata_edit_failure">Müvafiq Vikidata obyektini yeniləmək alınmadı!</string>
<string name="menu_set_wallpaper">Divar kağızı kimi təyin et</string>
<string name="wallpaper_set_successfully">Divar kağızı uğurla təyin edildi!</string>
<string name="quiz">Sorğu</string>
<string name="quiz_question_string">Bu şəkli yükləmək olar?</string>
<string name="question">Sual</string>
<string name="result">Nəticə</string>
<string name="quiz_back_button">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?</string>
<string name="quiz_alert_message">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?</string>
<string name="selfie_answer">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.</string>
<string name="taj_mahal_answer">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.</string>
<string name="screenshot_answer">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.</string>
<string name="blurry_image_answer">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şııqlandırma ilə gözəl şəkillər çəkməyə çalışın.</string>
<string name="construction_event_answer">Texnologiya və ya mədəniyyəti təsvir edən şəkillər üçün Vikianbarda həmişə yer var.</string>
<string name="congratulatory_message_quiz">Cavabların %1$s ədədi düzgündür. Təbriklər!</string>
<string name="warning_for_no_answer">Suala cavab vermək üçün iki variantdan birini seçin</string>
<string name="user_not_logged_in">Giriş müddəti bitib. Zəhmət olmasa, yenidən daxil olun.</string>
<string name="quiz_result_share_message">Bunu dostlarınızla paylaşın!</string>
<string name="continue_message">Davam et</string>
<string name="correct">Düzgün Cavab</string>
<string name="wrong">Yanlış Cavab</string>
<string name="quiz_screenshot_question">Bu skrinşotu yükləmək olar?</string>
<string name="share_app_title">Proqramı paylaş</string>
<string name="rotate">Fırlat</string>
<string name="error_fetching_nearby_places">Yaxınlıqdakı yerləri yükləmək mümkün olmadı</string>
<string name="no_pictures_in_this_area">Bu ərazidə şəkillər yoxdur</string>
<string name="no_nearby_places_around">Yaxınlıqda yer tapılmadı</string>
<string name="error_fetching_nearby_monuments">Yaxınlıqdakı abidələri əldə edərkən xəta baş verdi.</string>
<string name="no_recent_searches">Son axtarışlar yoxdur</string>
<string name="delete_recent_searches_dialog">Axtarış tarixçəsini silmək istədiyindən əminsiniz?</string>
<string name="cancel_upload_dialog">Bu yükləməni ləğv etmək istədiyinizə əminsiniz?</string>
<string name="delete_search_dialog">Bu axtarışı silmək istəyirsiniz?</string>
<string name="search_history_deleted">Axtarış tarixçəsi silindi</string>
<string name="nominate_delete">Silinməyə namizəd göstər</string>
<string name="delete">Sil</string>
<string name="Achievements">Nailiyyətlər</string>
<string name="Profile">Profil</string>
<string name="badges">Rozetlər</string>
<string name="statistics">Statistika</string>
<string name="statistics_thanks">Qəbul edilən təşəkkürlər</string>
<string name="statistics_featured">Seçilmiş şəkillər</string>
<string name="statistics_wikidata_edits">\"Yaxınlıqdakı yerlər\" vasitəsilə şəkillər</string>
<string name="level">Səviyyə %d</string>
<string name="profileLevel">%s (Səviyyə %s)</string>
<string name="images_uploaded">Yüklənən şəkillər</string>
<string name="image_reverts">Geri qaytarılan şəkillər</string>
<string name="images_used_by_wiki">İstifadə olunan şəkillər</string>
<string name="achievements_share_message">Nailiyyətlərinizi dostlarınızla paylaşın!</string>
<string name="achievements_info_message">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.</string>
<string name="achievements_revert_limit_message">minimum tələb olunur:</string>
<string name="images_uploaded_explanation">İstənilən yükləmə proqramı vasitəsilə Vikianbara yüklədiyiniz şəkillərin sayı</string>
<string name="images_reverted_explanation">Vikianbara yüklədiyiniz və silinməyən şəkillərin faizi</string>
<string name="images_used_explanation">Vikianbara yüklədiyiniz və Vikimedia məqalələrində istifadə olunan şəkillərin sayı</string>
<string name="error_occurred">Xəta baş verdi!</string>
<string name="notifications_channel_name_all">Vikianbar bildirişi</string>
<string name="preference_author_name_toggle">Fərdi müəllif adından istifadə et</string>
<string name="preference_author_name_toggle_summary">Fotoşəkilləri yükləyərkən istifadəçi adınız əvəzinə fərdi müəllif adından istifadə edin</string>
<string name="preference_author_name">Fərdi müəllif adı</string>
<string name="contributions_fragment">Töhfələr</string>
<string name="nearby_fragment">Yaxınlıqdakılar</string>
<string name="notifications">Bildirişlər</string>
<string name="read_notifications">Bildirişlər (oxunmuş)</string>
<string name="display_nearby_notification">Yaxınlıqdakı bildirişini göstər</string>
<string name="display_nearby_notification_summary">Şəkillərə ehtiyacı olan ən yaxın yerlər üçün tətbiqdaxili bildiriş göstərin</string>
<string name="list_sheet">Siyahı</string>
<string name="storage_permission">Yaddaş icazəsi</string>
<string name="write_storage_permission_rationale_for_image_share">Şəkilləri yükləmək üçün cihazınızın xarici yaddaşına giriş icazəsi lazımdır.</string>
<string name="nearby_notification_dismiss_message">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.</string>
<string name="send_thank_success_title">Təşəkkür uğurla göndərildi</string>
<string name="send_thank_failure_message">Təşəkkür göndərilə bilmədi %1$s</string>
<string name="send_thank_failure_title">Təşəkkür göndərilir: Xəta</string>

View file

@ -394,7 +394,7 @@
<string name="deletion_reason_bad_for_my_privacy">Shvatio/la sam da je loša za moju privatnost</string>
<string name="deletion_reason_no_longer_want_public">Promenio/la sam mišljenje, ne želim da više javno budu vidljive</string>
<string name="deletion_reason_not_interesting">Ova slika nije zanimljiva za enciklopediju</string>
<string name="uploaded_by_myself">Otpremio/la sam na %1$s, koristi se u članku/cima — %2$d.</string>
<string name="uploaded_by_myself" fuzzy="true">Otpremio/la sam na %1$s, koristi se u članku/cima — %2$d.</string>
<string name="no_uploads">Dobro došli na Ostavu!\n\nOtpremite prve medije dodirom dugmeta za dodavanje.</string>
<string name="no_categories_selected">Kategorije nisu izabrane</string>
<string name="no_categories_selected_warning_desc">Slike bez kategorija retko su upotrebljive. Zaista želite da nastavite bez izbora kategorija?</string>

View file

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Authors:
* Farorud
* Ibrahim
* ToJack
* Vashgird
@ -102,7 +103,7 @@
<string name="navigation_item_logout">Баромад</string>
<string name="navigation_item_notification">Огоҳиҳо</string>
<string name="navigation_item_review">Тафтиш</string>
<string name="nearby_info_menu_wikidata_article">Унсури Викидода</string>
<string name="nearby_info_menu_wikidata_article">Элементи Викидода</string>
<string name="navigation_item_login">Вуруд</string>
<string name="nearby_directions">Ҷиҳат</string>
<string name="nearby_wikidata">Викидода</string>

View file

@ -12,6 +12,7 @@
* Mohammed Galib Hasan
* Muktogayn
* NahidHossain
* Nokib Sarkar
* R4356th
* Rasal Lia
* Sankarshan
@ -32,6 +33,7 @@
<string name="commons_github">কমন্স গিটহাব উৎস কোড</string>
<string name="commons_logo">কমন্সের লোগো</string>
<string name="commons_website">কমন্স ওয়েবসাইট</string>
<string name="exit_location_picker">লোকেশন পিকার থেকে বেরিয়ে আসুন</string>
<string name="submit">জমা দিন</string>
<string name="add_another_description">আরেকটি বিবরণ যোগ করুন</string>
<string name="add_new_contribution">নতুন অবদান যোগ করুন</string>
@ -44,6 +46,9 @@
<string name="show_captions_description">বিবরণ</string>
<string name="nearby_row_image">চিত্র</string>
<string name="nearby_all">সব</string>
<string name="nearby_filter_toggle">টগল আপ</string>
<string name="nearby_filter_search">অনুসন্ধান দৃশ্য</string>
<string name="nearby_filter_state">স্থানের অবস্থা</string>
<string name="appwidget_img">আজকের চিত্র</string>
<plurals name="uploads_pending_notification_indicator">
<item quantity="one">%1$dটি ফাইল আপলোড হচ্ছে</item>
@ -70,6 +75,10 @@
<item quantity="one">%1$dটি আপলোড</item>
<item quantity="other">%1$dটি আপলোড</item>
</plurals>
<plurals name="receiving_shared_content">
<item quantity="one">শেয়ার করা কন্টেন্ট পাওয়া। ছবির আকার এবং আপনার ডিভাইসের উপর নির্ভর করে ছবিটি প্রক্রিয়াকরণে কিছুটা সময় লাগতে পারে</item>
<item quantity="other">শেয়ার করা কন্টেন্ট পাওয়া। ছবির আকার এবং আপনার ডিভাইসের উপর নির্ভর করে ছবি প্রক্রিয়াকরণে কিছুটা সময় লাগতে পারে</item>
</plurals>
<string name="navigation_item_explore">অন্বেষণ</string>
<string name="preference_category_appearance">অবয়ব</string>
<string name="preference_category_general">সাধারণ</string>
@ -89,9 +98,11 @@
<string name="logging_in_message">অনুগ্রহ করে অপেক্ষা করুন…</string>
<string name="updating_caption_title">ক্যাপশন ও বিবরণ হালনাগাদ করা হচ্ছে</string>
<string name="updating_caption_message">অনুগ্রহ করে অপেক্ষা করুন…</string>
<string name="login_success" fuzzy="true">প্রবেশ সফল!</string>
<string name="login_success">প্রবেশ সফল</string>
<string name="login_failed">প্রবেশ ব্যর্থ!</string>
<string name="upload_failed">ফাইল পাওয়া যায়নি। আরেকটি ফাইল চেষ্টা করুন।</string>
<string name="retry_limit_reached">সর্বোচ্চ পুনচেষ্টা করার সীমা পেরিয়ে গেছে! অনুগ্রহ করে আপলোডটি বাতিল করে আবার চেষ্টা করুন।</string>
<string name="unrestricted_battery_mode">ব্যাটারি অপ্টিমাইজেশন বন্ধ করবেন?</string>
<string name="authentication_failed">প্রমাণীকরণ ব্যর্থ হয়েছে। অনুগ্রহ করে আবার প্রবেশ করুন।</string>
<string name="uploading_started">আপলোড আরম্ভ হয়েছে!</string>
<string name="uploading_queued">আপলোড সারিবদ্ধ করা হয়েছে (সীমিত সংযোগ মোড সক্রিয় করা)</string>
@ -113,6 +124,8 @@
<string name="menu_from_camera">ছবি তুলুন</string>
<string name="menu_nearby">কাছাকাছি</string>
<string name="provider_contributions">আমার আপলোড</string>
<string name="menu_copy_link">লিংক কপি করুন</string>
<string name="menu_link_copied">লিঙ্কটি ক্লিপবোর্ডে কপি করা হয়েছে।</string>
<string name="menu_share">বণ্টন</string>
<string name="menu_view_file_page">ফাইলের পাতাটি দেখুন</string>
<string name="share_title_hint">ক্যাপশন (আবশ্যক)</string>
@ -123,7 +136,8 @@
<string name="login_failed_throttled">খুব বেশি অসফল প্রচেষ্টা। অনুগ্রহ করে কয়েক মিনিট পরে আবারও চেষ্টা করুন।</string>
<string name="login_failed_blocked">দুঃখিত, এই ব্যবহারকারীকে কমন্সে বাধা দেয়া হয়েছে</string>
<string name="login_failed_2fa_needed">আপনাকে অবশ্যই আপনার দু\'স্তরের সত্যায়নকরণ কোড দিতে হবে।</string>
<string name="login_failed_generic" fuzzy="true">প্রবেশ ব্যর্থ</string>
<string name="login_failed_email_auth_needed">আপনার ইমেল ঠিকানায় একটি লগইন যাচাইকরণ কোড পাঠানো হয়েছে। লগ ইন করার জন্য অনুগ্রহ করে কোডটি প্রদান করুন।</string>
<string name="login_failed_generic">প্রবেশ ব্যর্থ</string>
<string name="share_upload_button">আপলোড</string>
<string name="multiple_share_base_title">এই সেটের নাম</string>
<string name="provider_modifications">পরিবর্তনসমূহ</string>
@ -131,6 +145,7 @@
<string name="categories_search_text_hint">বিষয়শ্রেণী অনুসন্ধান</string>
<string name="depicts_search_text_hint">আপনার মিডিয়া চিত্রিত আইটেমগুলি অনুসন্ধান করুন (পর্বত, তাজমহল, ইত্যাদি)</string>
<string name="menu_save_categories">সংরক্ষণ</string>
<string name="menu_overflow_desc">ওভারফ্লো মেনু</string>
<string name="refresh_button">পুনঃসতেজ</string>
<string name="display_list_button">তালিকা</string>
<string name="contributions_subtitle_zero">(আপলোড করেন নি)</string>
@ -223,6 +238,7 @@
<string name="become_a_tester_title">বিটা টেস্টার হোন</string>
<string name="become_a_tester_description">গুগল প্লেতে আমাদের বেটা চ্যানেলে যুক্ত হন! এতে নতুন বৈশিষ্ট্যের পাশাপাশি পুরানো বাগগুলো\'র সংশোধিত রুপ সবার আগে ব্যবহারের সুযোগ পাবেন</string>
<string name="_2fa_code">2FA কোড</string>
<string name="email_auth_code">ইমেল যাচাইকরণ কোড</string>
<string name="logout_verification">আপনি কি সত্যিই প্রস্থান করতে চান?</string>
<string name="mediaimage_failed">মিডিয়া চিত্র ব্যর্থ হয়েছে</string>
<string name="no_subcategory_found">কোন উপবিষয়শ্রেনী পাওয়া যায় নি।</string>
@ -243,6 +259,7 @@
<string name="navigation_item_about">সম্পর্কে</string>
<string name="navigation_item_settings">সেটিং</string>
<string name="navigation_item_feedback">প্রতিক্রিয়া</string>
<string name="navigation_item_feedback_github">GitHub এর মাধ্যমে প্রতিক্রিয়া</string>
<string name="navigation_item_logout">প্রস্থান</string>
<string name="navigation_item_info">ভূমিকা</string>
<string name="navigation_item_notification">বিজ্ঞপ্তি</string>
@ -252,6 +269,7 @@
<string name="nearby_info_menu_wikidata_article">উইকিউপাত্ত আইটেম</string>
<string name="nearby_info_menu_wikipedia_article">উইকিপিডিয়া নিবন্ধ</string>
<string name="description_info">যতটা সম্ভব মিডিয়াটি বর্ণনা করুন: এটি কোথায় ধারণ করা হয়েছিল? এটি কি প্রদর্শন করে? এটির প্রসঙ্গ কি? ধারণকৃত বস্তু অথবা ব্যক্তির বর্ণনা করুন। সহজে অনুমান করা যায়না সেরকম তথ্য উদঘাটন করুন, উদাহরণস্বরূপ, যদি ল্যান্ডস্কেপ হয় তাহলে দিবসকালের সময় দিন।</string>
<string name="caption_info">দয়া করে ছবির একটি সংক্ষিপ্ত বিবরণ লিখুন। প্রথম ক্যাপশনটি ছবির শিরোনাম হিসেবে ব্যবহার করা হবে। ২৫৫ অক্ষরের মধ্যে সীমাবদ্ধ।</string>
<string name="upload_problem_exist">এই চিত্রের সম্ভাব্য সমস্যাগুলি:</string>
<string name="upload_problem_image_dark">চিত্রটি খুব অন্ধকারযুক্ত।</string>
<string name="upload_problem_image_blurry">চিত্রটি অস্পষ্ট।</string>
@ -275,12 +293,13 @@
<string name="nominated_see_more">বিশদের জন্য ওয়েবপৃষ্ঠা দেখুন</string>
<string name="skip_login">এড়ান</string>
<string name="navigation_item_login">প্রবেশ করুন</string>
<string name="skip_login_title" fuzzy="true">আপনি কি সত্যিই প্রবেশকরণ এড়িয়ে যেতে চান?</string>
<string name="skip_login_message" fuzzy="true">ভবিষ্যতে চিত্র আপলোড করতে চাইলে আপনাকে প্রবেশ করতে হবে।</string>
<string name="skip_login_title">আপনি কি সত্যিই লগ-ইন এড়িয়ে যেতে চান?</string>
<string name="skip_login_message">ভবিষ্যতে চিত্র আপলোড করতে চাইলে আপনাকে প্রবেশ করতে হবে।</string>
<string name="login_alert_message">এই সুবিধাটি ব্যাবহার করতে প্রবেশ করুন</string>
<string name="copy_wikicode">উইকিপাঠ্যটি ক্লিপবোর্ডে অনুলিপি করুন</string>
<string name="wikicode_copied">উইকিপাঠ্যটি ক্লিপবোর্ডে অনুলিপি করা হয়েছে</string>
<string name="nearby_location_not_available">কাছাকাছি সঠিকভাবে কাজ করছে না, অবস্থান উপলব্ধ নয়।</string>
<string name="nearby_showing_pins_offline">ইন্টারনেট অনুপলব্ধ। শুধুমাত্র ক্যাশে করা স্থানগুলো দেখানো হচ্ছে।</string>
<string name="location_permission_rationale_nearby">কাছাকাছি স্থানসমূহের একটি তালিকা প্রদর্শন করতে অনুমতি প্রয়োজন</string>
<string name="nearby_directions">দিকনির্দেশ</string>
<string name="nearby_wikidata">উইকিউপাত্ত</string>
@ -334,19 +353,26 @@
<string name="quiz_screenshot_question">এই স্ক্রিনশটটি কি আপলোড করা ঠিক হয়েছে?</string>
<string name="share_app_title">অ্যাপ শেয়ার করুন</string>
<string name="rotate">ঘোরান</string>
<string name="error_fetching_nearby_places" fuzzy="true">কাছাকাছি স্থানগুলি আনতে ত্রুটি।</string>
<string name="error_fetching_nearby_places">কাছাকাছি জায়গাগুলি লোড করা যায়নি</string>
<string name="no_pictures_in_this_area">এই এলাকার কোনও ছবি নেই</string>
<string name="no_nearby_places_around">আশেপাশে কোনো এলাকা পাওয়া যায়নি</string>
<string name="error_fetching_nearby_monuments">আশেপাশের স্মৃতিস্তম্ভগুলি আনায়নে ত্রুটি৷</string>
<string name="no_recent_searches">কোনও সাম্প্রতিক অনুসন্ধান নেই</string>
<string name="delete_recent_searches_dialog">আপনি কি নিশ্চিত যে আপনি আপনার অনুসন্ধান ইতিহাস মুছে ফেলতে চান?</string>
<string name="cancel_upload_dialog">আপনি কি নিশ্চিত যে আপনি এই আপলোডটি বাতিল করতে চান?</string>
<string name="delete_search_dialog">আপনি কি এই অনুসন্ধানটি মুছে ফেলতে চান?</string>
<string name="search_history_deleted">অনুসন্ধানের ইতিহাস মুছে ফেলা হয়েছে</string>
<string name="nominate_delete">অপসারণের জন্য মনোনীত করুন</string>
<string name="delete">অপসারণ</string>
<string name="Achievements">কৃতিত্বগুলি</string>
<string name="Profile">প্রোফাইল</string>
<string name="badges">ব্যাজ</string>
<string name="statistics">পরিসংখ্যান</string>
<string name="statistics_thanks">ধন্যবাদ পেয়েছেন</string>
<string name="statistics_featured">নির্বাচিত ছবি</string>
<string name="level" fuzzy="true">স্তর</string>
<string name="statistics_wikidata_edits">\"কাছাকাছি স্থান\" এর মাধ্যমে ছবি</string>
<string name="level">স্তর %d</string>
<string name="profileLevel">%s (স্তর %s )</string>
<string name="images_uploaded">আপলোডকৃত চিত্র</string>
<string name="image_reverts">ছবিগুলো প্রত্যাবর্তন করা হয়নি</string>
<string name="images_used_by_wiki">ব্যবহৃত ছবি</string>
@ -539,4 +565,5 @@
<string name="failed">ব্যর্থ হয়েছে</string>
<string name="caption">ক্যাপশন</string>
<string name="caption_copied_to_clipboard">ক্যাপশন ক্লিপবোর্ডে অনুলিপি করা হয়েছে</string>
<string name="nominated_for_deletion_btn">অপসারণের জন্য মনোনীত</string>
</resources>

View file

@ -448,7 +448,7 @@
<string name="deletion_reason_bad_for_my_privacy">Uvědomil/a jsem si, že je to špatné pro mé soukromí</string>
<string name="deletion_reason_no_longer_want_public">Změnil/a jsem názor, nechci, aby to bylo veřejně viditelné</string>
<string name="deletion_reason_not_interesting">Omlouváme se, že tento obrázek není zajímavý pro encyklopedii</string>
<string name="uploaded_by_myself">Náhráno mnou %1$s, použito v(e) %2$d článku/článcích.</string>
<string name="uploaded_by_myself" fuzzy="true">Náhráno mnou %1$s, použito v(e) %2$d článku/článcích.</string>
<string name="no_uploads">Vítejte na Commons!\n\nNahrajte svá první média klepnutím na tlačítko přidat.</string>
<string name="no_categories_selected">Nebyly vybrány žádné kategorie</string>
<string name="no_categories_selected_warning_desc">Obrázky bez kategorií jsou používány jen zřídka. Opravdu chcete nahrát obrázek bez výběru kategorií?</string>

View file

@ -135,7 +135,7 @@
<string name="categories_not_found">Ingen kategorier matchende %1$s er fundet</string>
<string name="depictions_not_found">Der blev ikke fundet nogen Wikidata-elementer, der matcher %1$s</string>
<string name="no_child_classes">%1$s har ingen underklasser</string>
<string name="no_parent_classes">%1$s har ingen forældreklasser</string>
<string name="no_parent_classes">%1$s har ingen overordnede klasser</string>
<string name="categories_skip_explanation">Tilføj kategorier for at gøre dine billeder mere synlig på Wikimedia Commons. Begynd at taste for at tilføje kategorier.</string>
<string name="categories_activity_title">Kategorier</string>
<string name="title_activity_settings">Indstillinger</string>
@ -419,7 +419,7 @@
<string name="deletion_reason_bad_for_my_privacy">Jeg indså, det var skidt for mit privatliv</string>
<string name="deletion_reason_no_longer_want_public">Jeg ombestemte mig, jeg ønsker ikke at det skal være offentligt synligt længere</string>
<string name="deletion_reason_not_interesting">Beklager dette billede er ikke interessant for en encyklopædi</string>
<string name="uploaded_by_myself">Uploadet af mig selv på %1$s , brugt i %2$d artikel(er).</string>
<string name="uploaded_by_myself">Uploadet af mig selv på %1$s , brugt i mindst %2$d artikel(er).</string>
<string name="no_uploads">Velkommen til Commons!\n\nUpload dit første medie ved at trykke på Tilføj-knappen.</string>
<string name="no_categories_selected">Ingen kategorier valgt</string>
<string name="no_categories_selected_warning_desc">Billeder uden kategorier er sjældent brugbare. Er du sikker på, at du vil fortsætte uden at vælge kategorier?</string>
@ -584,6 +584,8 @@
<string name="title_for_media">MEDIER</string>
<string name="title_for_child_classes">UNDERKLASSER</string>
<string name="title_for_parent_classes">OVERORDNEDE KLASSER</string>
<string name="title_for_subcategories">UNDERKATEGORIER</string>
<string name="title_for_parent_categories">OVERORDNEDE KATEGORIER</string>
<string name="upload_nearby_place_found_title">Sted fundet i nærheden</string>
<string name="upload_nearby_place_found_description_plural">Er disse billeder af %1$s ?</string>
<string name="upload_nearby_place_found_description_singular">Er dette et billede af %1$s?</string>
@ -828,4 +830,5 @@
<string name="show_in_nearby">Vis i I nærheden</string>
<string name="image_tag_line_created_and_uploaded_by">Oprettet og uploadet af: %1$s</string>
<string name="image_tag_line_created_by_and_uploaded_by">Oprettet af %1$s og uploadet af %2$s</string>
<string name="nominated_for_deletion_btn">Nomineret til sletning</string>
</resources>

View file

@ -453,7 +453,7 @@
<string name="deletion_reason_bad_for_my_privacy">Ich habe erkannt, dass es schlecht für meine Privatsphäre ist.</string>
<string name="deletion_reason_no_longer_want_public">Ich habe meine Meinung geändert. Ich möchte nicht mehr, dass es öffentlich sichtbar ist.</string>
<string name="deletion_reason_not_interesting">Entschuldigung, aber dieses Bild ist für eine Enzyklopädie nicht relevant.</string>
<string name="uploaded_by_myself">Von mir selbst hochgeladen am %1$s, verwendet in %2$d Artikel(n).</string>
<string name="uploaded_by_myself" fuzzy="true">Von mir selbst hochgeladen am %1$s, verwendet in %2$d Artikel(n).</string>
<string name="no_uploads">Willkommen bei Commons!\n\nLade deine erste Datei hoch, indem du auf die Hinzufügen-Schaltfläche tippst.</string>
<string name="no_categories_selected">Keine Kategorien ausgewählt</string>
<string name="no_categories_selected_warning_desc">Bilder ohne Kategorien sind selten nutzbar. Bist du sicher, dass du ohne die Auswahl von Kategorien hochladen möchtest?</string>

View file

@ -433,7 +433,7 @@
<string name="deletion_reason_bad_for_my_privacy">Συνειδητοποίησα ότι είναι κακό για την ιδιωτικότητά μου</string>
<string name="deletion_reason_no_longer_want_public">Άλλαξα γνώμη, δε θέλω να προβάλλεται πλέον δημόσια</string>
<string name="deletion_reason_not_interesting">Συγγνώμη, αυτή η φωτογραφία δεν είναι ενδιαφέρουσα για μια εγκυκλοπαίδεια</string>
<string name="uploaded_by_myself">Ανέβηκε από εμένα στο %1$s, χρησιμοποιήθηκε σε %2$d άρθρο/α</string>
<string name="uploaded_by_myself" fuzzy="true">Ανέβηκε από εμένα στο %1$s, χρησιμοποιήθηκε σε %2$d άρθρο/α</string>
<string name="no_uploads">Καλώς ήρθατε στα Commons!\n\nΑνεβάστε τα πρώτα σας πολυμέσα πατώντας το κουμπί της προσθήκης.</string>
<string name="no_categories_selected">Δεν επιλέχθηκαν κατηγορίες</string>
<string name="no_categories_selected_warning_desc">Οι εικόνες χωρίς κατηγορίες χρησιμοποιούνται σπάνια. Θέλετε πράγματι να συνεχίσετε δίχως να επιλέξετε κατηγορίες;</string>

View file

@ -408,7 +408,7 @@
<string name="deletion_reason_bad_for_my_privacy">Mi eksciis ke ĝi esta malbona por mia privateco</string>
<string name="deletion_reason_no_longer_want_public">Mi ŝanĝis mian preferon; mi ne plu volas ke ĝi estu publike videbla</string>
<string name="deletion_reason_not_interesting">Domaĝe, ĉi tiu bildo ne estas interesa por enciklopedio</string>
<string name="uploaded_by_myself">Alŝutita de mi je %1$s, uzata en %2$d artikolo(j).</string>
<string name="uploaded_by_myself" fuzzy="true">Alŝutita de mi je %1$s, uzata en %2$d artikolo(j).</string>
<string name="no_uploads">Bonvenon al Komunejo!\n\nAlŝutu vian unuan aŭdvidaĵon per frapeto de la butono \'Aldoni\'.</string>
<string name="no_categories_selected">Neniu Elektita Kategorio</string>
<string name="no_categories_selected_warning_desc">Bildoj sen kategorioj estas malofte uzebla. Ĉu vi certas, ke vi volas daŭrigi sen elekti kategoriojn?</string>

View file

@ -30,6 +30,7 @@
* Jduranboger
* Jelou
* Johnny243
* JorgeElias2302
* Josuert
* Juanman
* Keneth Urrutia
@ -152,6 +153,7 @@
<string name="menu_nearby">Cercanos</string>
<string name="provider_contributions">Mis subidas</string>
<string name="menu_copy_link">Copiar enlace</string>
<string name="menu_link_copied">El enlace ha sido copiado al portapapeles.</string>
<string name="menu_share">Compartir</string>
<string name="menu_view_file_page">Ver página del archivo</string>
<string name="share_title_hint">Leyenda (requerido)</string>
@ -162,6 +164,7 @@
<string name="login_failed_throttled">Demasiados intentos fallidos. Inténtalo de nuevo en unos minutos.</string>
<string name="login_failed_blocked">Lo sentimos, este usuario ha sido bloqueado en Commons</string>
<string name="login_failed_2fa_needed">Debe proporcionar su código de autenticación de dos factores.</string>
<string name="login_failed_email_auth_needed">Se ha enviado un código de verificación de inicio de sesión a tu correo electrónico. Indícalo para iniciar sesión.</string>
<string name="login_failed_generic">Falló el inicio de sesión</string>
<string name="share_upload_button">Subir</string>
<string name="multiple_share_base_title">Nombrar este conjunto</string>
@ -267,6 +270,7 @@
<string name="become_a_tester_title">Prueba la versión beta</string>
<string name="become_a_tester_description">Apúntate a nuestro canal beta en Google Play y obtén acceso a funcionalidades nuevas y correcciones de errores</string>
<string name="_2fa_code">Código de autenticación de 2 pasos</string>
<string name="email_auth_code">Código de verificación de correo electrónico</string>
<string name="logout_verification">¿Confirmas que quieres salir?</string>
<string name="mediaimage_failed">Falló la imagen de multimedia</string>
<string name="no_subcategory_found">No se encontró ninguna subcategoría</string>
@ -327,6 +331,7 @@
<string name="copy_wikicode">Copia el wikitexto al portapapeles</string>
<string name="wikicode_copied">El wikitexto fue copiado al portapapeles</string>
<string name="nearby_location_not_available">Cercanos no puede funcionar correctamente. La ubicación no está disponible.</string>
<string name="nearby_showing_pins_offline">Internet no disponible. Solo se muestran ubicaciones en caché.</string>
<string name="upload_location_access_denied">Acceso a la ubicación denegado. Configura tu ubicación manualmente para utilizar esta función.</string>
<string name="location_permission_rationale_nearby">Se necesita permiso para mostrar una lista de lugares cercanos</string>
<string name="location_permission_rationale_explore">Se necesita permiso para mostrar una lista de lugares cercanos</string>
@ -416,6 +421,7 @@
<string name="statistics_featured">Imágenes destacadas</string>
<string name="statistics_wikidata_edits">Imágenes vía \"Sitios Cercanos\"</string>
<string name="level">Nivel %d</string>
<string name="profileLevel">%s (Nivel %s)</string>
<string name="images_uploaded">Imágenes subidas</string>
<string name="image_reverts">Imágenes no revertidas</string>
<string name="images_used_by_wiki">Imágenes utilizadas</string>
@ -447,6 +453,7 @@
<string name="map_application_missing">No se encontró una aplicación de mapas compatible en tu dispositivo. Instala una para usar esta característica.</string>
<string name="title_page_bookmarks_pictures">Imágenes</string>
<string name="title_page_bookmarks_locations">Ubicaciones</string>
<string name="title_page_bookmarks_categories">Categorías</string>
<string name="menu_bookmark">Añadir o quitar de marcadores</string>
<string name="provider_bookmarks">Marcadores</string>
<string name="bookmark_empty">No has añadido ningún marcador</string>
@ -457,7 +464,7 @@
<string name="deletion_reason_bad_for_my_privacy">Me di cuenta que es malo para mi privacidad</string>
<string name="deletion_reason_no_longer_want_public">Cambié de opinión, no quiero que siga siendo visible públicamente</string>
<string name="deletion_reason_not_interesting">Lo sentimos, esta imagen no es interesante para una enciclopedia</string>
<string name="uploaded_by_myself">Subida por mí mismo el %1$s, usado en %2$d artículo(s).</string>
<string name="uploaded_by_myself" fuzzy="true">Subida por mí mismo el %1$s, usado en %2$d artículo(s).</string>
<string name="no_uploads">Te damos la bienvenida a Commons.\n\nCarga tu primer archivo mediante el botón Añadir.</string>
<string name="no_categories_selected">No hay categorías seleccionadas</string>
<string name="no_categories_selected_warning_desc">Las imágenes sin categorías raramente se pueden usar. ¿Seguro que quieres continuar sin seleccionar ninguna categoría?</string>
@ -827,6 +834,7 @@
<string name="talk">Discusión</string>
<string name="write_something_about_the_item">Escriba algo sobre el elemento \'%1$s\'. Será visible públicamente.</string>
<string name="does_not_exist_anymore_no_picture_can_ever_be_taken_of_it">\' %1$s \' ya no existe, nunca se podrá tomar ninguna fotografía de él.</string>
<string name="is_at_a_different_place_wikidata">\'%1$s\' está en un lugar diferente.</string>
<string name="is_at_a_different_place_please_specify_the_correct_place_below_if_possible_tell_us_the_correct_latitude_longitude">\' %1$s \' está en un lugar diferente. Especifique el lugar correcto a continuación y, si es posible, escriba la latitud y longitud correctas.</string>
<string name="other_problem_or_information_please_explain_below">Otro problema o información (por favor explique a continuación).</string>
<string name="feedback_destination_note">Sus comentarios se publicarán en la siguiente página wiki: &lt;a href=\"https://commons.wikimedia.org/wiki/Commons:Mobile_app/Feedback\"&gt;Commons:Mobile_app/Feedback&lt;/a&gt;</string>
@ -848,4 +856,21 @@
<string name="red_pin">Este lugar aún no tiene foto, ¡ve y toma una!</string>
<string name="green_pin">Este lugar ya tiene una foto.</string>
<string name="grey_pin">Ahora comprobando si este lugar tiene una foto.</string>
<string name="error_while_loading">Error al cargar</string>
<string name="no_usages_found">No se encontraron usos</string>
<string name="usages_on_commons_heading">Commons</string>
<string name="usages_on_other_wikis_heading">Otras wikis</string>
<string name="file_usages_container_heading">Usos de archivos</string>
<string name="title_activity_single_web_view">Actividad de vista web única</string>
<string name="account">Cuenta</string>
<string name="vanish_account">Desaparecer cuenta</string>
<string name="account_vanish_request_confirm_title">Advertencia sobre la desaparición de la cuenta</string>
<string name="account_vanish_request_confirm">La desaparición de una cuenta en Wikimedia Commons es un &lt;b&gt;último recurso&lt;/b&gt; y &lt;b&gt;solo debe usarse cuando desee dejar de editar para siempre y también para ocultar la mayor cantidad posible de sus asociaciones pasadas.&lt;br/&gt;&lt;br/&gt;La desaparición de una cuenta en Wikimedia Commons se realiza cambiando el nombre de la cuenta para que otros no puedan reconocer sus contribuciones en un proceso llamado desaparición de la cuenta. &lt;b&gt;La desaparición de la cuenta no garantiza el anonimato completo ni elimina las contribuciones a los proyectos&lt;/b&gt;.</string>
<string name="caption">Leyenda</string>
<string name="caption_copied_to_clipboard">Leyenda copiado al portapapeles</string>
<string name="congratulations_all_pictures_in_this_album_have_been_either_uploaded_or_marked_as_not_for_upload">Felicitaciones, todas las imágenes de este álbum han sido cargadas o marcadas como no para cargar.</string>
<string name="show_in_explore">Mostrar en Explorar</string>
<string name="show_in_nearby">Mostrar en las cercanías</string>
<string name="image_tag_line_created_and_uploaded_by">Creado y cargado por: %1$s</string>
<string name="image_tag_line_created_by_and_uploaded_by">Creado por %1$s y cargado por %2$s</string>
</resources>

View file

@ -386,7 +386,7 @@
<string name="deletion_reason_bad_for_my_privacy">فهمیدم که این برای حریم خصوصی من بد است.</string>
<string name="deletion_reason_no_longer_want_public">من تغییر عقیده دادم، نمی‌خواهم دیگر برای همه قابل‌مشاهده باشد.</string>
<string name="deletion_reason_not_interesting">با پوزش، این تصویر برای یک دانشنامه مناسب نیست</string>
<string name="uploaded_by_myself">بارگذاری‌شده توسط خودم در %1$s؛ استفاده‌شده در %2$d مقاله.</string>
<string name="uploaded_by_myself">بارگذاری‌شده توسط خودم در %1$s؛ استفاده‌شده در دست کم %2$d مقاله.</string>
<string name="no_uploads">به ویکی‌انبار خوش آمدید!\n\nاولین فایلتان را با فشردن کلید اضافه بارگذاری کنید.</string>
<string name="no_categories_selected">رده‌ای انتخاب نشده است</string>
<string name="no_categories_selected_warning_desc">تصاویر بدون رده به ندرت قابل‌استفاده هستند. آیا مطمئنید که می‌خواهید بدون انتخاب رده ادامه دهید؟</string>
@ -508,4 +508,5 @@
<string name="your_feedback">بازخورد شما</string>
<string name="see_your_achievements">دستاوردهای خود را ببینید</string>
<string name="could_not_load_place_data">ناتوان از بارگیری داده‌های مکانی</string>
<string name="nominated_for_deletion_btn">نامزدشده برای حذف</string>
</resources>

View file

@ -180,7 +180,7 @@
<string name="tutorial_4_subtext_2">Kuvaus: Sydneyn oopperatalo lahden toiselta puolelta katsottuna</string>
<string name="tutorial_4_subtext_3">Luokat: Sydneyn oopperatalo lännestä katsottuna, Sydneyn oopperatalo kaukaa katsottuna</string>
<string name="welcome_wikipedia_text">Herätä Wikipedia-artikkelit eloon kuvillasi! Tuo kuvasi Wikipediaan.</string>
<string name="welcome_wikipedia_subtext">Wikipedian kuvat tulevat Wikimedia Commonsista.</string>
<string name="welcome_wikipedia_subtext">Wikipedian kuvat ovat peräisin Wikimedia Commonsista.</string>
<string name="welcome_copyright_text">Kuvasi auttavat useita ihmisiä ympäri maailmaa artikkeleiden ymmärtämisessä.</string>
<string name="welcome_copyright_subtext">Vältä tekijänoikeuksien alaista materiaalia, kuten julisteita, kirjan kansia ja useimpia Internetistä löydettyjä kuvia.</string>
<string name="welcome_final_text">Luuletko ymmärtäneesi tämän?</string>
@ -404,7 +404,7 @@
<string name="deletion_reason_bad_for_my_privacy">Huomasin, että se on haitallinen yksityisyydelleni.</string>
<string name="deletion_reason_no_longer_want_public">Muutin mieleni, en halua että se on enää julkisesti näkyvissä.</string>
<string name="deletion_reason_not_interesting">Valitettavasti tämä kuva ei ole kiinnostava tietosanakirjaan</string>
<string name="uploaded_by_myself">Minä olen ladannut kohteen %1$s käytetty %2$d artikkelissa.</string>
<string name="uploaded_by_myself" fuzzy="true">Minä olen ladannut kohteen %1$s käytetty %2$d artikkelissa.</string>
<string name="no_uploads">Tervetuloa Commonsiin!\n\nTallenna ensimmäinen mediasi koskettamalla lisäyspainiketta.</string>
<string name="no_categories_selected">Luokkia ei valittu</string>
<string name="no_categories_selected_warning_desc">Kuvat, jotka eivät ole luokissa, ovat harvoin käyttökelpoisia. Haluatko varmasti jatkaa valitsematta luokkia?</string>

View file

@ -457,7 +457,7 @@
<string name="deletion_reason_bad_for_my_privacy">Jai pensé que ce nétait pas bon pour ma vie privée</string>
<string name="deletion_reason_no_longer_want_public">Jai changé d\'avis, je ne désire plus que cel soit visible publiquement</string>
<string name="deletion_reason_not_interesting">Désolé mais cette image nest pas intéressante pour une encyclopédie</string>
<string name="uploaded_by_myself">Téléversé par moi-même le %1$s, utilisé dans %2$d article(s).</string>
<string name="uploaded_by_myself">Téléversé par moi-même le %1$s, utilisé dans au moins %2$d article(s).</string>
<string name="no_uploads">Bienvenue sur Commons!\n\nTéléversez votre premier média en tapant sur le bouton Ajouter.</string>
<string name="no_categories_selected">Aucune catégorie sélectionnée</string>
<string name="no_categories_selected_warning_desc">Les images sans catégories sont rarement utilisables. Voulez-vous vraiment continuer sans sélectionner des catégories appropriées?</string>
@ -869,4 +869,5 @@
<string name="show_in_nearby">Afficher à proximité</string>
<string name="image_tag_line_created_and_uploaded_by">Crée et publiée par: %1$s</string>
<string name="image_tag_line_created_by_and_uploaded_by">Créé par %1$s et publiée par %2$s</string>
<string name="nominated_for_deletion_btn">Nommé pour suppression</string>
</resources>

View file

@ -394,7 +394,7 @@
<string name="deletion_reason_bad_for_my_privacy">Decateime de que prexudica a miña privacidade</string>
<string name="deletion_reason_no_longer_want_public">Cambiei de idea, non quero que siga sendo visible de forma pública</string>
<string name="deletion_reason_not_interesting">Desculpas, esta imaxe non é interesante para unha enciclopedia</string>
<string name="uploaded_by_myself">Cargada por min o %1$s, usada en %2$d artigo(s).</string>
<string name="uploaded_by_myself" fuzzy="true">Cargada por min o %1$s, usada en %2$d artigo(s).</string>
<string name="no_uploads">Dámoslle a benvida ó Commonsǃ\n\nCargue o seu primeiro ficheiro premendo no botón Engadir.</string>
<string name="no_categories_selected">Non hai categorías seleccionadas</string>
<string name="no_categories_selected_warning_desc">As imaxes sen categorías só son utilizables en contadas ocasións. Está seguro de que quere continuar sen seleccionar categorías?</string>

View file

@ -362,7 +362,7 @@
<string name="deletion_reason_bad_for_my_privacy">Észleltem, hogy ez rossz a magánszférámnak</string>
<string name="deletion_reason_no_longer_want_public">Meggondoltam magam, nem szeretném, hogy nyilvánosan látható legyen</string>
<string name="deletion_reason_not_interesting">Sajnos ez a fénykép nem érdekes egy enciklopédia számára</string>
<string name="uploaded_by_myself">Feltöltve ekkorː %1$s, %2$d cikkben használva.</string>
<string name="uploaded_by_myself" fuzzy="true">Feltöltve ekkorː %1$s, %2$d cikkben használva.</string>
<string name="no_uploads">Üdvözlünk a Commons-ban. \n\nA hozzáadás gombra koppintva feltöltheted első képedet.</string>
<string name="no_categories_selected">Nincs kiválasztott kategória</string>
<string name="no_categories_selected_warning_desc">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?</string>

View file

@ -407,7 +407,7 @@
<string name="deletion_reason_bad_for_my_privacy">Io ha comprendite que es mal pro mi vita private</string>
<string name="deletion_reason_no_longer_want_public">Io ha cambiate de idea, io non vole plus que illo sia publicamente visibile</string>
<string name="deletion_reason_not_interesting">Iste imagine non es interessante pro un encyclopedia</string>
<string name="uploaded_by_myself">Incargate per me mesme le %1$s, usate in %2$d articulo(s).</string>
<string name="uploaded_by_myself">Incargate per me mesme le %1$s, usate al minus in %2$d articulo(s).</string>
<string name="no_uploads">Benvenite a Commons!\n\nPro incargar tu prime file multimedial, tocca le button Adder.</string>
<string name="no_categories_selected">Necun categoria seligite</string>
<string name="no_categories_selected_warning_desc">Imagines sin categorias es rarmente usabile. Es tu secur de voler continuar sin seliger categorias?</string>
@ -816,4 +816,5 @@
<string name="show_in_nearby">Monstrar in A proximitate</string>
<string name="image_tag_line_created_and_uploaded_by">Create e incargate per: %1$s</string>
<string name="image_tag_line_created_by_and_uploaded_by">Create per %1$s e incargate per %2$s</string>
<string name="nominated_for_deletion_btn">Nominate pro deletion</string>
</resources>

View file

@ -410,7 +410,7 @@
<string name="deletion_reason_bad_for_my_privacy">Saya menyadari itu buruk untuk privasi saya</string>
<string name="deletion_reason_no_longer_want_public">Saya berubah pikiran, saya tidak ingin itu terlihat publik lagi</string>
<string name="deletion_reason_not_interesting">Maaf gambar ini tidak menarik untuk ensiklopedia</string>
<string name="uploaded_by_myself">Diunggah saya sendiri pada %1$s, digunakan dalam %2$d artikel.</string>
<string name="uploaded_by_myself" fuzzy="true">Diunggah saya sendiri pada %1$s, digunakan dalam %2$d artikel.</string>
<string name="no_uploads">Unggah media pertama Anda dengan mengetuk tombol.</string>
<string name="no_categories_selected">Tidak ada Kategori yang Dipilih</string>
<string name="no_categories_selected_warning_desc">Gambar tanpa kategori jarang dapat digunakan. Apakah Anda yakin ingin mengirim tanpa memilih kategori?</string>

View file

@ -410,7 +410,7 @@
<string name="deletion_reason_bad_for_my_privacy">Me konstatis ke ol esas mala por mea privateso</string>
<string name="deletion_reason_no_longer_want_public">Me chanjis mea ideo: me ne pluse deziras ke ol esos publike videbla</string>
<string name="deletion_reason_not_interesting">Pardonez! Ca imajo ne esas interesanta por ula enciklopedio</string>
<string name="uploaded_by_myself">Adjuntita da me, che %1$s, uzita en %2$d artiklo/artikli.</string>
<string name="uploaded_by_myself" fuzzy="true">Adjuntita da me, che %1$s, uzita en %2$d artiklo/artikli.</string>
<string name="no_uploads">Bonveno a Commons!\n\nSendez vua unesma arkivo kliktanta sur butono \"adjuntez\" (\'\'add\'\').</string>
<string name="no_categories_selected">Nula kategorio selektita</string>
<string name="no_categories_selected_warning_desc">Imaji sen kategorii rare esas uzebla. Ka vu fakte deziras sendar ol sen selektar irga kategorio?</string>

View file

@ -392,7 +392,7 @@
<string name="deletion_reason_bad_for_my_privacy">Ég áttaði mig á að þetta væri slæmt fyrir gagnaleynd mína</string>
<string name="deletion_reason_no_longer_want_public">Ég skipti um skoðun, ég vil ekki að hún sé lengur öllum sýnileg</string>
<string name="deletion_reason_not_interesting">Afsakið, þessi mynd hefur ekkert gildi fyrir alfræðirit</string>
<string name="uploaded_by_myself">Sent inn af mér sjálfum %1$s, notað í %2$d grein(um).</string>
<string name="uploaded_by_myself" fuzzy="true">Sent inn af mér sjálfum %1$s, notað í %2$d grein(um).</string>
<string name="no_uploads">Velkomin í Commons!\n\nSendu inn fyrstu margmiðlunargögnin þín með því að ýta á viðbætingarhnappinn.</string>
<string name="no_categories_selected">Engir flokkar valdir</string>
<string name="no_categories_selected_warning_desc">Myndir án flokka eru sjaldnast nýtilegar. Ertu viss um að þú viljir halda áfram án þess að velja flokka?</string>

View file

@ -143,6 +143,7 @@
<string name="categories_search_text_hint">Cerca categorie</string>
<string name="depicts_search_text_hint">Cerca elementi che il tuo file rappresenta (montagna, Taj Mahal, ecc.)</string>
<string name="menu_save_categories">Salva</string>
<string name="menu_overflow_desc">Menu overflow</string>
<string name="refresh_button">Aggiorna</string>
<string name="display_list_button">Elenco</string>
<string name="contributions_subtitle_zero">(Non è stato ancora caricato niente)</string>
@ -289,7 +290,7 @@
<string name="send_log_file_description">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</string>
<string name="no_web_browser">Nessun browser web trovato per aprire l\'URL</string>
<string name="null_url">Errore! URL non trovato</string>
<string name="nominate_deletion">Proponi per la Cancellazione</string>
<string name="nominate_deletion">Proponi per la cancellazione</string>
<string name="nominated_for_deletion">Questa immagine è stata proposta per la cancellazione.</string>
<string name="nominated_see_more">Vedi la pagina web per i dettagli</string>
<string name="skip_login">Salta</string>
@ -433,7 +434,7 @@
<string name="deletion_reason_bad_for_my_privacy">Mi sono reso conto che viola la mia privacy</string>
<string name="deletion_reason_no_longer_want_public">Ho cambiato idea, non voglio più che sia pubblicamente visibile</string>
<string name="deletion_reason_not_interesting">Spiacente, questa immagine non è interessante per un\'enciclopedia</string>
<string name="uploaded_by_myself">Caricato da me stesso il %1$s, utilizzato in %2$d voce/i.</string>
<string name="uploaded_by_myself">Caricato da me stesso il %1$s, utilizzato almeno in %2$d voce/i.</string>
<string name="no_uploads">Benvenuto su Commons!\n\nCarica il tuo primo file multimediale toccando il pulsante aggiungi.</string>
<string name="no_categories_selected">Nessuna categoria selezionata</string>
<string name="no_categories_selected_warning_desc">Le immagini senza categorie sono raramente utilizzabili. Sei sicuro di voler continuare senza selezionare le categorie?</string>
@ -594,6 +595,7 @@
<string name="title_for_media">MEDIA</string>
<string name="title_for_child_classes">CLASSI FIGLIE</string>
<string name="title_for_parent_classes">CLASSI SUPERIORI</string>
<string name="title_for_subcategories">Sottocategorie</string>
<string name="upload_nearby_place_found_title">Rinvenuto luogo nei pressi</string>
<string name="upload_nearby_place_found_description_plural">Queste sono immagini di %1$s?</string>
<string name="upload_nearby_place_found_description_singular">Questa è un\'immagine di %1$s?</string>
@ -771,7 +773,7 @@
<string name="learn_how_to_write_a_useful_caption">Impara come scrivere una didascalia utile</string>
<string name="see_your_achievements">Vedi i tuoi risultati</string>
<string name="edit_image">Modifica Immagine</string>
<string name="edit_location">Modifica Posizione</string>
<string name="edit_location">Modifica posizione</string>
<string name="location_updated">Posizione aggiornata!</string>
<string name="remove_location">Rimuovi posizione</string>
<string name="remove_location_warning_title">Rimuovi avviso di posizione</string>
@ -836,4 +838,5 @@
<string name="show_in_nearby">Mostra nelle vicinanze</string>
<string name="image_tag_line_created_and_uploaded_by">Creato e caricato da: %1$s</string>
<string name="image_tag_line_created_by_and_uploaded_by">Creato da %1$s e caricato da %2$s</string>
<string name="nominated_for_deletion_btn">Proposto per la cancellazione</string>
</resources>

View file

@ -444,7 +444,7 @@
<string name="deletion_reason_bad_for_my_privacy">הבנתי שזה לא טוב לפרטיות שלי</string>
<string name="deletion_reason_no_longer_want_public">התחרטתי, אינני רוצה עוד שהיא תוצג לציבור</string>
<string name="deletion_reason_not_interesting">סליחה, התמונה אינה מעניינת עבור אנציקלופדיה</string>
<string name="uploaded_by_myself">הועלה על ידי ב-%1$s, בשימוש ב-%2$d מאמר(ים)</string>
<string name="uploaded_by_myself">הועלה על ידי ב־%1$s, בשימוש ב־%2$d ערכים לפחות.</string>
<string name="no_uploads">ברוך בואך לוויקישיתוף!\n\nכדי להעלות את המדיה הראשונה שלך, נא להקיש על כפתור ההוספה.</string>
<string name="no_categories_selected">לא נבחרו קטגוריות</string>
<string name="no_categories_selected_warning_desc">תמונות ללא קטגוריות בדרך כלל אינן שימושיות. להמשיך ללא בחירת קטגוריות?</string>
@ -862,4 +862,5 @@
<string name="show_in_nearby">בתצוגת בסביבה</string>
<string name="image_tag_line_created_and_uploaded_by">נוצר והועלה על־ידי: %1$s</string>
<string name="image_tag_line_created_by_and_uploaded_by">נוצר על־ידי %1$s והועלה על־ידי %2$s</string>
<string name="nominated_for_deletion_btn">הועמדה למחיקה</string>
</resources>

View file

@ -408,7 +408,7 @@
<string name="deletion_reason_bad_for_my_privacy">自分のプライバシーを傷つけると気がつきました</string>
<string name="deletion_reason_no_longer_want_public">以前とは考えが変わりました。今後は公衆の場で自分の画像を公開したくありません</string>
<string name="deletion_reason_not_interesting">残念ですがこの画像は百科事典の目的に合いません</string>
<string name="uploaded_by_myself">私本人が%1$sにアップロードし、%2$d件の記事で使用されました。</string>
<string name="uploaded_by_myself" fuzzy="true">私本人が%1$sにアップロードし、%2$d件の記事で使用されました。</string>
<string name="no_uploads">コモンズへようこそ!\n\n追加ボタンを押して、ぜひご自分のメディアの初投稿をしましょう。</string>
<string name="no_categories_selected">カテゴリが選択されていません</string>
<string name="no_categories_selected_warning_desc">カテゴリを指定しない画像は使用されることがほとんどありません。ほんとうにカテゴリを選択しないまま作業を続けますか?</string>

View file

@ -113,6 +113,7 @@
<string name="login_failed_throttled">Кёб джетишимсиз сынау болду. Тилейбиз, талай минст сора энтда кёрюгюз.</string>
<string name="login_failed_blocked">Кечериксиз, бу хайырланыучу Гёзенде блок этилгенди</string>
<string name="login_failed_2fa_needed">Эки факторлу аутентификация кодну берирге керексиз.</string>
<string name="login_failed_email_auth_needed">Бегитиу код электрон почта адресигизге джиберилгенди. Кирир ючюн аны салыгъыз.</string>
<string name="login_failed_generic">Системагъа кириуде халат</string>
<string name="share_upload_button">Джюкле</string>
<string name="multiple_share_base_title">Бу къауумха ат бер</string>
@ -216,6 +217,7 @@
<string name="become_a_tester_title">Бета-Тестер болугъуз</string>
<string name="become_a_tester_description">Google Play-де бета каналыбызгъа къошулугъуз эмда джангы функциялагъа эмда халат тюзетиулеге эртде джетишигиз</string>
<string name="_2fa_code">2FA Код</string>
<string name="email_auth_code">Электрон почтаны верификация этигиз</string>
<string name="logout_verification">Кертиден чыгъаргъа излеймисиз?</string>
<string name="mediaimage_failed">Медиа Сурат Джетишимсизди</string>
<string name="no_subcategory_found">Тюб категория табылмады</string>
@ -409,7 +411,7 @@
<string name="deletion_reason_bad_for_my_privacy">Ташалыгъыма аман болгъанын ангыладым</string>
<string name="deletion_reason_no_longer_want_public">Сагъышымы тюрлендирдим, энди хар кимге кёрюннюгюмю излемейме</string>
<string name="deletion_reason_not_interesting">Кечериксиз, бу сурат энциклопедия ючюн эс бёлюрча тюлдю</string>
<string name="uploaded_by_myself">Кесим джюклеген %1$s сайтха джюкленди эмда %2$d статьяда хайырландырылды.</string>
<string name="uploaded_by_myself">Кесим джюклеген %1$s сайтха джюкленди эмда эм азы бла %2$d статьяда хайырландырылды.</string>
<string name="no_uploads">Гёзеннге Хош Келигиз!\n\nКъош тиекге басыб биринчи медиагъызны джюклегиз.</string>
<string name="no_categories_selected">Категорияла Сайланмадыла</string>
<string name="no_categories_selected_warning_desc">Категориясыз суратла аз хайырланадыла. Категория сайламай бардырыргъа излегенигизге ишексизмисиз?</string>
@ -779,6 +781,7 @@
<string name="talk">Сюзюу</string>
<string name="write_something_about_the_item">%1$s элементни юсюнден бир затла джазыгъыз. Буну хар ким да кёрлюкдю.</string>
<string name="does_not_exist_anymore_no_picture_can_ever_be_taken_of_it">\'%1$s\' энди бар тюлдю, аны картха алырча тюлдю.</string>
<string name="is_at_a_different_place_wikidata">\'%1$s\' башха джердеди.</string>
<string name="is_at_a_different_place_please_specify_the_correct_place_below_if_possible_tell_us_the_correct_latitude_longitude">\'%1$s\' - башха джерди. Тилейбиз, тюз джерни энишгерекде белгилегиз, эмда мадар бар эсе, тюз кенглик бла узунлукъну джазыгъыз.</string>
<string name="other_problem_or_information_please_explain_below">Башха проблема неда информация (тилейбиз, энишгерекде ангылатыгъыз).</string>
<string name="feedback_destination_note">Сизни кери оюмугъуз тюбюндеги вики бетге джиберилликди: &lt;a href=\"https://commons.wikimedia.org/wiki/Commons:Mobile_app/Feedback\"&gt;Commons:Mobile app/Feedback&lt;/a&gt;</string>
@ -815,4 +818,7 @@
<string name="congratulations_all_pictures_in_this_album_have_been_either_uploaded_or_marked_as_not_for_upload">Алгъышлайбыз, бу альбомна бютеу сратла не джюкленнгендиле, неда джюкленирге джораланмагъанлача белгиленнгендиле.</string>
<string name="show_in_explore">Explore-де кёргюз</string>
<string name="show_in_nearby">Nearby-да кёргюз</string>
<string name="image_tag_line_created_and_uploaded_by">Болдургъан эм джюклеген: %1$s</string>
<string name="image_tag_line_created_by_and_uploaded_by">%1$s болдурду эмда %2$s джюкледи</string>
<string name="nominated_for_deletion_btn">Кетериуге теджелгенди</string>
</resources>

View file

@ -345,7 +345,7 @@
<string name="deletion_reason_bad_for_my_privacy">M tɛn\'ɛs ye di na an bɛ\'ɛd tisi m mɛŋ gu\'udim yɛla</string>
<string name="deletion_reason_no_longer_want_public">M tiaki m pʋtɛ\'ɛr, m pʋ lɛm bɔɔd ye di bɛ zin\'ikanɛ ka sɔ\' wʋsa na nyɛ ya\'asa</string>
<string name="deletion_reason_not_interesting">Gafara footo kaŋa pʋ nar ye di bɛ encyclopedia</string>
<string name="uploaded_by_myself">M kpɛ\'ɛsidinɛ %1$s m mɛŋ, ka nɔki tʋm %2$d atikil (nam) ni.</string>
<string name="uploaded_by_myself" fuzzy="true">M kpɛ\'ɛsidinɛ %1$s m mɛŋ, ka nɔki tʋm %2$d atikil (nam) ni.</string>
<string name="no_uploads">Commons pʋ\'ʋsidif ken-ken!\n\nKpɛn\'ɛmi fʋ yiiga media dɔlisid fʋn na din paasim la.</string>
<string name="no_categories_selected">Fʋ pʋ gaŋ buudsi\'a</string>
<string name="no_depictions_selected">Fʋ pʋ gaŋ banɛ nwan taaba</string>

View file

@ -134,7 +134,7 @@
<string name="menu_download">Eroflueden</string>
<string name="preference_license">Standardlizenz</string>
<string name="use_previous">Viregen Titel a Beschreiwung benotzen</string>
<string name="preference_theme">Faarfschema</string>
<string name="preference_theme">Faarfscheema</string>
<string name="license_name_cc_by_sa_four">Attribution-ShareAlike 4.0</string>
<string name="license_name_cc_by_four">Attribution 4.0</string>
<string name="license_name_cc_by_sa">CC Attribution-ShareAlike 3.0</string>
@ -295,6 +295,7 @@
<string name="continue_message">Virufueren</string>
<string name="correct">Richteg Äntwert</string>
<string name="wrong">Falsch Äntwert</string>
<string name="quiz_screenshot_question">Ass et an der Rei fir dëse Screenshot eropzelueden?</string>
<string name="share_app_title">App deelen</string>
<string name="rotate">Dréinen</string>
<string name="no_pictures_in_this_area">Keng Biller an dëser Géigend</string>
@ -332,7 +333,7 @@
<string name="deletion_reason_bad_for_my_privacy">Ech hu gemierkt, datt et schlecht fir meng Privatsphär ass</string>
<string name="deletion_reason_no_longer_want_public">Ech hu meng Meenung geännert, ech wëll net méi, datt et ëffentlech siichtbar ass</string>
<string name="deletion_reason_not_interesting">Pardon, dëst Bild ass net interessant fir eng Enzyklopedie</string>
<string name="uploaded_by_myself">Vu mir selwer de(n) %1$s eropgelueden, benotzt a(n) %2$d Artikel(en).</string>
<string name="uploaded_by_myself">Vu mir selwer de(n) %1$s eropgelueden, benotzt a mindestens %2$d Artikel(en).</string>
<string name="no_categories_selected">Keng Kategorien erausgesicht</string>
<string name="no_categories_selected_warning_desc">Biller ouni Kategorië si meeschtens net benotzbar. Sidd Dir sécher, datt Dir weider maache wëllt ouni eng Kategorie auszewielen?</string>
<string name="back_button_warning">Eroplueden ofbriechen</string>
@ -541,4 +542,5 @@
<string name="vanish_account">Kont opléisen</string>
<string name="caption">Beschrëftung</string>
<string name="caption_copied_to_clipboard">Text an den Tëschespäicher kopéiert</string>
<string name="nominated_for_deletion_btn">Virgeschloe fir ze läschen</string>
</resources>

View file

@ -396,7 +396,7 @@
<string name="deletion_reason_bad_for_my_privacy">Supratau, kad tai kenkia mano privatumui</string>
<string name="deletion_reason_no_longer_want_public">Persigalvojau, nebenoriu, kad tai būtų viešai matoma</string>
<string name="deletion_reason_not_interesting">Atsiprašome, ši nuotrauka neįdomi enciklopedijai</string>
<string name="uploaded_by_myself">Įkelta mano %1$s, naudojama %2$d straipsnyje(-iuose).</string>
<string name="uploaded_by_myself" fuzzy="true">Įkelta mano %1$s, naudojama %2$d straipsnyje(-iuose).</string>
<string name="no_uploads">Sveiki atvykę į Vikiteką!\n\nĮkelkite pirmąjį failą bakstelėdami pridėjimo mygtuką.</string>
<string name="no_categories_selected">Nepasirinkta jokių kategorijų</string>
<string name="no_categories_selected_warning_desc">Vaizdai be kategorijų retai naudojami. Ar tikrai norite tęsti nepasirinkę kategorijų?</string>

View file

@ -413,7 +413,7 @@
<string name="deletion_reason_bad_for_my_privacy">Сфатив дека ќе ми ја наруши приватноста</string>
<string name="deletion_reason_no_longer_want_public">Се премислив. Не сакам повеќе да биде видлива</string>
<string name="deletion_reason_not_interesting">За жал, сликава не е соодветна за енциклопедија</string>
<string name="uploaded_by_myself">Подигнато од мене %1$s, кое се користи во %2$d статии.</string>
<string name="uploaded_by_myself">Подигнато од мене на %1$s, и се користи во барем {{PLURAL:%2$d|one=една статија|%2$d статии}}.</string>
<string name="no_uploads">Добре дојдовте на Ризницата!\n\nПодигнете ја вашата прва слика или снимка допирајќи го копчето за додавање.</string>
<string name="no_categories_selected">Нема избрано категории</string>
<string name="no_categories_selected_warning_desc">Некатегоризираните слики се слабо употребливи. Дали сигурно сакате да продолжите без да ставите категории?</string>
@ -825,4 +825,5 @@
<string name="show_in_nearby">Прикажи во „Во близина“</string>
<string name="image_tag_line_created_and_uploaded_by">Создал: %1$s</string>
<string name="image_tag_line_created_by_and_uploaded_by">Создал %1$s, а подигнал %2$s</string>
<string name="nominated_for_deletion_btn">Предложено за бришење</string>
</resources>

Some files were not shown because too many files have changed in this diff Show more