Merge branch 'master' into master

This commit is contained in:
ShridharGoel 2018-06-28 18:40:08 +05:30 committed by GitHub
commit 05def522af
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
179 changed files with 3699 additions and 1650 deletions

View file

@ -19,12 +19,13 @@ android:
components: components:
- tools - tools
- platform-tools - platform-tools
- build-tools-26.0.2 - build-tools-27.0.0
- extra-google-m2repository - extra-google-m2repository
- extra-android-m2repository - extra-android-m2repository
- ${ANDROID_TARGET} - ${ANDROID_TARGET}
- android-25 - android-25
- android-26 - android-26
- android-27
- sys-img-${ANDROID_ABI}-${ANDROID_TARGET} - sys-img-${ANDROID_ABI}-${ANDROID_TARGET}
licenses: licenses:
- 'android-sdk-license-.+' - 'android-sdk-license-.+'

View file

@ -1,5 +1,8 @@
# Wikimedia Commons for Android # Wikimedia Commons for Android
## v2.7.2
- Modified subtext for "automatically get current location" setting to emphasize that it will reveal user's location
## v2.7.1 ## v2.7.1
- Fixed UI and permission issues with Nearby - Fixed UI and permission issues with Nearby
- Fixed issue with My Recent Uploads being empty - Fixed issue with My Recent Uploads being empty

View file

@ -1,3 +1,7 @@
## Title (required)
Fixes #{GitHub issue number and title (Please do not forget adding title) }
## Description (required) ## Description (required)
Fixes #{GitHub issue number and title} Fixes #{GitHub issue number and title}
@ -12,4 +16,4 @@ Tested on {API level & name of device/emulator}, with {build variant, e.g. ProdD
{Only for user interface changes, otherwise remove this section. See [how to take a screenshot](https://android.stackexchange.com/questions/1759/how-to-take-a-screenshot-with-an-android-device)} {Only for user interface changes, otherwise remove this section. See [how to take a screenshot](https://android.stackexchange.com/questions/1759/how-to-take-a-screenshot-with-an-android-device)}
_Note: Please ensure that you have read CONTRIBUTING.md if this is your first pull request._ _Note: Please ensure that you have read CONTRIBUTING.md if this is your first pull request._

View file

@ -7,11 +7,12 @@ apply from: 'quality.gradle'
apply plugin: 'com.getkeepsafe.dexcount' apply plugin: 'com.getkeepsafe.dexcount'
dependencies { dependencies {
implementation 'com.squareup.picasso:picasso:2.71828'
implementation 'com.prof.rssparser:rssparser:1.1'
implementation 'com.github.nicolas-raoul:Quadtree:ac16ea8035bf07' implementation 'com.github.nicolas-raoul:Quadtree:ac16ea8035bf07'
implementation 'fr.avianey.com.viewpagerindicator:library:2.4.1.1@aar' implementation 'fr.avianey.com.viewpagerindicator:library:2.4.1.1@aar'
implementation 'in.yuvi:http.fluent:1.3' implementation 'in.yuvi:http.fluent:1.3'
implementation 'com.github.chrisbanes:PhotoView:2.0.0' implementation 'com.github.chrisbanes:PhotoView:2.0.0'
implementation 'com.android.volley:volley:1.0.0'
implementation 'ch.acra:acra:4.9.2' implementation 'ch.acra:acra:4.9.2'
implementation 'org.mediawiki:api:1.3' implementation 'org.mediawiki:api:1.3'
implementation 'commons-codec:commons-codec:1.10' implementation 'commons-codec:commons-codec:1.10'
@ -21,47 +22,36 @@ dependencies {
implementation 'info.debatty:java-string-similarity:0.24' implementation 'info.debatty:java-string-similarity:0.24'
implementation 'com.borjabravo:readmoretextview:2.1.0' implementation 'com.borjabravo:readmoretextview:2.1.0'
implementation 'com.android.support.constraint:constraint-layout:1.0.2' implementation 'com.android.support.constraint:constraint-layout:1.0.2'
implementation ('com.mapbox.mapboxsdk:mapbox-android-sdk:5.4.1@aar'){ implementation('com.mapbox.mapboxsdk:mapbox-android-sdk:5.5.0@aar') {
transitive=true transitive = true
} }
implementation 'com.github.deano2390:MaterialShowcaseView:1.2.0'
implementation "com.github.deano2390:MaterialShowcaseView:1.2.0"
implementation "com.android.support:support-v4:$SUPPORT_LIB_VERSION" implementation "com.android.support:support-v4:$SUPPORT_LIB_VERSION"
implementation "com.android.support:appcompat-v7:$SUPPORT_LIB_VERSION" implementation "com.android.support:appcompat-v7:$SUPPORT_LIB_VERSION"
implementation "com.android.support:design:$SUPPORT_LIB_VERSION" implementation "com.android.support:design:$SUPPORT_LIB_VERSION"
implementation "com.android.support:customtabs:$SUPPORT_LIB_VERSION" implementation "com.android.support:customtabs:$SUPPORT_LIB_VERSION"
implementation "com.android.support:cardview-v7:$SUPPORT_LIB_VERSION" implementation "com.android.support:cardview-v7:$SUPPORT_LIB_VERSION"
implementation "com.jakewharton:butterknife:$BUTTERKNIFE_VERSION" implementation "com.jakewharton:butterknife:$BUTTERKNIFE_VERSION"
kapt "com.jakewharton:butterknife-compiler:$BUTTERKNIFE_VERSION" kapt "com.jakewharton:butterknife-compiler:$BUTTERKNIFE_VERSION"
implementation 'com.squareup.okhttp3:okhttp:3.9.1' implementation 'com.squareup.okhttp3:okhttp:3.9.1'
implementation 'com.squareup.okio:okio:1.13.0' implementation 'com.squareup.okio:okio:1.13.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.0.1' implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
// Because RxAndroid releases are few and far between, it is recommended you also // Because RxAndroid releases are few and far between, it is recommended you also
// explicitly depend on RxJava's latest version for bug fixes and new features. // explicitly depend on RxJava's latest version for bug fixes and new features.
implementation 'com.android.support:multidex:1.0.3' implementation 'com.android.support:multidex:1.0.3'
implementation 'io.reactivex.rxjava2:rxjava:2.1.2' implementation 'io.reactivex.rxjava2:rxjava:2.1.2'
implementation 'com.jakewharton.rxbinding2:rxbinding:2.0.0' implementation 'com.jakewharton.rxbinding2:rxbinding:2.0.0'
implementation 'com.jakewharton.rxbinding2:rxbinding-support-v4:2.0.0' implementation 'com.jakewharton.rxbinding2:rxbinding-support-v4:2.0.0'
implementation 'com.jakewharton.rxbinding2:rxbinding-appcompat-v7:2.0.0' implementation 'com.jakewharton.rxbinding2:rxbinding-appcompat-v7:2.0.0'
implementation 'com.jakewharton.rxbinding2:rxbinding-design:2.0.0' implementation 'com.jakewharton.rxbinding2:rxbinding-design:2.0.0'
implementation 'org.jsoup:jsoup:1.11.3' implementation 'org.jsoup:jsoup:1.11.3'
implementation 'com.facebook.fresco:fresco:1.5.0' implementation 'com.facebook.fresco:fresco:1.5.0'
implementation 'com.facebook.stetho:stetho:1.5.0' implementation 'com.facebook.stetho:stetho:1.5.0'
implementation "com.google.dagger:dagger:$DAGGER_VERSION" implementation "com.google.dagger:dagger:$DAGGER_VERSION"
implementation "com.google.dagger:dagger-android-support:$DAGGER_VERSION" implementation "com.google.dagger:dagger-android-support:$DAGGER_VERSION"
kapt "com.google.dagger:dagger-android-processor:$DAGGER_VERSION" kapt "com.google.dagger:dagger-android-processor:$DAGGER_VERSION"
kapt "com.google.dagger:dagger-compiler:$DAGGER_VERSION" kapt "com.google.dagger:dagger-compiler:$DAGGER_VERSION"
testImplementation 'org.robolectric:multidex:3.4.2'
testImplementation "org.robolectric:multidex:3.4.2"
testImplementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" testImplementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
testImplementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" testImplementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
@ -69,10 +59,16 @@ dependencies {
testImplementation 'com.nhaarman:mockito-kotlin:1.5.0' testImplementation 'com.nhaarman:mockito-kotlin:1.5.0'
testImplementation 'com.squareup.okhttp3:mockwebserver:3.8.1' testImplementation 'com.squareup.okhttp3:mockwebserver:3.8.1'
implementation 'com.caverock:androidsvg:1.2.1'
implementation 'com.github.bumptech.glide:glide:4.7.1'
kapt 'com.github.bumptech.glide:compiler:4.7.1'
androidTestImplementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" androidTestImplementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
androidTestImplementation 'com.squareup.okhttp3:mockwebserver:3.8.1' androidTestImplementation 'com.squareup.okhttp3:mockwebserver:3.8.1'
androidTestImplementation "com.android.support:support-annotations:$SUPPORT_LIB_VERSION" androidTestImplementation "com.android.support:support-annotations:$SUPPORT_LIB_VERSION"
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2-alpha1' androidTestImplementation 'com.android.support.test:rules:1.0.2'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
debugImplementation "com.squareup.leakcanary:leakcanary-android:$LEAK_CANARY" debugImplementation "com.squareup.leakcanary:leakcanary-android:$LEAK_CANARY"
releaseImplementation "com.squareup.leakcanary:leakcanary-android-no-op:$LEAK_CANARY" releaseImplementation "com.squareup.leakcanary:leakcanary-android-no-op:$LEAK_CANARY"
@ -87,8 +83,8 @@ android {
defaultConfig { defaultConfig {
applicationId 'fr.free.nrw.commons' applicationId 'fr.free.nrw.commons'
versionCode 84 versionCode 85
versionName '2.7.1' versionName '2.7.2'
setProperty("archivesBaseName", "app-commons-v$versionName-" + getBranchName()) setProperty("archivesBaseName", "app-commons-v$versionName-" + getBranchName())
minSdkVersion project.minSdkVersion minSdkVersion project.minSdkVersion
@ -117,7 +113,7 @@ android {
buildTypes { buildTypes {
release { release {
minifyEnabled false // See https://stackoverflow.com/questions/40232404/google-play-apk-and-android-studio-apk-usb-debug-behaving-differently - proguard.cfg modification alone insufficient. minifyEnabled false // See https://stackoverflow.com/questions/40232404/google-play-apk-and-android-studio-apk-usb-debug-behaving-differently - proguard.cfg modification alone insufficient.
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt', 'proguard-glide.txt'
} }
debug { debug {
applicationIdSuffix ".debug" applicationIdSuffix ".debug"
@ -129,7 +125,9 @@ android {
flavorDimensions 'tier' flavorDimensions 'tier'
productFlavors { productFlavors {
prod { prod {
buildConfigField "String", "WIKIMEDIA_API_POTD", "\"https://commons.wikimedia.org/w/api.php?action=featuredfeed&feed=potd&feedformat=rss&language=en\""
buildConfigField "String", "WIKIMEDIA_API_HOST", "\"https://commons.wikimedia.org/w/api.php\"" buildConfigField "String", "WIKIMEDIA_API_HOST", "\"https://commons.wikimedia.org/w/api.php\""
buildConfigField "String", "WIKIDATA_API_HOST", "\"https://www.wikidata.org/w/api.php\""
buildConfigField "String", "WIKIMEDIA_FORGE_API_HOST", "\"https://tools.wmflabs.org/\"" buildConfigField "String", "WIKIMEDIA_FORGE_API_HOST", "\"https://tools.wmflabs.org/\""
buildConfigField "String", "IMAGE_URL_BASE", "\"https://upload.wikimedia.org/wikipedia/commons\"" buildConfigField "String", "IMAGE_URL_BASE", "\"https://upload.wikimedia.org/wikipedia/commons\""
buildConfigField "String", "HOME_URL", "\"https://commons.wikimedia.org/wiki/\"" buildConfigField "String", "HOME_URL", "\"https://commons.wikimedia.org/wiki/\""
@ -145,7 +143,9 @@ android {
beta { beta {
// What values do we need to hit the BETA versions of the site / api ? // What values do we need to hit the BETA versions of the site / api ?
buildConfigField "String", "WIKIMEDIA_API_POTD", "\"https://commons.wikimedia.org/w/api.php?action=featuredfeed&feed=potd&feedformat=rss&language=en\""
buildConfigField "String", "WIKIMEDIA_API_HOST", "\"https://commons.wikimedia.beta.wmflabs.org/w/api.php\"" buildConfigField "String", "WIKIMEDIA_API_HOST", "\"https://commons.wikimedia.beta.wmflabs.org/w/api.php\""
buildConfigField "String", "WIKIDATA_API_HOST", "\"https://www.wikidata.org/w/api.php\""
buildConfigField "String", "WIKIMEDIA_FORGE_API_HOST", "\"https://tools.wmflabs.org/\"" buildConfigField "String", "WIKIMEDIA_FORGE_API_HOST", "\"https://tools.wmflabs.org/\""
buildConfigField "String", "IMAGE_URL_BASE", "\"https://upload.beta.wmflabs.org/wikipedia/commons\"" buildConfigField "String", "IMAGE_URL_BASE", "\"https://upload.beta.wmflabs.org/wikipedia/commons\""
buildConfigField "String", "HOME_URL", "\"https://commons.wikimedia.beta.wmflabs.org/wiki/\"" buildConfigField "String", "HOME_URL", "\"https://commons.wikimedia.beta.wmflabs.org/wiki/\""

BIN
app/libs/java-json.jar Normal file

Binary file not shown.

9
app/proguard-glide.txt Normal file
View file

@ -0,0 +1,9 @@
-keep public class * implements com.bumptech.glide.module.GlideModule
-keep public class * extends com.bumptech.glide.module.AppGlideModule
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
**[] $VALUES;
public *;
}
# for DexGuard only
-keepresourcexmlelements manifest/application/meta-data@value=GlideModule

View file

@ -1,5 +1,4 @@
-dontobfuscate -dontobfuscate
-keep class org.apache.http.** { *; } -keep class org.apache.http.** { *; }
-dontwarn org.apache.http.** -dontwarn org.apache.http.**
-keep class fr.free.nrw.commons.upload.MwVolleyApi$Page {*;}
-keep class android.support.v7.widget.ShareActionProvider { *; } -keep class android.support.v7.widget.ShareActionProvider { *; }

View file

@ -18,7 +18,7 @@ task checkstyle(type: Checkstyle) {
reports { reports {
html { html {
enabled true enabled true
destination "${project.buildDir}/reports/checkstyle/checkstyle.html" destination file("${project.buildDir}/reports/checkstyle/checkstyle.html")
} }
} }
} }
@ -36,10 +36,10 @@ task pmd(type: Pmd) {
xml.enabled = false xml.enabled = false
html.enabled = true html.enabled = true
xml { xml {
destination "${project.buildDir}/reports/pmd/pmd.xml" destination file("${project.buildDir}/reports/pmd/pmd.xml")
} }
html { html {
destination "${project.buildDir}/reports/pmd/pmd.html" destination file("${project.buildDir}/reports/pmd/pmd.html")
} }
} }
} }

View file

@ -15,6 +15,7 @@
<uses-permission android:name="android.permission.MANAGE_DOCUMENTS" /> <uses-permission android:name="android.permission.MANAGE_DOCUMENTS" />
<uses-permission android:name="com.google.android.apps.photos.permission.GOOGLE_PHOTOS" /> <uses-permission android:name="com.google.android.apps.photos.permission.GOOGLE_PHOTOS" />
<uses-permission android:name="android.permission.READ_LOGS"/> <uses-permission android:name="android.permission.READ_LOGS"/>
<uses-permission android:name="android.permission.SET_WALLPAPER"/>
<!-- Needed only if your app targets Android 5.0 (API level 21) or higher. --> <!-- Needed only if your app targets Android 5.0 (API level 21) or higher. -->
<uses-feature android:name="android.hardware.location.gps" /> <uses-feature android:name="android.hardware.location.gps" />
@ -26,10 +27,10 @@
android:theme="@style/LightAppTheme" android:theme="@style/LightAppTheme"
android:supportsRtl="true" > android:supportsRtl="true" >
<activity android:name="org.acra.CrashReportDialog" <activity android:name="org.acra.CrashReportDialog"
android:theme="@android:style/Theme.Dialog" android:theme="@android:style/Theme.Dialog"
android:launchMode="singleInstance" android:launchMode="singleInstance"
android:excludeFromRecents="true" android:excludeFromRecents="true"
android:finishOnTaskLaunch="true" /> android:finishOnTaskLaunch="true" />
<activity android:name=".auth.LoginActivity"> <activity android:name=".auth.LoginActivity">
<intent-filter> <intent-filter>
@ -164,6 +165,16 @@
android:label="@string/provider_categories" android:label="@string/provider_categories"
android:syncable="false" /> android:syncable="false" />
<receiver android:name=".widget.PicOfDayAppWidget">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/pic_of_day_app_widget_info" />
</receiver>
</application> </application>
</manifest> </manifest>

View file

@ -9,9 +9,6 @@ import android.os.Bundle;
import android.text.Html; import android.text.Html;
import android.text.SpannableString; import android.text.SpannableString;
import android.text.style.UnderlineSpan; import android.text.style.UnderlineSpan;
import android.util.Log;
import android.support.customtabs.CustomTabsIntent;
import android.support.v4.content.ContextCompat;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
@ -20,7 +17,6 @@ import android.widget.ArrayAdapter;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.Spinner; import android.widget.Spinner;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast;
import butterknife.BindView; import butterknife.BindView;
import butterknife.ButterKnife; import butterknife.ButterKnife;
@ -28,8 +24,6 @@ import butterknife.OnClick;
import fr.free.nrw.commons.theme.NavigationBaseActivity; import fr.free.nrw.commons.theme.NavigationBaseActivity;
import fr.free.nrw.commons.ui.widget.HtmlTextView; import fr.free.nrw.commons.ui.widget.HtmlTextView;
import static android.widget.Toast.LENGTH_SHORT;
/** /**
* Represents about screen of this app * Represents about screen of this app
*/ */

View file

@ -1,6 +1,5 @@
package fr.free.nrw.commons; package fr.free.nrw.commons;
import android.app.Application;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
@ -27,7 +26,7 @@ import fr.free.nrw.commons.contributions.ContributionDao;
import fr.free.nrw.commons.data.DBOpenHelper; import fr.free.nrw.commons.data.DBOpenHelper;
import fr.free.nrw.commons.di.ApplicationlessInjection; import fr.free.nrw.commons.di.ApplicationlessInjection;
import fr.free.nrw.commons.modifications.ModifierSequenceDao; import fr.free.nrw.commons.modifications.ModifierSequenceDao;
import fr.free.nrw.commons.utils.FileUtils; import fr.free.nrw.commons.upload.FileUtils;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers; import io.reactivex.schedulers.Schedulers;
import timber.log.Timber; import timber.log.Timber;

View file

@ -178,6 +178,7 @@ public class Utils {
} }
public static void handleWebUrl(Context context, Uri url) { public static void handleWebUrl(Context context, Uri url) {
Timber.d("Launching web url %s", url.toString());
Intent browserIntent = new Intent(Intent.ACTION_VIEW, url); Intent browserIntent = new Intent(Intent.ACTION_VIEW, url);
if (browserIntent.resolveActivity(context.getPackageManager()) == null) { if (browserIntent.resolveActivity(context.getPackageManager()) == null) {
Toast toast = Toast.makeText(context, context.getString(R.string.no_web_browser), LENGTH_SHORT); Toast toast = Toast.makeText(context, context.getString(R.string.no_web_browser), LENGTH_SHORT);

View file

@ -4,8 +4,8 @@ import android.accounts.Account;
import android.accounts.AccountAuthenticatorActivity; import android.accounts.AccountAuthenticatorActivity;
import android.accounts.AccountAuthenticatorResponse; import android.accounts.AccountAuthenticatorResponse;
import android.accounts.AccountManager; import android.accounts.AccountManager;
import android.app.Activity;
import android.app.ProgressDialog; import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.net.Uri; import android.net.Uri;
@ -23,7 +23,6 @@ import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button; import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
import android.widget.TextView; import android.widget.TextView;
@ -136,6 +135,11 @@ public class LoginActivity extends AccountAuthenticatorActivity {
} }
} }
public static void startYourself(Context context) {
Intent intent = new Intent(context, LoginActivity.class);
context.startActivity(intent);
}
private void forgotPassword() { private void forgotPassword() {
Utils.handleWebUrl(this, Uri.parse(BuildConfig.FORGOT_PASSWORD_URL)); Utils.handleWebUrl(this, Uri.parse(BuildConfig.FORGOT_PASSWORD_URL));
} }

View file

@ -81,6 +81,12 @@ public class SessionManager {
return sharedPreferences.getBoolean("isUserLoggedIn", false); return sharedPreferences.getBoolean("isUserLoggedIn", false);
} }
public void forceLogin(Context context) {
if (context != null) {
LoginActivity.startYourself(context);
}
}
public Completable clearAllAccounts() { public Completable clearAllAccounts() {
AccountManager accountManager = AccountManager.get(context); AccountManager accountManager = AccountManager.get(context);
Account[] allAccounts = accountManager.getAccountsByType(ACCOUNT_TYPE); Account[] allAccounts = accountManager.getAccountsByType(ACCOUNT_TYPE);

View file

@ -7,18 +7,25 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import fr.free.nrw.commons.upload.MwVolleyApi; import javax.inject.Inject;
import javax.inject.Singleton;
import fr.free.nrw.commons.upload.GpsCategoryModel;
import timber.log.Timber; import timber.log.Timber;
@Singleton
public class CacheController { public class CacheController {
private final GpsCategoryModel gpsCategoryModel;
private final QuadTree<List<String>> quadTree;
private double x, y; private double x, y;
private QuadTree<List<String>> quadTree;
private double xMinus, xPlus, yMinus, yPlus; private double xMinus, xPlus, yMinus, yPlus;
private static final int EARTH_RADIUS = 6378137; private static final int EARTH_RADIUS = 6378137;
public CacheController() { @Inject
CacheController(GpsCategoryModel gpsCategoryModel) {
this.gpsCategoryModel = gpsCategoryModel;
quadTree = new QuadTree<>(-180, -90, +180, +90); quadTree = new QuadTree<>(-180, -90, +180, +90);
} }
@ -31,8 +38,8 @@ public class CacheController {
public void cacheCategory() { public void cacheCategory() {
List<String> pointCatList = new ArrayList<>(); List<String> pointCatList = new ArrayList<>();
if (MwVolleyApi.GpsCatExists.getGpsCatExists()) { if (gpsCategoryModel.getGpsCatExists()) {
pointCatList.addAll(MwVolleyApi.getGpsCat()); pointCatList.addAll(gpsCategoryModel.getCategoryList());
Timber.d("Categories being cached: %s", pointCatList); Timber.d("Categories being cached: %s", pointCatList);
} else { } else {
Timber.d("No categories found, so no categories cached"); Timber.d("No categories found, so no categories cached");
@ -65,7 +72,7 @@ public class CacheController {
} }
//Based on algorithm at http://gis.stackexchange.com/questions/2951/algorithm-for-offsetting-a-latitude-longitude-by-some-amount-of-meters //Based on algorithm at http://gis.stackexchange.com/questions/2951/algorithm-for-offsetting-a-latitude-longitude-by-some-amount-of-meters
public void convertCoordRange() { private void convertCoordRange() {
//Position, decimal degrees //Position, decimal degrees
double lat = y; double lat = y;
double lon = x; double lon = x;

View file

@ -39,7 +39,7 @@ import butterknife.ButterKnife;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment; import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
import fr.free.nrw.commons.mwapi.MediaWikiApi; import fr.free.nrw.commons.mwapi.MediaWikiApi;
import fr.free.nrw.commons.upload.MwVolleyApi; import fr.free.nrw.commons.upload.GpsCategoryModel;
import fr.free.nrw.commons.utils.StringSortingUtils; import fr.free.nrw.commons.utils.StringSortingUtils;
import fr.free.nrw.commons.utils.ViewUtil; import fr.free.nrw.commons.utils.ViewUtil;
import io.reactivex.Observable; import io.reactivex.Observable;
@ -73,6 +73,7 @@ public class CategorizationFragment extends CommonsDaggerSupportFragment {
@Inject @Named("prefs") SharedPreferences prefsPrefs; @Inject @Named("prefs") SharedPreferences prefsPrefs;
@Inject @Named("direct_nearby_upload_prefs") SharedPreferences directPrefs; @Inject @Named("direct_nearby_upload_prefs") SharedPreferences directPrefs;
@Inject CategoryDao categoryDao; @Inject CategoryDao categoryDao;
@Inject GpsCategoryModel gpsCategoryModel;
private RVRendererAdapter<CategoryItem> categoriesAdapter; private RVRendererAdapter<CategoryItem> categoriesAdapter;
private OnCategoriesSaveHandler onCategoriesSaveHandler; private OnCategoriesSaveHandler onCategoriesSaveHandler;
@ -253,7 +254,6 @@ public class CategorizationFragment extends CommonsDaggerSupportFragment {
} }
private Observable<CategoryItem> defaultCategories() { private Observable<CategoryItem> defaultCategories() {
Observable<CategoryItem> directCat = directCategories(); Observable<CategoryItem> directCat = directCategories();
if (hasDirectCategories) { if (hasDirectCategories) {
Timber.d("Image has direct Cat"); Timber.d("Image has direct Cat");
@ -287,9 +287,7 @@ public class CategorizationFragment extends CommonsDaggerSupportFragment {
} }
private Observable<CategoryItem> gpsCategories() { private Observable<CategoryItem> gpsCategories() {
return Observable.fromIterable( return Observable.fromIterable(gpsCategoryModel.getCategoryList())
MwVolleyApi.GpsCatExists.getGpsCatExists()
? MwVolleyApi.getGpsCat() : new ArrayList<>())
.map(name -> new CategoryItem(name, false)); .map(name -> new CategoryItem(name, false));
} }

View file

@ -14,7 +14,6 @@ import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.auth.AuthenticatedActivity; import fr.free.nrw.commons.auth.AuthenticatedActivity;
import fr.free.nrw.commons.media.MediaDetailPagerFragment; import fr.free.nrw.commons.media.MediaDetailPagerFragment;
import timber.log.Timber;
/** /**
* This activity displays pictures of a particular category * This activity displays pictures of a particular category

View file

@ -224,4 +224,14 @@ public class CategoryImagesListFragment extends DaggerFragment {
public ListAdapter getAdapter() { public ListAdapter getAdapter() {
return gridView.getAdapter(); return gridView.getAdapter();
} }
/**
* This method will be called on back pressed of CategoryImagesActivity.
* It initializes the grid view by setting adapter.
*/
@Override
public void onResume() {
gridView.setAdapter(gridAdapter);
super.onResume();
}
} }

View file

@ -45,6 +45,7 @@ public class Contribution extends Media {
private long transferred; private long transferred;
private String decimalCoords; private String decimalCoords;
private boolean isMultiple; private boolean isMultiple;
private String wikiDataEntityId;
public Contribution(Uri contentUri, String filename, Uri localUri, String imageUrl, Date timestamp, public Contribution(Uri contentUri, String filename, Uri localUri, String imageUrl, Date timestamp,
int state, long dataLength, Date dateUploaded, long transferred, int state, long dataLength, Date dateUploaded, long transferred,
@ -222,4 +223,17 @@ public class Contribution extends Media {
throw new RuntimeException("Unrecognized license value: " + license); throw new RuntimeException("Unrecognized license value: " + license);
} }
public String getWikiDataEntityId() {
return wikiDataEntityId;
}
/**
* When the corresponding wikidata entity is known as in case of nearby uploads, it can be set
* using the setter method
* @param wikiDataEntityId
*/
public void setWikiDataEntityId(String wikiDataEntityId) {
this.wikiDataEntityId = wikiDataEntityId;
}
} }

View file

@ -90,7 +90,7 @@ public class ContributionController {
fragment.startActivityForResult(pickImageIntent, SELECT_FROM_GALLERY); fragment.startActivityForResult(pickImageIntent, SELECT_FROM_GALLERY);
} }
public void handleImagePicked(int requestCode, Intent data, boolean isDirectUpload) { public void handleImagePicked(int requestCode, Intent data, boolean isDirectUpload, String wikiDataEntityId) {
FragmentActivity activity = fragment.getActivity(); FragmentActivity activity = fragment.getActivity();
Timber.d("handleImagePicked() called with onActivityResult()"); Timber.d("handleImagePicked() called with onActivityResult()");
Intent shareIntent = new Intent(activity, ShareActivity.class); Intent shareIntent = new Intent(activity, ShareActivity.class);
@ -102,9 +102,6 @@ public class ContributionController {
shareIntent.setType(activity.getContentResolver().getType(imageData)); shareIntent.setType(activity.getContentResolver().getType(imageData));
shareIntent.putExtra(EXTRA_STREAM, imageData); shareIntent.putExtra(EXTRA_STREAM, imageData);
shareIntent.putExtra(EXTRA_SOURCE, SOURCE_GALLERY); shareIntent.putExtra(EXTRA_SOURCE, SOURCE_GALLERY);
if (isDirectUpload) {
shareIntent.putExtra("isDirectUpload", true);
}
break; break;
case SELECT_FROM_CAMERA: case SELECT_FROM_CAMERA:
//FIXME: Find out appropriate mime type //FIXME: Find out appropriate mime type
@ -113,9 +110,6 @@ public class ContributionController {
shareIntent.setType("image/jpeg"); shareIntent.setType("image/jpeg");
shareIntent.putExtra(EXTRA_STREAM, lastGeneratedCaptureUri); shareIntent.putExtra(EXTRA_STREAM, lastGeneratedCaptureUri);
shareIntent.putExtra(EXTRA_SOURCE, SOURCE_CAMERA); shareIntent.putExtra(EXTRA_SOURCE, SOURCE_CAMERA);
if (isDirectUpload) {
shareIntent.putExtra("isDirectUpload", true);
}
break; break;
default: default:
@ -123,6 +117,10 @@ public class ContributionController {
} }
Timber.i("Image selected"); Timber.i("Image selected");
try { try {
shareIntent.putExtra("isDirectUpload", isDirectUpload);
if (wikiDataEntityId != null && !wikiDataEntityId.equals("")) {
shareIntent.putExtra("wikiDataEntityId", wikiDataEntityId);
}
activity.startActivity(shareIntent); activity.startActivity(shareIntent);
} catch (SecurityException e) { } catch (SecurityException e) {
Timber.e(e, "Security Exception"); Timber.e(e, "Security Exception");

View file

@ -8,7 +8,6 @@ import android.net.Uri;
import android.os.RemoteException; import android.os.RemoteException;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log;
import java.util.Date; import java.util.Date;

View file

@ -276,17 +276,25 @@ public class ContributionsActivity
.getUploadCount(sessionManager.getCurrentAccount().name) .getUploadCount(sessionManager.getCurrentAccount().name)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe( .subscribe(this::displayUploadCount,
uploadCount -> getSupportActionBar().setSubtitle(getResources()
.getQuantityString(R.plurals.contributions_subtitle,
uploadCount, uploadCount)),
t -> Timber.e(t, "Fetching upload count failed") t -> Timber.e(t, "Fetching upload count failed")
)); ));
} }
public void betaSetUploadCount(int betaUploadCount){ private void displayUploadCount(Integer uploadCount) {
if (isFinishing()
|| getSupportActionBar() == null
|| getResources() == null) {
return;
}
getSupportActionBar().setSubtitle(getResources() getSupportActionBar().setSubtitle(getResources()
.getQuantityString(R.plurals.contributions_subtitle, betaUploadCount, betaUploadCount)); .getQuantityString(R.plurals.contributions_subtitle,
uploadCount, uploadCount));
}
public void betaSetUploadCount(int betaUploadCount) {
displayUploadCount(betaUploadCount);
} }

View file

@ -117,7 +117,7 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment {
if (resultCode == RESULT_OK) { if (resultCode == RESULT_OK) {
Timber.d("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s", Timber.d("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
requestCode, resultCode, data); requestCode, resultCode, data);
controller.handleImagePicked(requestCode, data, false); controller.handleImagePicked(requestCode, data, false, null);
} else { } else {
Timber.e("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s", Timber.e("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
requestCode, resultCode, data); requestCode, resultCode, data);

View file

@ -9,17 +9,18 @@ import dagger.android.support.AndroidSupportInjectionModule;
import fr.free.nrw.commons.CommonsApplication; import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.MediaWikiImageView; import fr.free.nrw.commons.MediaWikiImageView;
import fr.free.nrw.commons.auth.LoginActivity; import fr.free.nrw.commons.auth.LoginActivity;
import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.contributions.ContributionsActivity;
import fr.free.nrw.commons.contributions.ContributionsSyncAdapter; import fr.free.nrw.commons.contributions.ContributionsSyncAdapter;
import fr.free.nrw.commons.delete.DeleteTask; import fr.free.nrw.commons.delete.DeleteTask;
import fr.free.nrw.commons.modifications.ModificationsSyncAdapter; import fr.free.nrw.commons.modifications.ModificationsSyncAdapter;
import fr.free.nrw.commons.settings.SettingsFragment;
import fr.free.nrw.commons.nearby.PlaceRenderer; import fr.free.nrw.commons.nearby.PlaceRenderer;
import fr.free.nrw.commons.upload.FileProcessor;
import fr.free.nrw.commons.settings.SettingsFragment;
@Singleton @Singleton
@Component(modules = { @Component(modules = {
CommonsApplicationModule.class, CommonsApplicationModule.class,
NetworkingModule.class,
AndroidInjectionModule.class, AndroidInjectionModule.class,
AndroidSupportInjectionModule.class, AndroidSupportInjectionModule.class,
ActivityBuilderModule.class, ActivityBuilderModule.class,
@ -47,6 +48,8 @@ public interface CommonsApplicationComponent extends AndroidInjector<Application
void inject(PlaceRenderer placeRenderer); void inject(PlaceRenderer placeRenderer);
void inject(FileProcessor fileProcessor);
@Component.Builder @Component.Builder
@SuppressWarnings({"WeakerAccess", "unused"}) @SuppressWarnings({"WeakerAccess", "unused"})
interface Builder { interface Builder {

View file

@ -6,24 +6,21 @@ import android.content.SharedPreferences;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.support.v4.util.LruCache; import android.support.v4.util.LruCache;
import com.google.gson.Gson;
import javax.inject.Named; import javax.inject.Named;
import javax.inject.Singleton; import javax.inject.Singleton;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.auth.AccountUtil; import fr.free.nrw.commons.auth.AccountUtil;
import fr.free.nrw.commons.auth.SessionManager; import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.caching.CacheController;
import fr.free.nrw.commons.data.DBOpenHelper; import fr.free.nrw.commons.data.DBOpenHelper;
import fr.free.nrw.commons.location.LocationServiceManager; import fr.free.nrw.commons.location.LocationServiceManager;
import fr.free.nrw.commons.mwapi.ApacheHttpClientMediaWikiApi;
import fr.free.nrw.commons.mwapi.MediaWikiApi; import fr.free.nrw.commons.mwapi.MediaWikiApi;
import fr.free.nrw.commons.nearby.NearbyPlaces; import fr.free.nrw.commons.nearby.NearbyPlaces;
import fr.free.nrw.commons.upload.UploadController; import fr.free.nrw.commons.upload.UploadController;
import fr.free.nrw.commons.wikidata.WikidataEditListener;
import fr.free.nrw.commons.wikidata.WikidataEditListenerImpl;
import static android.content.Context.MODE_PRIVATE; import static android.content.Context.MODE_PRIVATE;
import static fr.free.nrw.commons.contributions.ContributionsContentProvider.CONTRIBUTION_AUTHORITY; import static fr.free.nrw.commons.contributions.ContributionsContentProvider.CONTRIBUTION_AUTHORITY;
@ -33,7 +30,6 @@ import static fr.free.nrw.commons.modifications.ModificationsContentProvider.MOD
@SuppressWarnings({"WeakerAccess", "unused"}) @SuppressWarnings({"WeakerAccess", "unused"})
public class CommonsApplicationModule { public class CommonsApplicationModule {
public static final String CATEGORY_AUTHORITY = "fr.free.nrw.commons.categories.contentprovider"; public static final String CATEGORY_AUTHORITY = "fr.free.nrw.commons.categories.contentprovider";
public static final long OK_HTTP_CACHE_SIZE = 10 * 1024 * 1024;
private Context applicationContext; private Context applicationContext;
@ -117,37 +113,12 @@ public class CommonsApplicationModule {
return new SessionManager(context, mediaWikiApi, sharedPreferences); return new SessionManager(context, mediaWikiApi, sharedPreferences);
} }
@Provides
@Singleton
public MediaWikiApi provideMediaWikiApi(Context context,
@Named("default_preferences") SharedPreferences defaultPreferences,
@Named("category_prefs") SharedPreferences categoryPrefs,
Gson gson) {
return new ApacheHttpClientMediaWikiApi(context, BuildConfig.WIKIMEDIA_API_HOST, defaultPreferences, categoryPrefs, gson);
}
@Provides @Provides
@Singleton @Singleton
public LocationServiceManager provideLocationServiceManager(Context context) { public LocationServiceManager provideLocationServiceManager(Context context) {
return new LocationServiceManager(context); return new LocationServiceManager(context);
} }
/**
* Gson objects are very heavy. The app should ideally be using just one instance of it instead of creating new instances everywhere.
* @return returns a singleton Gson instance
*/
@Provides
@Singleton
public Gson provideGson() {
return new Gson();
}
@Provides
@Singleton
public CacheController provideCacheController() {
return new CacheController();
}
@Provides @Provides
@Singleton @Singleton
public DBOpenHelper provideDBOpenHelper(Context context) { public DBOpenHelper provideDBOpenHelper(Context context) {
@ -165,4 +136,10 @@ public class CommonsApplicationModule {
public LruCache<String, String> provideLruCache() { public LruCache<String, String> provideLruCache() {
return new LruCache<>(1024); return new LruCache<>(1024);
} }
@Provides
@Singleton
public WikidataEditListener provideWikidataEditListener() {
return new WikidataEditListenerImpl();
}
} }

View file

@ -0,0 +1,59 @@
package fr.free.nrw.commons.di;
import android.content.Context;
import android.content.SharedPreferences;
import android.support.annotation.NonNull;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import javax.inject.Named;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.mwapi.ApacheHttpClientMediaWikiApi;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
@Module
@SuppressWarnings({"WeakerAccess", "unused"})
public class NetworkingModule {
public static final long OK_HTTP_CACHE_SIZE = 10 * 1024 * 1024;
@Provides
@Singleton
public OkHttpClient provideOkHttpClient() {
return new OkHttpClient.Builder().build();
}
@Provides
@Singleton
public MediaWikiApi provideMediaWikiApi(Context context,
@Named("default_preferences") SharedPreferences defaultPreferences,
@Named("category_prefs") SharedPreferences categoryPrefs,
Gson gson) {
return new ApacheHttpClientMediaWikiApi(context, BuildConfig.WIKIMEDIA_API_HOST, BuildConfig.WIKIDATA_API_HOST, defaultPreferences, categoryPrefs, gson);
}
@Provides
@Named("commons_mediawiki_url")
@NonNull
@SuppressWarnings("ConstantConditions")
public HttpUrl provideMwUrl() {
return HttpUrl.parse(BuildConfig.COMMONS_URL);
}
/**
* Gson objects are very heavy. The app should ideally be using just one instance of it instead of creating new instances everywhere.
* @return returns a singleton Gson instance
*/
@Provides
@Singleton
public Gson provideGson() {
return new GsonBuilder().create();
}
}

View file

@ -0,0 +1,36 @@
package fr.free.nrw.commons.glide;
import android.support.annotation.NonNull;
import com.bumptech.glide.load.Options;
import com.bumptech.glide.load.ResourceDecoder;
import com.bumptech.glide.load.engine.Resource;
import com.bumptech.glide.load.resource.SimpleResource;
import com.caverock.androidsvg.SVG;
import com.caverock.androidsvg.SVGParseException;
import java.io.IOException;
import java.io.InputStream;
/**
* Decodes an SVG internal representation from an {@link InputStream}.
*/
public class SvgDecoder implements ResourceDecoder<InputStream, SVG> {
@Override
public boolean handles(@NonNull InputStream source, @NonNull Options options) {
// TODO: Can we tell?
return true;
}
public Resource<SVG> decode(@NonNull InputStream source, int width, int height,
@NonNull Options options)
throws IOException {
try {
SVG svg = SVG.getFromInputStream(source);
return new SimpleResource<>(svg);
} catch (SVGParseException ex) {
throw new IOException("Cannot load SVG from stream", ex);
}
}
}

View file

@ -0,0 +1,28 @@
package fr.free.nrw.commons.glide;
import android.graphics.Picture;
import android.graphics.drawable.PictureDrawable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.bumptech.glide.load.Options;
import com.bumptech.glide.load.engine.Resource;
import com.bumptech.glide.load.resource.SimpleResource;
import com.bumptech.glide.load.resource.transcode.ResourceTranscoder;
import com.caverock.androidsvg.SVG;
/**
* Convert the {@link SVG}'s internal representation to an Android-compatible one
* ({@link Picture}).
*/
public class SvgDrawableTranscoder implements ResourceTranscoder<SVG, PictureDrawable> {
@Nullable
@Override
public Resource<PictureDrawable> transcode(@NonNull Resource<SVG> toTranscode,
@NonNull Options options) {
SVG svg = toTranscode.get();
Picture picture = svg.renderToPicture();
PictureDrawable drawable = new PictureDrawable(picture);
return new SimpleResource<>(drawable);
}
}

View file

@ -0,0 +1,51 @@
package fr.free.nrw.commons.glide;
import android.graphics.drawable.PictureDrawable;
import android.widget.ImageView;
import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.engine.GlideException;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.target.ImageViewTarget;
import com.bumptech.glide.request.target.Target;
/**
* Listener which updates the {@link ImageView} to be software rendered, because
* {@link com.caverock.androidsvg.SVG SVG}/{@link android.graphics.Picture Picture} can't render on
* a hardware backed {@link android.graphics.Canvas Canvas}.
*/
public class SvgSoftwareLayerSetter implements RequestListener<PictureDrawable> {
/**
* Sets the layer type to none if the load fails
* @param e
* @param model
* @param target
* @param isFirstResource
* @return
*/
@Override
public boolean onLoadFailed(GlideException e, Object model, Target<PictureDrawable> target,
boolean isFirstResource) {
ImageView view = ((ImageViewTarget<?>) target).getView();
view.setLayerType(ImageView.LAYER_TYPE_NONE, null);
return false;
}
/**
* Sets the layer type to software when the resource is ready
* @param resource
* @param model
* @param target
* @param dataSource
* @param isFirstResource
* @return
*/
@Override
public boolean onResourceReady(PictureDrawable resource, Object model,
Target<PictureDrawable> target, DataSource dataSource, boolean isFirstResource) {
ImageView view = ((ImageViewTarget<?>) target).getView();
view.setLayerType(ImageView.LAYER_TYPE_SOFTWARE, null);
return false;
}
}

View file

@ -1,6 +1,7 @@
package fr.free.nrw.commons.location; package fr.free.nrw.commons.location;
import android.Manifest; import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
@ -10,9 +11,10 @@ import android.location.LocationManager;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.ActivityCompat; import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
import android.util.Log;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import timber.log.Timber; import timber.log.Timber;
@ -29,6 +31,7 @@ public class LocationServiceManager implements LocationListener {
private Location lastLocation; private Location lastLocation;
private final List<LocationUpdateListener> locationListeners = new CopyOnWriteArrayList<>(); private final List<LocationUpdateListener> locationListeners = new CopyOnWriteArrayList<>();
private boolean isLocationManagerRegistered = false; private boolean isLocationManagerRegistered = false;
private Set<Activity> locationExplanationDisplayed = new HashSet<>();
/** /**
* Constructs a new instance of LocationServiceManager. * Constructs a new instance of LocationServiceManager.
@ -51,7 +54,6 @@ public class LocationServiceManager implements LocationListener {
/** /**
* Returns whether the location permission is granted. * Returns whether the location permission is granted.
*
* @return true if the location permission is granted * @return true if the location permission is granted
*/ */
public boolean isLocationPermissionGranted() { public boolean isLocationPermissionGranted() {
@ -73,10 +75,23 @@ public class LocationServiceManager implements LocationListener {
LOCATION_REQUEST); LOCATION_REQUEST);
} }
/**
* The permission explanation dialog box is now displayed just once for a particular activity. We are subscribing
* to updates from multiple providers so its important to show the dialog just once. Otherwise it will be displayed
* once for every provider, which in our case currently is 2.
* @param activity
* @return
*/
public boolean isPermissionExplanationRequired(Activity activity) { public boolean isPermissionExplanationRequired(Activity activity) {
return !activity.isFinishing() && if (activity.isFinishing()) {
ActivityCompat.shouldShowRequestPermissionRationale(activity, return false;
Manifest.permission.ACCESS_FINE_LOCATION); }
boolean showRequestPermissionRationale = ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.ACCESS_FINE_LOCATION);
if (showRequestPermissionRationale && !locationExplanationDisplayed.contains(activity)) {
locationExplanationDisplayed.add(activity);
return true;
}
return false;
} }
/** /**
@ -84,8 +99,9 @@ public class LocationServiceManager implements LocationListener {
* (e.g. when Location permission just granted) * (e.g. when Location permission just granted)
* @return last known LatLng * @return last known LatLng
*/ */
@SuppressLint("MissingPermission")
public LatLng getLKL() { public LatLng getLKL() {
if (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { if (isLocationPermissionGranted()) {
Location lastKL = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER); Location lastKL = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
if (lastKL == null) { if (lastKL == null) {
lastKL = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER); lastKL = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
@ -107,9 +123,10 @@ public class LocationServiceManager implements LocationListener {
* Registers a LocationManager to listen for current location. * Registers a LocationManager to listen for current location.
*/ */
public void registerLocationManager() { public void registerLocationManager() {
if (!isLocationManagerRegistered) if (!isLocationManagerRegistered) {
isLocationManagerRegistered = requestLocationUpdatesFromProvider(LocationManager.NETWORK_PROVIDER) isLocationManagerRegistered = requestLocationUpdatesFromProvider(LocationManager.NETWORK_PROVIDER)
&& requestLocationUpdatesFromProvider(LocationManager.GPS_PROVIDER); && requestLocationUpdatesFromProvider(LocationManager.GPS_PROVIDER);
}
} }
/** /**
@ -142,7 +159,7 @@ public class LocationServiceManager implements LocationListener {
* @return LOCATION_SIGNIFICANTLY_CHANGED if location changed significantly * @return LOCATION_SIGNIFICANTLY_CHANGED if location changed significantly
* LOCATION_SLIGHTLY_CHANGED if location changed slightly * LOCATION_SLIGHTLY_CHANGED if location changed slightly
*/ */
protected LocationChangeType isBetterLocation(Location location, Location currentBestLocation) { private LocationChangeType isBetterLocation(Location location, Location currentBestLocation) {
if (currentBestLocation == null) { if (currentBestLocation == null) {
// A new location is always better than no location // A new location is always better than no location
@ -267,6 +284,7 @@ public class LocationServiceManager implements LocationListener {
LOCATION_SIGNIFICANTLY_CHANGED, //Went out of borders of nearby markers LOCATION_SIGNIFICANTLY_CHANGED, //Went out of borders of nearby markers
LOCATION_SLIGHTLY_CHANGED, //User might be walking or driving LOCATION_SLIGHTLY_CHANGED, //User might be walking or driving
LOCATION_NOT_CHANGED, LOCATION_NOT_CHANGED,
PERMISSION_JUST_GRANTED PERMISSION_JUST_GRANTED,
MAP_UPDATED
} }
} }

View file

@ -56,16 +56,16 @@ import static android.widget.Toast.LENGTH_SHORT;
public class MediaDetailFragment extends CommonsDaggerSupportFragment { public class MediaDetailFragment extends CommonsDaggerSupportFragment {
private boolean editable; private boolean editable;
private boolean isFeaturedMedia; private boolean isCategoryImage;
private MediaDetailPagerFragment.MediaDetailProvider detailProvider; private MediaDetailPagerFragment.MediaDetailProvider detailProvider;
private int index; private int index;
public static MediaDetailFragment forMedia(int index, boolean editable, boolean isFeaturedMedia) { public static MediaDetailFragment forMedia(int index, boolean editable, boolean isCategoryImage) {
MediaDetailFragment mf = new MediaDetailFragment(); MediaDetailFragment mf = new MediaDetailFragment();
Bundle state = new Bundle(); Bundle state = new Bundle();
state.putBoolean("editable", editable); state.putBoolean("editable", editable);
state.putBoolean("isFeaturedMedia", isFeaturedMedia); state.putBoolean("isCategoryImage", isCategoryImage);
state.putInt("index", index); state.putInt("index", index);
state.putInt("listIndex", 0); state.putInt("listIndex", 0);
state.putInt("listTop", 0); state.putInt("listTop", 0);
@ -128,7 +128,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
outState.putInt("index", index); outState.putInt("index", index);
outState.putBoolean("editable", editable); outState.putBoolean("editable", editable);
outState.putBoolean("isFeaturedMedia", isFeaturedMedia); outState.putBoolean("isCategoryImage", isCategoryImage);
getScrollPosition(); getScrollPosition();
outState.putInt("listTop", initialListTop); outState.putInt("listTop", initialListTop);
@ -144,12 +144,12 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
if (savedInstanceState != null) { if (savedInstanceState != null) {
editable = savedInstanceState.getBoolean("editable"); editable = savedInstanceState.getBoolean("editable");
isFeaturedMedia = savedInstanceState.getBoolean("isFeaturedMedia"); isCategoryImage = savedInstanceState.getBoolean("isCategoryImage");
index = savedInstanceState.getInt("index"); index = savedInstanceState.getInt("index");
initialListTop = savedInstanceState.getInt("listTop"); initialListTop = savedInstanceState.getInt("listTop");
} else { } else {
editable = getArguments().getBoolean("editable"); editable = getArguments().getBoolean("editable");
isFeaturedMedia = getArguments().getBoolean("isFeaturedMedia"); isCategoryImage = getArguments().getBoolean("isCategoryImage");
index = getArguments().getInt("index"); index = getArguments().getInt("index");
initialListTop = 0; initialListTop = 0;
} }
@ -161,7 +161,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
ButterKnife.bind(this,view); ButterKnife.bind(this,view);
if (isFeaturedMedia){ if (isCategoryImage){
authorLayout.setVisibility(VISIBLE); authorLayout.setVisibility(VISIBLE);
} else { } else {
authorLayout.setVisibility(GONE); authorLayout.setVisibility(GONE);
@ -328,7 +328,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
if (!TextUtils.isEmpty(licenseLink(media))) { if (!TextUtils.isEmpty(licenseLink(media))) {
openWebBrowser(licenseLink(media)); openWebBrowser(licenseLink(media));
} else { } else {
if(isFeaturedMedia) { if(isCategoryImage) {
Timber.d("Unable to fetch license URL for %s", media.getLicense()); Timber.d("Unable to fetch license URL for %s", media.getLicense());
} else { } else {
Toast toast = Toast.makeText(getContext(), getString(R.string.null_url), Toast.LENGTH_SHORT); Toast toast = Toast.makeText(getContext(), getString(R.string.null_url), Toast.LENGTH_SHORT);
@ -503,8 +503,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
if (media.getRequestedDeletion()){ if (media.getRequestedDeletion()){
delete.setVisibility(GONE); delete.setVisibility(GONE);
nominatedForDeletion.setVisibility(VISIBLE); nominatedForDeletion.setVisibility(VISIBLE);
} } else if (!isCategoryImage) {
else{
delete.setVisibility(VISIBLE); delete.setVisibility(VISIBLE);
nominatedForDeletion.setVisibility(GONE); nominatedForDeletion.setVisibility(GONE);
} }

View file

@ -38,6 +38,8 @@ import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.contributions.ContributionsActivity; import fr.free.nrw.commons.contributions.ContributionsActivity;
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment; import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
import fr.free.nrw.commons.mwapi.MediaWikiApi; import fr.free.nrw.commons.mwapi.MediaWikiApi;
import fr.free.nrw.commons.utils.ImageUtils;
import timber.log.Timber;
import static android.Manifest.permission.READ_EXTERNAL_STORAGE; import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
import static android.content.Context.DOWNLOAD_SERVICE; import static android.content.Context.DOWNLOAD_SERVICE;
@ -140,6 +142,10 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
// Download // Download
downloadMedia(m); downloadMedia(m);
return true; return true;
case R.id.menu_set_as_wallpaper:
// Set wallpaper
setWallpaper(m);
return true;
case R.id.menu_retry_current_image: case R.id.menu_retry_current_image:
// Retry // Retry
((ContributionsActivity) getActivity()).retryUpload(pager.getCurrentItem()); ((ContributionsActivity) getActivity()).retryUpload(pager.getCurrentItem());
@ -155,6 +161,19 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
} }
} }
/**
* Set the media as the device's wallpaper if the imageUrl is not null
* Fails silently if setting the wallpaper fails
* @param media
*/
private void setWallpaper(Media media) {
if(media.getImageUrl() == null || media.getImageUrl().isEmpty()) {
Timber.d("Media URL not present");
return;
}
ImageUtils.setWallpaperFromImageUrl(getActivity(), Uri.parse(media.getImageUrl()));
}
/** /**
* Start the media file downloading to the local SD card/storage. * Start the media file downloading to the local SD card/storage.
* The file can then be opened in Gallery or other apps. * The file can then be opened in Gallery or other apps.

View file

@ -25,6 +25,8 @@ import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.util.EntityUtils; import org.apache.http.util.EntityUtils;
import org.mediawiki.api.ApiResult; import org.mediawiki.api.ApiResult;
import org.mediawiki.api.MWApi; import org.mediawiki.api.MWApi;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList; import org.w3c.dom.NodeList;
import java.io.IOException; import java.io.IOException;
@ -62,6 +64,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
private static final String THUMB_SIZE = "640"; private static final String THUMB_SIZE = "640";
private AbstractHttpClient httpClient; private AbstractHttpClient httpClient;
private MWApi api; private MWApi api;
private MWApi wikidataApi;
private Context context; private Context context;
private SharedPreferences defaultPreferences; private SharedPreferences defaultPreferences;
private SharedPreferences categoryPreferences; private SharedPreferences categoryPreferences;
@ -69,6 +72,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
public ApacheHttpClientMediaWikiApi(Context context, public ApacheHttpClientMediaWikiApi(Context context,
String apiURL, String apiURL,
String wikidatApiURL,
SharedPreferences defaultPreferences, SharedPreferences defaultPreferences,
SharedPreferences categoryPreferences, SharedPreferences categoryPreferences,
Gson gson) { Gson gson) {
@ -82,6 +86,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
params.setParameter(CoreProtocolPNames.USER_AGENT, getUserAgent()); params.setParameter(CoreProtocolPNames.USER_AGENT, getUserAgent());
httpClient = new DefaultHttpClient(cm, params); httpClient = new DefaultHttpClient(cm, params);
api = new MWApi(apiURL, httpClient); api = new MWApi(apiURL, httpClient);
wikidataApi = new MWApi(wikidatApiURL, httpClient);
this.defaultPreferences = defaultPreferences; this.defaultPreferences = defaultPreferences;
this.categoryPreferences = categoryPreferences; this.categoryPreferences = categoryPreferences;
this.gson = gson; this.gson = gson;
@ -206,6 +211,15 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
return api.getEditToken(); return api.getEditToken();
} }
@Override
public String getCentralAuthToken() throws IOException {
String centralAuthToken = api.action("centralauthtoken")
.get()
.getString("/api/centralauthtoken/@centralauthtoken");
Timber.d("MediaWiki Central auth token is %s", centralAuthToken);
return centralAuthToken;
}
@Override @Override
public boolean fileExistsWithName(String fileName) throws IOException { public boolean fileExistsWithName(String fileName) throws IOException {
return api.action("query") return api.action("query")
@ -351,6 +365,98 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
}).flatMapObservable(Observable::fromIterable); }).flatMapObservable(Observable::fromIterable);
} }
/**
* Get the edit token for making wiki data edits
* https://www.mediawiki.org/wiki/API:Tokens
* @return
* @throws IOException
*/
private String getWikidataEditToken() throws IOException {
return wikidataApi.getEditToken();
}
@Override
public String getWikidataCsrfToken() throws IOException {
String wikidataCsrfToken = wikidataApi.action("query")
.param("action", "query")
.param("centralauthtoken", getCentralAuthToken())
.param("meta", "tokens")
.post()
.getString("/api/query/tokens/@csrftoken");
Timber.d("Wikidata csrf token is %s", wikidataCsrfToken);
return wikidataCsrfToken;
}
/**
* Creates a new claim using the wikidata API
* https://www.mediawiki.org/wiki/Wikibase/API
* @param entityId the wikidata entity to be edited
* @param property the property to be edited, for eg P18 for images
* @param snaktype the type of value stored for that property
* @param value the actual value to be stored for the property, for eg filename in case of P18
* @return returns revisionId if the claim is successfully created else returns null
* @throws IOException
*/
@Nullable
@Override
public String wikidatCreateClaim(String entityId, String property, String snaktype, String value) throws IOException {
Timber.d("Filename is %s", value);
ApiResult result = wikidataApi.action("wbcreateclaim")
.param("entity", entityId)
.param("centralauthtoken", getCentralAuthToken())
.param("token", getWikidataCsrfToken())
.param("snaktype", snaktype)
.param("property", property)
.param("value", value)
.post();
if (result == null || result.getNode("api") == null) {
return null;
}
Node node = result.getNode("api").getDocument();
Element element = (Element) node;
if (element != null && element.getAttribute("success").equals("1")) {
return result.getString("api/pageinfo/@lastrevid");
} else {
Timber.e(result.getString("api/error/@code") + " " + result.getString("api/error/@info"));
}
return null;
}
/**
* Adds the wikimedia-commons-app tag to the edits made on wikidata
* @param revisionId
* @return
* @throws IOException
*/
@Nullable
@Override
public boolean addWikidataEditTag(String revisionId) throws IOException {
ApiResult result = wikidataApi.action("tag")
.param("revid", revisionId)
.param("centralauthtoken", getCentralAuthToken())
.param("token", getWikidataCsrfToken())
.param("add", "wikimedia-commons-app")
.param("reason", "Add tag for edits made using Android Commons app")
.post();
if (result == null || result.getNode("api") == null) {
return false;
}
Node node = result.getNode("api").getDocument();
Element element = (Element) node;
if (element != null && element.getAttribute("status").equals("success")) {
return true;
} else {
Timber.e(result.getString("api/error/@code") + " " + result.getString("api/error/@info"));
}
return false;
}
@Override @Override
@NonNull @NonNull
public Observable<String> searchTitles(String title, int searchCatsLimit) { public Observable<String> searchTitles(String title, int searchCatsLimit) {
@ -444,8 +550,8 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
.param("notprop", "list") .param("notprop", "list")
.param("format", "xml") .param("format", "xml")
.param("meta", "notifications") .param("meta", "notifications")
// .param("meta", "notifications")
.param("notformat", "model") .param("notformat", "model")
.param("notwikis", "wikidatawiki|commonswiki|enwiki")
.get() .get()
.getNode("/api/query/notifications/list"); .getNode("/api/query/notifications/list");
} catch (IOException e) { } catch (IOException e) {
@ -480,6 +586,8 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
.param("format", "xml") .param("format", "xml")
.param("gcmtype", "file") .param("gcmtype", "file")
.param("gcmtitle", categoryName) .param("gcmtitle", categoryName)
.param("gcmsort", "timestamp")//property to sort by;timestamp
.param("gcmdir", "desc")//in which direction to sort;descending
.param("prop", "imageinfo") .param("prop", "imageinfo")
.param("gcmlimit", "10") .param("gcmlimit", "10")
.param("iiprop", "url|extmetadata"); .param("iiprop", "url|extmetadata");
@ -586,6 +694,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
String resultStatus = result.getString("/api/upload/@result"); String resultStatus = result.getString("/api/upload/@result");
if (!resultStatus.equals("Success")) { if (!resultStatus.equals("Success")) {
String errorCode = result.getString("/api/error/@code"); String errorCode = result.getString("/api/error/@code");
Timber.e(errorCode);
return new UploadResult(resultStatus, errorCode); return new UploadResult(resultStatus, errorCode);
} else { } else {
Date dateUploaded = parseMWDate(result.getString("/api/upload/imageinfo/@timestamp")); Date dateUploaded = parseMWDate(result.getString("/api/upload/imageinfo/@timestamp"));

View file

@ -0,0 +1,101 @@
package fr.free.nrw.commons.mwapi;
import com.google.gson.Gson;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import javax.inject.Inject;
import javax.inject.Named;
import fr.free.nrw.commons.mwapi.model.ApiResponse;
import fr.free.nrw.commons.mwapi.model.Page;
import fr.free.nrw.commons.mwapi.model.PageCategory;
import io.reactivex.Single;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import timber.log.Timber;
/**
* Uses the OkHttp library to implement calls to the Commons MediaWiki API to match GPS coordinates
* with nearby Commons categories. Parses the results using GSON to obtain a list of relevant
* categories. Note: that caller is responsible for executing the request() method on a background
* thread.
*/
public class CategoryApi {
private final OkHttpClient okHttpClient;
private final HttpUrl mwUrl;
private final Gson gson;
@Inject
public CategoryApi(OkHttpClient okHttpClient, Gson gson,
@Named("commons_mediawiki_url") HttpUrl mwUrl) {
this.okHttpClient = okHttpClient;
this.mwUrl = mwUrl;
this.gson = gson;
}
public Single<List<String>> request(String coords) {
return Single.fromCallable(() -> {
HttpUrl apiUrl = buildUrl(coords);
Timber.d("URL: %s", apiUrl.toString());
Request request = new Request.Builder().get().url(apiUrl).build();
Response response = okHttpClient.newCall(request).execute();
ResponseBody body = response.body();
if (body == null) {
return Collections.emptyList();
}
ApiResponse apiResponse = gson.fromJson(body.charStream(), ApiResponse.class);
Set<String> categories = new LinkedHashSet<>();
if (apiResponse != null && apiResponse.hasPages()) {
for (Page page : apiResponse.query.pages) {
for (PageCategory category : page.getCategories()) {
categories.add(category.withoutPrefix());
}
}
}
return new ArrayList<>(categories);
});
}
/**
* Builds URL with image coords for MediaWiki API calls
* Example URL: https://commons.wikimedia.org/w/api.php?action=query&prop=categories|coordinates|pageprops&format=json&clshow=!hidden&coprop=type|name|dim|country|region|globe&codistancefrompoint=38.11386944444445|13.356263888888888&generator=geosearch&redirects=&ggscoord=38.11386944444445|1.356263888888888&ggsradius=100&ggslimit=10&ggsnamespace=6&ggsprop=type|name|dim|country|region|globe&ggsprimary=all&formatversion=2
*
* @param coords Coordinates to build query with
* @return URL for API query
*/
private HttpUrl buildUrl(String coords) {
return mwUrl.newBuilder()
.addPathSegment("w")
.addPathSegment("api.php")
.addQueryParameter("action", "query")
.addQueryParameter("prop", "categories|coordinates|pageprops")
.addQueryParameter("format", "json")
.addQueryParameter("clshow", "!hidden")
.addQueryParameter("coprop", "type|name|dim|country|region|globe")
.addQueryParameter("codistancefrompoint", coords)
.addQueryParameter("generator", "geosearch")
.addQueryParameter("ggscoord", coords)
.addQueryParameter("ggsradius", "10000")
.addQueryParameter("ggslimit", "10")
.addQueryParameter("ggsnamespace", "6")
.addQueryParameter("ggsprop", "type|name|dim|country|region|globe")
.addQueryParameter("ggsprimary", "all")
.addQueryParameter("formatversion", "2")
.build();
}
}

View file

@ -27,6 +27,10 @@ public interface MediaWikiApi {
String getEditToken() throws IOException; String getEditToken() throws IOException;
String getWikidataCsrfToken() throws IOException;
String getCentralAuthToken() throws IOException;
boolean fileExistsWithName(String fileName) throws IOException; boolean fileExistsWithName(String fileName) throws IOException;
boolean pageExists(String pageName) throws IOException; boolean pageExists(String pageName) throws IOException;
@ -49,6 +53,12 @@ public interface MediaWikiApi {
@Nullable @Nullable
String appendEdit(String editToken, String processedPageContent, String filename, String summary) throws IOException; String appendEdit(String editToken, String processedPageContent, String filename, String summary) throws IOException;
@Nullable
String wikidatCreateClaim(String entityId, String property, String snaktype, String value) throws IOException;
@Nullable
boolean addWikidataEditTag(String revisionId) throws IOException;
@NonNull @NonNull
MediaResult fetchMediaByFilename(String filename) throws IOException; MediaResult fetchMediaByFilename(String filename) throws IOException;

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,5 @@
package fr.free.nrw.commons.nearby; package fr.free.nrw.commons.nearby;
import android.content.SharedPreferences;
import android.os.Build; import android.os.Build;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;

View file

@ -36,11 +36,13 @@ import butterknife.ButterKnife;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.location.LatLng; import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.location.LocationServiceManager; import fr.free.nrw.commons.location.LocationServiceManager;
import fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType;
import fr.free.nrw.commons.location.LocationUpdateListener; import fr.free.nrw.commons.location.LocationUpdateListener;
import fr.free.nrw.commons.theme.NavigationBaseActivity; import fr.free.nrw.commons.theme.NavigationBaseActivity;
import fr.free.nrw.commons.utils.NetworkUtils; import fr.free.nrw.commons.utils.NetworkUtils;
import fr.free.nrw.commons.utils.UriSerializer; import fr.free.nrw.commons.utils.UriSerializer;
import fr.free.nrw.commons.utils.ViewUtil; import fr.free.nrw.commons.utils.ViewUtil;
import fr.free.nrw.commons.wikidata.WikidataEditListener;
import io.reactivex.Observable; import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable; import io.reactivex.disposables.Disposable;
@ -49,8 +51,12 @@ import timber.log.Timber;
import uk.co.deanwild.materialshowcaseview.IShowcaseListener; import uk.co.deanwild.materialshowcaseview.IShowcaseListener;
import uk.co.deanwild.materialshowcaseview.MaterialShowcaseView; import uk.co.deanwild.materialshowcaseview.MaterialShowcaseView;
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.*;
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.MAP_UPDATED;
public class NearbyActivity extends NavigationBaseActivity implements LocationUpdateListener {
public class NearbyActivity extends NavigationBaseActivity implements LocationUpdateListener,
WikidataEditListener.WikidataP18EditListener {
private static final int LOCATION_REQUEST = 1; private static final int LOCATION_REQUEST = 1;
@ -70,6 +76,8 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
LocationServiceManager locationManager; LocationServiceManager locationManager;
@Inject @Inject
NearbyController nearbyController; NearbyController nearbyController;
@Inject WikidataEditListener wikidataEditListener;
@Inject @Inject
@Named("application_preferences") SharedPreferences applicationPrefs; @Named("application_preferences") SharedPreferences applicationPrefs;
private LatLng curLatLng; private LatLng curLatLng;
@ -104,6 +112,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
initBottomSheetBehaviour(); initBottomSheetBehaviour();
initDrawer(); initDrawer();
wikidataEditListener.setAuthenticationStateListener(this);
} }
private void resumeFragment() { private void resumeFragment() {
@ -213,7 +222,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
//Still need to check if GPS is enabled //Still need to check if GPS is enabled
checkGps(); checkGps();
lastKnownLocation = locationManager.getLKL(); lastKnownLocation = locationManager.getLKL();
refreshView(LocationServiceManager.LocationChangeType.PERMISSION_JUST_GRANTED); refreshView(PERMISSION_JUST_GRANTED);
} else { } else {
//If permission not granted, go to page that says Nearby Places cannot be displayed //If permission not granted, go to page that says Nearby Places cannot be displayed
hideProgressBar(); hideProgressBar();
@ -273,7 +282,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
private void checkLocationPermission() { private void checkLocationPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (locationManager.isLocationPermissionGranted()) { if (locationManager.isLocationPermissionGranted()) {
refreshView(LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED); refreshView(LOCATION_SIGNIFICANTLY_CHANGED);
} else { } else {
// Should we show an explanation? // Should we show an explanation?
if (locationManager.isPermissionExplanationRequired(this)) { if (locationManager.isPermissionExplanationRequired(this)) {
@ -299,7 +308,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
} }
} }
} else { } else {
refreshView(LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED); refreshView(LOCATION_SIGNIFICANTLY_CHANGED);
} }
} }
@ -308,7 +317,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
super.onActivityResult(requestCode, resultCode, data); super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 1) { if (requestCode == 1) {
Timber.d("User is back from Settings page"); Timber.d("User is back from Settings page");
refreshView(LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED); refreshView(LOCATION_SIGNIFICANTLY_CHANGED);
} }
} }
@ -316,7 +325,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
protected void onStart() { protected void onStart() {
super.onStart(); super.onStart();
locationManager.addLocationListener(this); locationManager.addLocationListener(this);
locationManager.registerLocationManager(); registerLocationUpdates();
} }
@Override @Override
@ -367,8 +376,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
if (NetworkUtils.isInternetConnectionEstablished(NearbyActivity.this)) { if (NetworkUtils.isInternetConnectionEstablished(NearbyActivity.this)) {
refreshView(LocationServiceManager refreshView(LOCATION_SIGNIFICANTLY_CHANGED);
.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED);
} else { } else {
ViewUtil.showLongToast(NearbyActivity.this, getString(R.string.no_internet)); ViewUtil.showLongToast(NearbyActivity.this, getString(R.string.no_internet));
} }
@ -384,7 +392,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
* *
* @param locationChangeType defines if location shanged significantly or slightly * @param locationChangeType defines if location shanged significantly or slightly
*/ */
private void refreshView(LocationServiceManager.LocationChangeType locationChangeType) { private void refreshView(LocationChangeType locationChangeType) {
if (lockNearbyView) { if (lockNearbyView) {
return; return;
} }
@ -394,15 +402,16 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
return; return;
} }
locationManager.registerLocationManager(); registerLocationUpdates();
LatLng lastLocation = locationManager.getLastLocation(); LatLng lastLocation = locationManager.getLastLocation();
if (curLatLng != null && curLatLng.equals(lastLocation)) { //refresh view only if location has changed if (curLatLng != null && curLatLng.equals(lastLocation)
&& !locationChangeType.equals(MAP_UPDATED)) { //refresh view only if location has changed
return; return;
} }
curLatLng = lastLocation; curLatLng = lastLocation;
if (locationChangeType.equals(LocationServiceManager.LocationChangeType.PERMISSION_JUST_GRANTED)) { if (locationChangeType.equals(PERMISSION_JUST_GRANTED)) {
curLatLng = lastKnownLocation; curLatLng = lastKnownLocation;
} }
@ -411,8 +420,9 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
return; return;
} }
if (locationChangeType.equals(LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED) if (locationChangeType.equals(LOCATION_SIGNIFICANTLY_CHANGED)
|| locationChangeType.equals(LocationServiceManager.LocationChangeType.PERMISSION_JUST_GRANTED)) { || locationChangeType.equals(PERMISSION_JUST_GRANTED)
|| locationChangeType.equals(MAP_UPDATED)) {
progressBar.setVisibility(View.VISIBLE); progressBar.setVisibility(View.VISIBLE);
//TODO: This hack inserts curLatLng before populatePlaces is called (see #1440). Ideally a proper fix should be found //TODO: This hack inserts curLatLng before populatePlaces is called (see #1440). Ideally a proper fix should be found
@ -427,8 +437,14 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
.loadAttractionsFromLocation(curLatLng)) .loadAttractionsFromLocation(curLatLng))
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(this::populatePlaces); .subscribe(this::populatePlaces,
} else if (locationChangeType.equals(LocationServiceManager.LocationChangeType.LOCATION_SLIGHTLY_CHANGED)) { throwable -> {
Timber.d(throwable);
showErrorMessage(getString(R.string.error_fetching_nearby_places));
progressBar.setVisibility(View.GONE);
});
} else if (locationChangeType
.equals(LOCATION_SLIGHTLY_CHANGED)) {
Gson gson = new GsonBuilder() Gson gson = new GsonBuilder()
.registerTypeAdapter(Uri.class, new UriSerializer()) .registerTypeAdapter(Uri.class, new UriSerializer())
.create(); .create();
@ -438,6 +454,39 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
} }
} }
/**
* This method first checks if the location permissions has been granted and then register the location manager for updates.
*/
private void registerLocationUpdates() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (locationManager.isLocationPermissionGranted()) {
locationManager.registerLocationManager();
} else {
// Should we show an explanation?
if (locationManager.isPermissionExplanationRequired(this)) {
new AlertDialog.Builder(this)
.setMessage(getString(R.string.location_permission_rationale_nearby))
.setPositiveButton("OK", (dialog, which) -> {
requestLocationPermissions();
dialog.dismiss();
})
.setNegativeButton("Cancel", (dialog, id) -> {
showLocationPermissionDeniedErrorDialog();
dialog.cancel();
})
.create()
.show();
} else {
// No explanation needed, we can request the permission.
requestLocationPermissions();
}
}
} else {
locationManager.registerLocationManager();
}
}
private void populatePlaces(NearbyController.NearbyPlacesInfo nearbyPlacesInfo) { private void populatePlaces(NearbyController.NearbyPlacesInfo nearbyPlacesInfo) {
List<Place> placeList = nearbyPlacesInfo.placeList; List<Place> placeList = nearbyPlacesInfo.placeList;
LatLng[] boundaryCoordinates = nearbyPlacesInfo.boundaryCoordinates; LatLng[] boundaryCoordinates = nearbyPlacesInfo.boundaryCoordinates;
@ -451,7 +500,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
if (placeList.size() == 0) { if (placeList.size() == 0) {
ViewUtil.showSnackbar(findViewById(R.id.container), R.string.no_nearby); ViewUtil.showSnackbar(findViewById(R.id.container), R.string.no_nearby);
} }
bundle.putString("PlaceList", gsonPlaceList); bundle.putString("PlaceList", gsonPlaceList);
//bundle.putString("CurLatLng", gsonCurLatLng); //bundle.putString("CurLatLng", gsonCurLatLng);
bundle.putString("BoundaryCoord", gsonBoundaryCoordinates); bundle.putString("BoundaryCoord", gsonBoundaryCoordinates);
@ -518,7 +567,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
locationManager.removeLocationListener(this); locationManager.removeLocationListener(this);
} else { } else {
lockNearbyView = false; lockNearbyView = false;
locationManager.registerLocationManager(); registerLocationUpdates();
locationManager.addLocationListener(this); locationManager.addLocationListener(this);
} }
} }
@ -580,7 +629,12 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
.loadAttractionsFromLocation(curLatLng)) .loadAttractionsFromLocation(curLatLng))
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(this::populatePlaces); .subscribe(this::populatePlaces,
throwable -> {
Timber.d(throwable);
showErrorMessage(getString(R.string.error_fetching_nearby_places));
progressBar.setVisibility(View.GONE);
});
nearbyMapFragment.setBundleForUpdtes(bundle); nearbyMapFragment.setBundleForUpdtes(bundle);
nearbyMapFragment.updateMapSignificantly(); nearbyMapFragment.updateMapSignificantly();
updateListFragment(); updateListFragment();
@ -635,15 +689,24 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
@Override @Override
public void onLocationChangedSignificantly(LatLng latLng) { public void onLocationChangedSignificantly(LatLng latLng) {
refreshView(LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED); refreshView(LOCATION_SIGNIFICANTLY_CHANGED);
} }
@Override @Override
public void onLocationChangedSlightly(LatLng latLng) { public void onLocationChangedSlightly(LatLng latLng) {
refreshView(LocationServiceManager.LocationChangeType.LOCATION_SLIGHTLY_CHANGED); refreshView(LOCATION_SLIGHTLY_CHANGED);
} }
public void prepareViewsForSheetPosition(int bottomSheetState) { public void prepareViewsForSheetPosition(int bottomSheetState) {
// TODO // TODO
} }
private void showErrorMessage(String message) {
ViewUtil.showLongToast(NearbyActivity.this, message);
}
@Override
public void onWikidataEditSuccessful() {
refreshView(MAP_UPDATED);
}
} }

View file

@ -7,6 +7,7 @@ import android.support.graphics.drawable.VectorDrawableCompat;
import com.mapbox.mapboxsdk.annotations.IconFactory; import com.mapbox.mapboxsdk.annotations.IconFactory;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
@ -44,7 +45,7 @@ public class NearbyController {
* @return NearbyPlacesInfo a variable holds Place list without distance information * @return NearbyPlacesInfo a variable holds Place list without distance information
* and boundary coordinates of current Place List * and boundary coordinates of current Place List
*/ */
public NearbyPlacesInfo loadAttractionsFromLocation(LatLng curLatLng) { public NearbyPlacesInfo loadAttractionsFromLocation(LatLng curLatLng) throws IOException {
Timber.d("Loading attractions near %s", curLatLng); Timber.d("Loading attractions near %s", curLatLng);
NearbyPlacesInfo nearbyPlacesInfo = new NearbyPlacesInfo(); NearbyPlacesInfo nearbyPlacesInfo = new NearbyPlacesInfo();

View file

@ -2,6 +2,7 @@ package fr.free.nrw.commons.nearby;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
@ -21,6 +22,9 @@ import java.lang.reflect.Type;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
import dagger.android.support.AndroidSupportInjection; import dagger.android.support.AndroidSupportInjection;
import dagger.android.support.DaggerFragment; import dagger.android.support.DaggerFragment;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
@ -47,6 +51,11 @@ public class NearbyListFragment extends DaggerFragment {
private RecyclerView recyclerView; private RecyclerView recyclerView;
private ContributionController controller; private ContributionController controller;
@Inject
@Named("direct_nearby_upload_prefs")
SharedPreferences directPrefs;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@ -137,7 +146,7 @@ public class NearbyListFragment extends DaggerFragment {
if (resultCode == RESULT_OK) { if (resultCode == RESULT_OK) {
Timber.d("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s", Timber.d("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
requestCode, resultCode, data); requestCode, resultCode, data);
controller.handleImagePicked(requestCode, data, true); controller.handleImagePicked(requestCode, data, true, directPrefs.getString("WikiDataEntityId", null));
} else { } else {
Timber.e("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s", Timber.e("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
requestCode, resultCode, data); requestCode, resultCode, data);

View file

@ -731,6 +731,7 @@ public class NearbyMapFragment extends DaggerFragment {
editor.putString("Title", place.getName()); editor.putString("Title", place.getName());
editor.putString("Desc", place.getLongDescription()); editor.putString("Desc", place.getLongDescription());
editor.putString("Category", place.getCategory()); editor.putString("Category", place.getCategory());
editor.putString("WikiDataEntityId", place.getWikiDataEntityId());
editor.apply(); editor.apply();
} }
@ -766,7 +767,7 @@ public class NearbyMapFragment extends DaggerFragment {
if (resultCode == RESULT_OK) { if (resultCode == RESULT_OK) {
Timber.d("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s", Timber.d("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
requestCode, resultCode, data); requestCode, resultCode, data);
controller.handleImagePicked(requestCode, data, true); controller.handleImagePicked(requestCode, data, true, directPrefs.getString("WikiDataEntityId", null));
} else { } else {
Timber.e("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s", Timber.e("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
requestCode, resultCode, data); requestCode, resultCode, data);

View file

@ -17,7 +17,7 @@ import java.util.regex.Pattern;
import fr.free.nrw.commons.Utils; import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.location.LatLng; import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.utils.FileUtils; import fr.free.nrw.commons.upload.FileUtils;
import timber.log.Timber; import timber.log.Timber;
public class NearbyPlaces { public class NearbyPlaces {
@ -40,10 +40,9 @@ public class NearbyPlaces {
} }
} }
List<Place> getFromWikidataQuery(LatLng curLatLng, String lang) { List<Place> getFromWikidataQuery(LatLng curLatLng, String lang) throws IOException {
List<Place> places = Collections.emptyList(); List<Place> places = Collections.emptyList();
try {
// increase the radius gradually to find a satisfactory number of nearby places // increase the radius gradually to find a satisfactory number of nearby places
while (radius <= MAX_RADIUS) { while (radius <= MAX_RADIUS) {
places = getFromWikidataQuery(curLatLng, lang, radius); places = getFromWikidataQuery(curLatLng, lang, radius);
@ -54,13 +53,6 @@ public class NearbyPlaces {
radius *= RADIUS_MULTIPLIER; radius *= RADIUS_MULTIPLIER;
} }
} }
} catch (IOException e) {
Timber.d(e.toString());
// errors tend to be caused by too many results (and time out)
// try a small radius next time
Timber.d("back to initial radius: %f", radius);
radius = INITIAL_RADIUS;
}
// make sure we will be able to send at least one request next time // make sure we will be able to send at least one request next time
if (radius > MAX_RADIUS) { if (radius > MAX_RADIUS) {
radius = MAX_RADIUS; radius = MAX_RADIUS;

View file

@ -3,6 +3,7 @@ package fr.free.nrw.commons.nearby;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.DrawableRes; import android.support.annotation.DrawableRes;
import android.support.annotation.Nullable;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -50,6 +51,20 @@ public class Place {
this.distance = distance; this.distance = distance;
} }
/**
* Extracts the entity id from the wikidata link
* @return returns the entity id if wikidata link exists
*/
@Nullable
public String getWikiDataEntityId() {
if (!hasWikidataLink()) {
return null;
}
String wikiDataLink = siteLinks.getWikidataLink().toString();
return wikiDataLink.replace("http://www.wikidata.org/entity/", "");
}
public boolean hasWikipediaLink() { public boolean hasWikipediaLink() {
return !(siteLinks == null || Uri.EMPTY.equals(siteLinks.getWikipediaLink())); return !(siteLinks == null || Uri.EMPTY.equals(siteLinks.getWikipediaLink()));
} }

View file

@ -25,7 +25,6 @@ import javax.inject.Named;
import butterknife.BindView; import butterknife.BindView;
import butterknife.ButterKnife; import butterknife.ButterKnife;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.Utils; import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.contributions.ContributionController; import fr.free.nrw.commons.contributions.ContributionController;

View file

@ -16,7 +16,6 @@ import android.widget.RelativeLayout;
import com.pedrogomez.renderers.RVRendererAdapter; import com.pedrogomez.renderers.RVRendererAdapter;
import java.lang.ref.WeakReference;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -26,6 +25,7 @@ import butterknife.BindView;
import butterknife.ButterKnife; import butterknife.ButterKnife;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.Utils; import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
import fr.free.nrw.commons.theme.NavigationBaseActivity; import fr.free.nrw.commons.theme.NavigationBaseActivity;
import fr.free.nrw.commons.utils.NetworkUtils; import fr.free.nrw.commons.utils.NetworkUtils;
import fr.free.nrw.commons.utils.ViewUtil; import fr.free.nrw.commons.utils.ViewUtil;
@ -46,6 +46,8 @@ public class NotificationActivity extends NavigationBaseActivity {
@BindView(R.id.container) RelativeLayout relativeLayout; @BindView(R.id.container) RelativeLayout relativeLayout;
@Inject NotificationController controller; @Inject NotificationController controller;
@Inject
MediaWikiApi mediaWikiApi;
private static final String TAG_NOTIFICATION_WORKER_FRAGMENT = "NotificationWorkerFragment"; private static final String TAG_NOTIFICATION_WORKER_FRAGMENT = "NotificationWorkerFragment";
private NotificationWorkerFragment mNotificationWorkerFragment; private NotificationWorkerFragment mNotificationWorkerFragment;
@ -81,7 +83,6 @@ public class NotificationActivity extends NavigationBaseActivity {
} }
} }
@SuppressLint("CheckResult") @SuppressLint("CheckResult")
private void addNotifications() { private void addNotifications() {
Timber.d("Add notifications"); Timber.d("Add notifications");

View file

@ -1,6 +1,7 @@
package fr.free.nrw.commons.notification; package fr.free.nrw.commons.notification;
import android.util.Log; import android.graphics.drawable.PictureDrawable;
import android.text.Html;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -8,17 +9,23 @@ import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import com.borjabravo.readmoretextview.ReadMoreTextView; import com.borjabravo.readmoretextview.ReadMoreTextView;
import com.bumptech.glide.RequestBuilder;
import com.pedrogomez.renderers.Renderer; import com.pedrogomez.renderers.Renderer;
import butterknife.BindView; import butterknife.BindView;
import butterknife.ButterKnife; import butterknife.ButterKnife;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.glide.SvgSoftwareLayerSetter;
import static com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade;
/** /**
* Created by root on 19.12.2017. * Created by root on 19.12.2017.
*/ */
public class NotificationRenderer extends Renderer<Notification> { public class NotificationRenderer extends Renderer<Notification> {
private RequestBuilder<PictureDrawable> requestBuilder;
@BindView(R.id.title) ReadMoreTextView title; @BindView(R.id.title) ReadMoreTextView title;
@BindView(R.id.time) TextView time; @BindView(R.id.time) TextView time;
@BindView(R.id.icon) ImageView icon; @BindView(R.id.icon) ImageView icon;
@ -41,23 +48,32 @@ public class NotificationRenderer extends Renderer<Notification> {
protected View inflate(LayoutInflater layoutInflater, ViewGroup viewGroup) { protected View inflate(LayoutInflater layoutInflater, ViewGroup viewGroup) {
View inflatedView = layoutInflater.inflate(R.layout.item_notification, viewGroup, false); View inflatedView = layoutInflater.inflate(R.layout.item_notification, viewGroup, false);
ButterKnife.bind(this, inflatedView); ButterKnife.bind(this, inflatedView);
requestBuilder = GlideApp.with(inflatedView.getContext())
.as(PictureDrawable.class)
.error(R.drawable.round_icon_unknown)
.transition(withCrossFade())
.listener(new SvgSoftwareLayerSetter());
return inflatedView; return inflatedView;
} }
@Override @Override
public void render() { public void render() {
Notification notification = getContent(); Notification notification = getContent();
String str = notification.notificationText.trim(); setTitle(notification.notificationText);
str = str.concat(" ");
title.setText(str);
time.setText(notification.date); time.setText(notification.date);
switch (notification.notificationType) { requestBuilder.load(notification.iconUrl).into(icon);
case THANK_YOU_EDIT: }
icon.setImageResource(R.drawable.ic_edit_black_24dp);
break; /**
default: * Cleans up the notification text and sets it as the title
icon.setImageResource(R.drawable.round_icon_unknown); * Clean up is required to fix escaped HTML string and extra white spaces at the beginning of the notification
} * @param notificationText
*/
private void setTitle(String notificationText) {
notificationText = notificationText.trim().replaceAll("(^\\h*)|(\\h*$)", "");
notificationText = Html.fromHtml(notificationText).toString();
notificationText = notificationText.concat(" ");
title.setText(notificationText);
} }
public interface NotificationClicked{ public interface NotificationClicked{

View file

@ -16,12 +16,13 @@ import javax.annotation.Nullable;
import fr.free.nrw.commons.BuildConfig; import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import static fr.free.nrw.commons.notification.NotificationType.THANK_YOU_EDIT;
import static fr.free.nrw.commons.notification.NotificationType.UNKNOWN; import static fr.free.nrw.commons.notification.NotificationType.UNKNOWN;
public class NotificationUtils { public class NotificationUtils {
private static final String COMMONS_WIKI = "commonswiki"; private static final String COMMONS_WIKI = "commonswiki";
private static final String WIKIDATA_WIKI = "wikidatawiki";
private static final String WIKIPEDIA_WIKI = "enwiki";
public static boolean isCommonsNotification(Node document) { public static boolean isCommonsNotification(Node document) {
if (document == null || !document.hasAttributes()) { if (document == null || !document.hasAttributes()) {
@ -31,6 +32,32 @@ public class NotificationUtils {
return COMMONS_WIKI.equals(element.getAttribute("wiki")); return COMMONS_WIKI.equals(element.getAttribute("wiki"));
} }
/**
* Returns true if the wiki attribute corresponds to wikidatawiki
* @param document
* @return
*/
public static boolean isWikidataNotification(Node document) {
if (document == null || !document.hasAttributes()) {
return false;
}
Element element = (Element) document;
return WIKIDATA_WIKI.equals(element.getAttribute("wiki"));
}
/**
* Returns true if the wiki attribute corresponds to enwiki
* @param document
* @return
*/
public static boolean isWikipediaNotification(Node document) {
if (document == null || !document.hasAttributes()) {
return false;
}
Element element = (Element) document;
return WIKIPEDIA_WIKI.equals(element.getAttribute("wiki"));
}
public static NotificationType getNotificationType(Node document) { public static NotificationType getNotificationType(Node document) {
Element element = (Element) document; Element element = (Element) document;
String type = element.getAttribute("type"); String type = element.getAttribute("type");
@ -68,10 +95,17 @@ public class NotificationUtils {
return notifications; return notifications;
} }
/**
* Currently the app is interested in showing notifications just from the following three wikis: commons, wikidata, wikipedia
* This function returns true only if the notification belongs to any of the above wikis and is of a known notification type
* @param node
* @return
*/
private static boolean isUsefulNotification(Node node) { private static boolean isUsefulNotification(Node node) {
return isCommonsNotification(node) return (isCommonsNotification(node)
&& !getNotificationType(node).equals(UNKNOWN) || isWikidataNotification(node)
&& !getNotificationType(node).equals(THANK_YOU_EDIT); || isWikipediaNotification(node))
&& !getNotificationType(node).equals(UNKNOWN);
} }
public static boolean isBundledNotification(Node document) { public static boolean isBundledNotification(Node document) {
@ -97,7 +131,7 @@ public class NotificationUtils {
switch (type) { switch (type) {
case THANK_YOU_EDIT: case THANK_YOU_EDIT:
notificationText = context.getString(R.string.notifications_thank_you_edit); notificationText = getThankYouEditDescription(document);
break; break;
case EDIT_USER_TALK: case EDIT_USER_TALK:
notificationText = getNotificationText(document); notificationText = getNotificationText(document);
@ -146,6 +180,16 @@ public class NotificationUtils {
return body != null ? body.getTextContent() : ""; return body != null ? body.getTextContent() : "";
} }
/**
* Gets the header node returned in the XML document to form the description for thank you edits
* @param document
* @return
*/
private static String getThankYouEditDescription(Node document) {
Node body = getNode(getModel(document), "header");
return body != null ? body.getTextContent() : "";
}
private static String getNotificationIconUrl(Node document) { private static String getNotificationIconUrl(Node document) {
String format = "%s%s"; String format = "%s%s";
Node iconUrl = getNode(getModel(document), "iconUrl"); Node iconUrl = getNode(getModel(document), "iconUrl");

View file

@ -0,0 +1,35 @@
package fr.free.nrw.commons.notification;
import android.content.Context;
import android.graphics.drawable.PictureDrawable;
import android.support.annotation.NonNull;
import com.bumptech.glide.Glide;
import com.bumptech.glide.Registry;
import com.bumptech.glide.annotation.GlideModule;
import com.bumptech.glide.module.AppGlideModule;
import com.caverock.androidsvg.SVG;
import java.io.InputStream;
import fr.free.nrw.commons.glide.SvgDecoder;
import fr.free.nrw.commons.glide.SvgDrawableTranscoder;
/**
* Module for the SVG sample app.
*/
@GlideModule
public class SvgModule extends AppGlideModule {
@Override
public void registerComponents(@NonNull Context context, @NonNull Glide glide,
@NonNull Registry registry) {
registry.register(SVG.class, PictureDrawable.class, new SvgDrawableTranscoder())
.append(InputStream.class, SVG.class, new SvgDecoder());
}
// Disable manifest parsing to avoid adding similar modules twice.
@Override
public boolean isManifestParsingEnabled() {
return false;
}
}

View file

@ -3,13 +3,10 @@ package fr.free.nrw.commons.settings;
import android.Manifest; import android.Manifest;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.content.ActivityNotFoundException; import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
@ -24,8 +21,6 @@ import android.support.v4.content.FileProvider;
import android.widget.Toast; import android.widget.Toast;
import java.io.File; import java.io.File;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
@ -35,7 +30,7 @@ import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.Utils; import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.di.ApplicationlessInjection; import fr.free.nrw.commons.di.ApplicationlessInjection;
import fr.free.nrw.commons.utils.FileUtils; import fr.free.nrw.commons.upload.FileUtils;
public class SettingsFragment extends PreferenceFragment { public class SettingsFragment extends PreferenceFragment {

View file

@ -1,10 +1,8 @@
package fr.free.nrw.commons.upload; package fr.free.nrw.commons.upload;
import android.app.Activity; import android.app.Activity;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.graphics.BitmapRegionDecoder; import android.graphics.BitmapRegionDecoder;
import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.support.v7.app.AlertDialog; import android.support.v7.app.AlertDialog;

View file

@ -0,0 +1,263 @@
package fr.free.nrw.commons.upload;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.Context;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.Date;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
import fr.free.nrw.commons.caching.CacheController;
import fr.free.nrw.commons.di.ApplicationlessInjection;
import fr.free.nrw.commons.mwapi.CategoryApi;
import io.reactivex.schedulers.Schedulers;
import timber.log.Timber;
import static com.mapbox.mapboxsdk.Mapbox.getApplicationContext;
/**
* Processing of the image file that is about to be uploaded via ShareActivity is done here
*/
public class FileProcessor implements SimilarImageDialogFragment.onResponse {
@Inject
CacheController cacheController;
@Inject
GpsCategoryModel gpsCategoryModel;
@Inject
CategoryApi apiCall;
@Inject
@Named("default_preferences")
SharedPreferences prefs;
private Uri mediaUri;
private ContentResolver contentResolver;
private GPSExtractor imageObj;
private Context context;
private String decimalCoords;
private boolean haveCheckedForOtherImages = false;
private String filePath;
private boolean useExtStorage;
private boolean cacheFound;
private GPSExtractor tempImageObj;
FileProcessor(Uri mediaUri, ContentResolver contentResolver, Context context) {
this.mediaUri = mediaUri;
this.contentResolver = contentResolver;
this.context = context;
ApplicationlessInjection.getInstance(context.getApplicationContext()).getCommonsApplicationComponent().inject(this);
useExtStorage = prefs.getBoolean("useExternalStorage", true);
}
/**
* Gets file path from media URI.
* In older devices getPath() may fail depending on the source URI, creating and using a copy of the file seems to work instead.
*
* @return file path of media
*/
@Nullable
private String getPathOfMediaOrCopy() {
filePath = FileUtils.getPath(context, mediaUri);
Timber.d("Filepath: " + filePath);
if (filePath == null) {
String copyPath = null;
try {
ParcelFileDescriptor descriptor = contentResolver.openFileDescriptor(mediaUri, "r");
if (descriptor != null) {
if (useExtStorage) {
copyPath = FileUtils.createCopyPath(descriptor);
return copyPath;
}
copyPath = getApplicationContext().getCacheDir().getAbsolutePath() + "/" + new Date().getTime() + ".jpg";
FileUtils.copy(descriptor.getFileDescriptor(), copyPath);
Timber.d("Filepath (copied): %s", copyPath);
return copyPath;
}
} catch (IOException e) {
Timber.w(e, "Error in file " + copyPath);
return null;
}
}
return filePath;
}
/**
* Processes file coordinates, either from EXIF data or user location
*
* @param gpsEnabled if true use GPS
*/
GPSExtractor processFileCoordinates(boolean gpsEnabled) {
Timber.d("Calling GPSExtractor");
try {
ParcelFileDescriptor descriptor = contentResolver.openFileDescriptor(mediaUri, "r");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
if (descriptor != null) {
imageObj = new GPSExtractor(descriptor.getFileDescriptor(), context, prefs);
}
} else {
String filePath = getPathOfMediaOrCopy();
if (filePath != null) {
imageObj = new GPSExtractor(filePath, context, prefs);
}
}
decimalCoords = imageObj.getCoords(gpsEnabled);
if (decimalCoords == null || !imageObj.imageCoordsExists) {
//Find other photos taken around the same time which has gps coordinates
if (!haveCheckedForOtherImages)
findOtherImages(gpsEnabled);// Do not do repeat the process
} else {
useImageCoords();
}
} catch (FileNotFoundException e) {
Timber.w("File not found: " + mediaUri, e);
}
return imageObj;
}
String getDecimalCoords() {
return decimalCoords;
}
/**
* Find other images around the same location that were taken within the last 20 sec
*
* @param gpsEnabled True if GPS is enabled
*/
private void findOtherImages(boolean gpsEnabled) {
Timber.d("filePath" + getPathOfMediaOrCopy());
long timeOfCreation = new File(filePath).lastModified();//Time when the original image was created
File folder = new File(filePath.substring(0, filePath.lastIndexOf('/')));
File[] files = folder.listFiles();
Timber.d("folderTime Number:" + files.length);
for (File file : files) {
if (file.lastModified() - timeOfCreation <= (120 * 1000) && file.lastModified() - timeOfCreation >= -(120 * 1000)) {
//Make sure the photos were taken within 20seconds
Timber.d("fild date:" + file.lastModified() + " time of creation" + timeOfCreation);
tempImageObj = null;//Temporary GPSExtractor to extract coords from these photos
ParcelFileDescriptor descriptor = null;
try {
descriptor = contentResolver.openFileDescriptor(Uri.parse(file.getAbsolutePath()), "r");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
if (descriptor != null) {
tempImageObj = new GPSExtractor(descriptor.getFileDescriptor(), context, prefs);
}
} else {
if (filePath != null) {
tempImageObj = new GPSExtractor(file.getAbsolutePath(), context, prefs);
}
}
if (tempImageObj != null) {
Timber.d("not null fild EXIF" + tempImageObj.imageCoordsExists + " coords" + tempImageObj.getCoords(gpsEnabled));
if (tempImageObj.getCoords(gpsEnabled) != null && tempImageObj.imageCoordsExists) {
// Current image has gps coordinates and it's not current gps locaiton
Timber.d("This file has image coords:" + file.getAbsolutePath());
SimilarImageDialogFragment newFragment = new SimilarImageDialogFragment();
Bundle args = new Bundle();
args.putString("originalImagePath", filePath);
args.putString("possibleImagePath", file.getAbsolutePath());
newFragment.setArguments(args);
newFragment.show(((AppCompatActivity) context).getSupportFragmentManager(), "dialog");
break;
}
}
}
}
haveCheckedForOtherImages = true; //Finished checking for other images
}
/**
* Initiates retrieval of image coordinates or user coordinates, and caching of coordinates.
* Then initiates the calls to MediaWiki API through an instance of CategoryApi.
*/
@SuppressLint("CheckResult")
public void useImageCoords() {
if (decimalCoords != null) {
Timber.d("Decimal coords of image: %s", decimalCoords);
Timber.d("is EXIF data present:" + imageObj.imageCoordsExists + " from findOther image");
// Only set cache for this point if image has coords
if (imageObj.imageCoordsExists) {
double decLongitude = imageObj.getDecLongitude();
double decLatitude = imageObj.getDecLatitude();
cacheController.setQtPoint(decLongitude, decLatitude);
}
List<String> displayCatList = cacheController.findCategory();
boolean catListEmpty = displayCatList.isEmpty();
// If no categories found in cache, call MediaWiki API to match image coords with nearby Commons categories
if (catListEmpty) {
cacheFound = false;
apiCall.request(decimalCoords)
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.subscribe(
gpsCategoryModel::setCategoryList,
throwable -> {
Timber.e(throwable);
gpsCategoryModel.clear();
}
);
Timber.d("displayCatList size 0, calling MWAPI %s", displayCatList);
} else {
cacheFound = true;
Timber.d("Cache found, setting categoryList in model to %s", displayCatList);
gpsCategoryModel.setCategoryList(displayCatList);
}
} else {
Timber.d("EXIF: no coords");
}
}
boolean isCacheFound() {
return cacheFound;
}
/**
* Calls the async task that detects if image is fuzzy, too dark, etc
*/
void detectUnwantedPictures() {
String imageMediaFilePath = FileUtils.getPath(context, mediaUri);
DetectUnwantedPicturesAsync detectUnwantedPicturesAsync
= new DetectUnwantedPicturesAsync(new WeakReference<Activity>((Activity) context), imageMediaFilePath);
detectUnwantedPicturesAsync.execute();
}
@Override
public void onPositiveResponse() {
imageObj = tempImageObj;
decimalCoords = imageObj.getCoords(false);// Not necessary to use gps as image already ha EXIF data
Timber.d("EXIF from tempImageObj");
useImageCoords();
}
@Override
public void onNegativeResponse() {
Timber.d("EXIF from imageObj");
useImageCoords();
}
}

View file

@ -15,18 +15,84 @@ import android.provider.MediaStore;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import java.io.BufferedReader;
import java.io.File; import java.io.File;
import java.io.FileDescriptor; import java.io.FileDescriptor;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.math.BigInteger;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Date; import java.util.Date;
import timber.log.Timber; import timber.log.Timber;
public class FileUtils { public class FileUtils {
/**
* Get SHA1 of file from input stream
*/
static String getSHA1(InputStream is) {
MessageDigest digest;
try {
digest = MessageDigest.getInstance("SHA1");
} catch (NoSuchAlgorithmException e) {
Timber.e(e, "Exception while getting Digest");
return "";
}
byte[] buffer = new byte[8192];
int read;
try {
while ((read = is.read(buffer)) > 0) {
digest.update(buffer, 0, read);
}
byte[] md5sum = digest.digest();
BigInteger bigInt = new BigInteger(1, md5sum);
String output = bigInt.toString(16);
// Fill to 40 chars
output = String.format("%40s", output).replace(' ', '0');
Timber.i("File SHA1: %s", output);
return output;
} catch (IOException e) {
Timber.e(e, "IO Exception");
return "";
} finally {
try {
is.close();
} catch (IOException e) {
Timber.e(e, "Exception on closing MD5 input stream");
}
}
}
/**
* In older devices getPath() may fail depending on the source URI. Creating and using a copy of the file seems to work instead.
* @return path of copy
*/
@Nullable
static String createCopyPath(ParcelFileDescriptor descriptor) {
try {
String copyPath = Environment.getExternalStorageDirectory().toString() + "/CommonsApp/" + new Date().getTime() + ".jpg";
File newFile = new File(Environment.getExternalStorageDirectory().toString() + "/CommonsApp");
newFile.mkdir();
FileUtils.copy(descriptor.getFileDescriptor(), copyPath);
Timber.d("Filepath (copied): %s", copyPath);
return copyPath;
} catch (IOException e) {
Timber.e(e);
return null;
}
}
/** /**
* Get a file path from a Uri. This will get the the path for Storage Access * Get a file path from a Uri. This will get the the path for Storage Access
* Framework Documents, as well as the _data field for the MediaStore and * Framework Documents, as well as the _data field for the MediaStore and
@ -235,4 +301,80 @@ public class FileUtils {
copy(new FileInputStream(source), new FileOutputStream(destination)); copy(new FileInputStream(source), new FileOutputStream(destination));
} }
/**
* Read and return the content of a resource file as string.
* @param fileName asset file's path (e.g. "/queries/nearby_query.rq")
* @return the content of the file
*/
public static String readFromResource(String fileName) throws IOException {
StringBuilder buffer = new StringBuilder();
BufferedReader reader = null;
try {
InputStream inputStream = FileUtils.class.getResourceAsStream(fileName);
if (inputStream == null) {
throw new FileNotFoundException(fileName);
}
reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
String line;
while ((line = reader.readLine()) != null) {
buffer.append(line).append("\n");
}
} finally {
if (reader != null) {
reader.close();
}
}
return buffer.toString();
}
/**
* Deletes files.
* @param file context
*/
public static boolean deleteFile(File file) {
boolean deletedAll = true;
if (file != null) {
if (file.isDirectory()) {
String[] children = file.list();
for (String child : children) {
deletedAll = deleteFile(new File(file, child)) && deletedAll;
}
} else {
deletedAll = file.delete();
}
}
return deletedAll;
}
public static File createAndGetAppLogsFile(String logs) {
try {
File commonsAppDirectory = new File(Environment.getExternalStorageDirectory().toString() + "/CommonsApp");
if (!commonsAppDirectory.exists()) {
commonsAppDirectory.mkdir();
}
File logsFile = new File(commonsAppDirectory,"logs.txt");
if (logsFile.exists()) {
//old logs file is useless
logsFile.delete();
}
logsFile.createNewFile();
FileOutputStream outputStream = new FileOutputStream(logsFile);
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
outputStreamWriter.append(logs);
outputStreamWriter.close();
outputStream.flush();
outputStream.close();
return logsFile;
} catch (IOException ioe) {
Timber.e(ioe);
return null;
}
}
} }

View file

@ -0,0 +1,40 @@
package fr.free.nrw.commons.upload;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.inject.Inject;
import javax.inject.Singleton;
@Singleton
public class GpsCategoryModel {
private Set<String> categorySet;
@Inject
public GpsCategoryModel() {
clear();
}
public void clear() {
categorySet = new HashSet<>();
}
public boolean getGpsCatExists() {
return !categorySet.isEmpty();
}
public List<String> getCategoryList() {
return new ArrayList<>(categorySet);
}
public void setCategoryList(List<String> categoryList) {
clear();
categorySet.addAll(categoryList != null ? categoryList : new ArrayList<>());
}
public void add(String categoryString) {
categorySet.add(categoryString);
}
}

View file

@ -47,6 +47,8 @@ import fr.free.nrw.commons.modifications.TemplateRemoveModifier;
import fr.free.nrw.commons.mwapi.MediaWikiApi; import fr.free.nrw.commons.mwapi.MediaWikiApi;
import timber.log.Timber; import timber.log.Timber;
//TODO: We should use this class to see how multiple uploads are handled, and then REMOVE it.
public class MultipleShareActivity extends AuthenticatedActivity public class MultipleShareActivity extends AuthenticatedActivity
implements MediaDetailPagerFragment.MediaDetailProvider, implements MediaDetailPagerFragment.MediaDetailProvider,
AdapterView.OnItemClickListener, AdapterView.OnItemClickListener,

View file

@ -1,6 +1,5 @@
package fr.free.nrw.commons.upload; package fr.free.nrw.commons.upload;
import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.graphics.Point; import android.graphics.Point;
import android.net.Uri; import android.net.Uri;
@ -17,7 +16,6 @@ import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.BaseAdapter; import android.widget.BaseAdapter;
import android.widget.EditText; import android.widget.EditText;
@ -26,6 +24,8 @@ import android.widget.GridView;
import android.widget.RelativeLayout; import android.widget.RelativeLayout;
import android.widget.TextView; import android.widget.TextView;
import butterknife.BindView;
import butterknife.ButterKnife;
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder; import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
import com.facebook.drawee.view.SimpleDraweeView; import com.facebook.drawee.view.SimpleDraweeView;
@ -41,9 +41,13 @@ public class MultipleUploadListFragment extends Fragment {
void OnMultipleUploadInitiated(); void OnMultipleUploadInitiated();
} }
private GridView photosGrid; @BindView(R.id.multipleShareBackground)
GridView photosGrid;
@BindView(R.id.multipleBaseTitle)
EditText baseTitle;
private PhotoDisplayAdapter photosAdapter; private PhotoDisplayAdapter photosAdapter;
private EditText baseTitle;
private TitleTextWatcher textWatcher = new TitleTextWatcher(); private TitleTextWatcher textWatcher = new TitleTextWatcher();
private Point photoSize; private Point photoSize;
@ -166,9 +170,7 @@ public class MultipleUploadListFragment extends Fragment {
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_multiple_uploads_list, container, false); View view = inflater.inflate(R.layout.fragment_multiple_uploads_list, container, false);
photosGrid = view.findViewById(R.id.multipleShareBackground); ButterKnife.bind(this,view);
baseTitle = view.findViewById(R.id.multipleBaseTitle);
photosAdapter = new PhotoDisplayAdapter(); photosAdapter = new PhotoDisplayAdapter();
photosGrid.setAdapter(photosAdapter); photosGrid.setAdapter(photosAdapter);
photosGrid.setOnItemClickListener((AdapterView.OnItemClickListener) getActivity()); photosGrid.setOnItemClickListener((AdapterView.OnItemClickListener) getActivity());

View file

@ -1,249 +0,0 @@
package fr.free.nrw.commons.upload;
import android.content.Context;
import android.net.Uri;
import com.android.volley.Cache;
import com.android.volley.NetworkResponse;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.HttpHeaderParser;
import com.android.volley.toolbox.JsonRequest;
import com.android.volley.toolbox.Volley;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import timber.log.Timber;
/**
* Uses the Volley library to implement asynchronous calls to the Commons MediaWiki API to match
* GPS coordinates with nearby Commons categories. Parses the results using GSON to obtain a list
* of relevant categories.
*/
public class MwVolleyApi {
private static RequestQueue REQUEST_QUEUE;
private static final Gson GSON = new GsonBuilder().create();
private static Set<String> categorySet;
private static List<String> categoryList;
private static final String MWURL = "https://commons.wikimedia.org/";
private final Context context;
public MwVolleyApi(Context context) {
this.context = context;
categorySet = new HashSet<>();
}
public static List<String> getGpsCat() {
return categoryList;
}
public static void setGpsCat(List<String> cachedList) {
categoryList = new ArrayList<>();
categoryList.addAll(cachedList);
Timber.d("Setting GPS cats from cache: %s", categoryList);
}
public void request(String coords) {
String apiUrl = buildUrl(coords);
Timber.d("URL: %s", apiUrl);
JsonRequest<QueryResponse> request = new QueryRequest(apiUrl,
new LogResponseListener<>(), new LogResponseErrorListener());
getQueue().add(request);
}
/**
* Builds URL with image coords for MediaWiki API calls
* Example URL: https://commons.wikimedia.org/w/api.php?action=query&prop=categories|coordinates|pageprops&format=json&clshow=!hidden&coprop=type|name|dim|country|region|globe&codistancefrompoint=38.11386944444445|13.356263888888888&generator=geosearch&redirects=&ggscoord=38.11386944444445|1.356263888888888&ggsradius=100&ggslimit=10&ggsnamespace=6&ggsprop=type|name|dim|country|region|globe&ggsprimary=all&formatversion=2
* @param coords Coordinates to build query with
* @return URL for API query
*/
private String buildUrl(String coords) {
Uri.Builder builder = Uri.parse(MWURL).buildUpon();
builder.appendPath("w")
.appendPath("api.php")
.appendQueryParameter("action", "query")
.appendQueryParameter("prop", "categories|coordinates|pageprops")
.appendQueryParameter("format", "json")
.appendQueryParameter("clshow", "!hidden")
.appendQueryParameter("coprop", "type|name|dim|country|region|globe")
.appendQueryParameter("codistancefrompoint", coords)
.appendQueryParameter("generator", "geosearch")
.appendQueryParameter("ggscoord", coords)
.appendQueryParameter("ggsradius", "10000")
.appendQueryParameter("ggslimit", "10")
.appendQueryParameter("ggsnamespace", "6")
.appendQueryParameter("ggsprop", "type|name|dim|country|region|globe")
.appendQueryParameter("ggsprimary", "all")
.appendQueryParameter("formatversion", "2");
return builder.toString();
}
private synchronized RequestQueue getQueue() {
if (REQUEST_QUEUE == null) {
REQUEST_QUEUE = Volley.newRequestQueue(context);
}
return REQUEST_QUEUE;
}
private static class LogResponseListener<T> implements Response.Listener<T> {
@Override
public void onResponse(T response) {
Timber.d(response.toString());
}
}
private static class LogResponseErrorListener implements Response.ErrorListener {
@Override
public void onErrorResponse(VolleyError error) {
Timber.e(error.toString());
}
}
private static class QueryRequest extends JsonRequest<QueryResponse> {
public QueryRequest(String url,
Response.Listener<QueryResponse> listener,
Response.ErrorListener errorListener) {
super(Request.Method.GET, url, null, listener, errorListener);
}
@Override
protected Response<QueryResponse> parseNetworkResponse(NetworkResponse response) {
String json = parseString(response);
QueryResponse queryResponse = GSON.fromJson(json, QueryResponse.class);
return Response.success(queryResponse, cacheEntry(response));
}
private Cache.Entry cacheEntry(NetworkResponse response) {
return HttpHeaderParser.parseCacheHeaders(response);
}
private String parseString(NetworkResponse response) {
try {
return new String(response.data, HttpHeaderParser.parseCharset(response.headers));
} catch (UnsupportedEncodingException e) {
return new String(response.data);
}
}
}
public static class GpsCatExists {
private static boolean gpsCatExists;
public static void setGpsCatExists(boolean gpsCat) {
gpsCatExists = gpsCat;
}
public static boolean getGpsCatExists() {
return gpsCatExists;
}
}
private static class QueryResponse {
private Query query = new Query();
private String printSet() {
if (categorySet == null || categorySet.isEmpty()) {
GpsCatExists.setGpsCatExists(false);
Timber.d("gpsCatExists=%b", GpsCatExists.getGpsCatExists());
return "No collection of categories";
} else {
GpsCatExists.setGpsCatExists(true);
Timber.d("gpsCatExists=%b", GpsCatExists.getGpsCatExists());
return "CATEGORIES FOUND" + categorySet.toString();
}
}
@Override
public String toString() {
if (query != null) {
return "query=" + query.toString() + "\n" + printSet();
} else {
return "No pages found";
}
}
}
private static class Query {
private Page [] pages;
@Override
public String toString() {
StringBuilder builder = new StringBuilder("pages=" + "\n");
if (pages != null) {
for (Page page : pages) {
builder.append(page.toString());
builder.append("\n");
}
builder.replace(builder.length() - 1, builder.length(), "");
return builder.toString();
} else {
return "No pages found";
}
}
}
public static class Page {
private int pageid;
private int ns;
private String title;
private Category[] categories;
private Category category;
public Page() {
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder("PAGEID=" + pageid + " ns=" + ns + " title=" + title + "\n" + " CATEGORIES= ");
if (categories == null || categories.length == 0) {
builder.append("no categories exist\n");
} else {
for (Category category : categories) {
builder.append(category.toString());
builder.append("\n");
if (category != null) {
String categoryString = category.toString().replace("Category:", "");
categorySet.add(categoryString);
}
}
}
categoryList = new ArrayList<>(categorySet);
builder.replace(builder.length() - 1, builder.length(), "");
return builder.toString();
}
}
private static class Category {
private String title;
@Override
public String toString() {
return title;
}
}
}

View file

@ -13,6 +13,9 @@ import android.view.ViewGroup;
import android.view.Window; import android.view.Window;
import android.widget.Button; import android.widget.Button;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder; import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
import com.facebook.drawee.view.SimpleDraweeView; import com.facebook.drawee.view.SimpleDraweeView;
import com.facebook.imagepipeline.listener.RequestListener; import com.facebook.imagepipeline.listener.RequestListener;
@ -29,29 +32,33 @@ import fr.free.nrw.commons.R;
*/ */
public class SimilarImageDialogFragment extends DialogFragment { public class SimilarImageDialogFragment extends DialogFragment {
@BindView(R.id.orginalImage)
SimpleDraweeView originalImage; SimpleDraweeView originalImage;
@BindView(R.id.possibleImage)
SimpleDraweeView possibleImage; SimpleDraweeView possibleImage;
@BindView(R.id.postive_button)
Button positiveButton; Button positiveButton;
@BindView(R.id.negative_button)
Button negativeButton; Button negativeButton;
onResponse mOnResponse;//Implemented interface from shareActivity onResponse mOnResponse;//Implemented interface from shareActivity
Boolean gotResponse = false; Boolean gotResponse = false;
public SimilarImageDialogFragment() { public SimilarImageDialogFragment() {
} }
public interface onResponse{ public interface onResponse{
public void onPostiveResponse(); public void onPositiveResponse();
public void onNegativeResponse(); public void onNegativeResponse();
} }
@Override @Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_similar_image_dialog, container, false); View view = inflater.inflate(R.layout.fragment_similar_image_dialog, container, false);
ButterKnife.bind(this,view);
Set<RequestListener> requestListeners = new HashSet<>(); Set<RequestListener> requestListeners = new HashSet<>();
requestListeners.add(new RequestLoggingListener()); requestListeners.add(new RequestLoggingListener());
originalImage =(SimpleDraweeView) view.findViewById(R.id.orginalImage);
possibleImage =(SimpleDraweeView) view.findViewById(R.id.possibleImage);
positiveButton = (Button) view.findViewById(R.id.postive_button);
negativeButton = (Button) view.findViewById(R.id.negative_button);
originalImage.setHierarchy(GenericDraweeHierarchyBuilder originalImage.setHierarchy(GenericDraweeHierarchyBuilder
.newInstance(getResources()) .newInstance(getResources())
.setPlaceholderImage(VectorDrawableCompat.create(getResources(), .setPlaceholderImage(VectorDrawableCompat.create(getResources(),
@ -70,22 +77,6 @@ public class SimilarImageDialogFragment extends DialogFragment {
originalImage.setImageURI(Uri.fromFile(new File(getArguments().getString("originalImagePath")))); originalImage.setImageURI(Uri.fromFile(new File(getArguments().getString("originalImagePath"))));
possibleImage.setImageURI(Uri.fromFile(new File(getArguments().getString("possibleImagePath")))); possibleImage.setImageURI(Uri.fromFile(new File(getArguments().getString("possibleImagePath"))));
negativeButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mOnResponse.onNegativeResponse();
gotResponse = true;
dismiss();
}
});
positiveButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mOnResponse.onPostiveResponse();
gotResponse = true;
dismiss();
}
});
return view; return view;
} }
@ -105,8 +96,23 @@ public class SimilarImageDialogFragment extends DialogFragment {
@Override @Override
public void onDismiss(DialogInterface dialog) { public void onDismiss(DialogInterface dialog) {
// I user dismisses dialog by pressing outside the dialog. // I user dismisses dialog by pressing outside the dialog.
if(!gotResponse) if (!gotResponse) {
mOnResponse.onNegativeResponse(); mOnResponse.onNegativeResponse();
}
super.onDismiss(dialog); super.onDismiss(dialog);
} }
@OnClick(R.id.negative_button)
public void onNegativeButtonClicked() {
mOnResponse.onNegativeResponse();
gotResponse = true;
dismiss();
}
@OnClick(R.id.postive_button)
public void onPositiveButtonClicked() {
mOnResponse.onPositiveResponse();
gotResponse = true;
dismiss();
}
} }

View file

@ -74,13 +74,13 @@ public class SingleUploadFragment extends CommonsDaggerSupportFragment {
//What happens when the 'submit' icon is tapped //What happens when the 'submit' icon is tapped
case R.id.menu_upload_single: case R.id.menu_upload_single:
if (titleEdit.getText().toString().isEmpty()) { if (titleEdit.getText().toString().trim().isEmpty()) {
Toast.makeText(getContext(), R.string.add_title_toast, Toast.LENGTH_LONG).show(); Toast.makeText(getContext(), R.string.add_title_toast, Toast.LENGTH_LONG).show();
return false; return false;
} }
String title = titleEdit.getText().toString(); String title = titleEdit.getText().toString().trim();
String desc = descEdit.getText().toString(); String desc = descEdit.getText().toString().trim();
//Save the title/desc in short-lived cache so next time this fragment is loaded, we can access these //Save the title/desc in short-lived cache so next time this fragment is loaded, we can access these
prefs.edit() prefs.edit()

View file

@ -92,7 +92,7 @@ public class UploadController {
* @param decimalCoords the coordinates in decimal. (e.g. "37.51136|-77.602615") * @param decimalCoords the coordinates in decimal. (e.g. "37.51136|-77.602615")
* @param onComplete the progress tracker * @param onComplete the progress tracker
*/ */
public void startUpload(String title, Uri mediaUri, String description, String mimeType, String source, String decimalCoords, ContributionUploadProgress onComplete) { public void startUpload(String title, Uri mediaUri, String description, String mimeType, String source, String decimalCoords, String wikiDataEntityId, ContributionUploadProgress onComplete) {
Contribution contribution; Contribution contribution;
//TODO: Modify this to include coords //TODO: Modify this to include coords
@ -100,12 +100,18 @@ public class UploadController {
null, null, sessionManager.getCurrentAccount().name, null, null, sessionManager.getCurrentAccount().name,
CommonsApplication.DEFAULT_EDIT_SUMMARY, decimalCoords); CommonsApplication.DEFAULT_EDIT_SUMMARY, decimalCoords);
contribution.setTag("mimeType", mimeType); contribution.setTag("mimeType", mimeType);
contribution.setSource(source); contribution.setSource(source);
//Calls the next overloaded method //Calls the next overloaded method
startUpload(contribution, onComplete); startUpload(contribution, onComplete);
contribution.setTag("mimeType", mimeType);
contribution.setSource(source);
contribution.setWikiDataEntityId(wikiDataEntityId);
} }
/** /**

View file

@ -7,7 +7,6 @@ import android.app.PendingIntent;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.ContentValues; import android.content.ContentValues;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat;
@ -23,7 +22,6 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named;
import fr.free.nrw.commons.HandlerService; import fr.free.nrw.commons.HandlerService;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
@ -36,6 +34,7 @@ import fr.free.nrw.commons.contributions.ContributionsContentProvider;
import fr.free.nrw.commons.modifications.ModificationsContentProvider; import fr.free.nrw.commons.modifications.ModificationsContentProvider;
import fr.free.nrw.commons.mwapi.MediaWikiApi; import fr.free.nrw.commons.mwapi.MediaWikiApi;
import fr.free.nrw.commons.mwapi.UploadResult; import fr.free.nrw.commons.mwapi.UploadResult;
import fr.free.nrw.commons.wikidata.WikidataEditService;
import timber.log.Timber; import timber.log.Timber;
public class UploadService extends HandlerService<Contribution> { public class UploadService extends HandlerService<Contribution> {
@ -49,8 +48,8 @@ public class UploadService extends HandlerService<Contribution> {
public static final String EXTRA_CAMPAIGN = EXTRA_PREFIX + ".campaign"; public static final String EXTRA_CAMPAIGN = EXTRA_PREFIX + ".campaign";
@Inject MediaWikiApi mwApi; @Inject MediaWikiApi mwApi;
@Inject WikidataEditService wikidataEditService;
@Inject SessionManager sessionManager; @Inject SessionManager sessionManager;
@Inject @Named("default_preferences") SharedPreferences prefs;
@Inject ContributionDao contributionDao; @Inject ContributionDao contributionDao;
private NotificationManager notificationManager; private NotificationManager notificationManager;
@ -137,6 +136,7 @@ public class UploadService extends HandlerService<Contribution> {
@Override @Override
public void queue(int what, Contribution contribution) { public void queue(int what, Contribution contribution) {
Timber.d("Upload service queue has contribution with wiki data entity id as %s", contribution.getWikiDataEntityId());
switch (what) { switch (what) {
case ACTION_UPLOAD_FILE: case ACTION_UPLOAD_FILE:
@ -231,10 +231,10 @@ public class UploadService extends HandlerService<Contribution> {
Timber.d("Successfully revalidated token!"); Timber.d("Successfully revalidated token!");
} else { } else {
Timber.d("Unable to revalidate :("); Timber.d("Unable to revalidate :(");
// TODO: Put up a new notification, ask them to re-login
stopForeground(true); stopForeground(true);
Toast failureToast = Toast.makeText(this, R.string.authentication_failed, Toast.LENGTH_LONG); Toast failureToast = Toast.makeText(this, R.string.authentication_failed, Toast.LENGTH_LONG);
failureToast.show(); failureToast.show();
sessionManager.forceLogin(this);
return; return;
} }
} }
@ -253,6 +253,7 @@ public class UploadService extends HandlerService<Contribution> {
if (!resultStatus.equals("Success")) { if (!resultStatus.equals("Success")) {
showFailedNotification(contribution); showFailedNotification(contribution);
} else { } else {
wikidataEditService.createClaimWithLogging(contribution.getWikiDataEntityId(), filename);
contribution.setFilename(uploadResult.getCanonicalFilename()); contribution.setFilename(uploadResult.getCanonicalFilename());
contribution.setImageUrl(uploadResult.getImageUrl()); contribution.setImageUrl(uploadResult.getImageUrl());
contribution.setState(Contribution.STATE_COMPLETED); contribution.setState(Contribution.STATE_COMPLETED);

View file

@ -0,0 +1,115 @@
package fr.free.nrw.commons.upload;
import android.content.ContentResolver;
import android.graphics.Bitmap;
import android.graphics.BitmapRegionDecoder;
import android.graphics.Point;
import android.graphics.Rect;
import android.net.Uri;
import android.provider.MediaStore;
import android.support.v4.graphics.BitmapCompat;
import android.view.View;
import android.widget.FrameLayout;
import java.io.IOException;
import java.io.InputStream;
import timber.log.Timber;
/**
* Contains utility methods for the Zoom function in ShareActivity.
*/
public class Zoom {
private View thumbView;
private ContentResolver contentResolver;
private FrameLayout flContainer;
Zoom(View thumbView, FrameLayout flContainer, ContentResolver contentResolver) {
this.thumbView = thumbView;
this.contentResolver = contentResolver;
this.flContainer = flContainer;
}
/**
* Create a scaled bitmap to display the zoomed-in image
* @param input the input stream corresponding to the uploaded image
* @param imageUri the uploaded image's URI
* @return a zoomable bitmap
*/
Bitmap createScaledImage(InputStream input, Uri imageUri) {
Bitmap scaled = null;
BitmapRegionDecoder decoder = null;
Bitmap bitmap = null;
try {
decoder = BitmapRegionDecoder.newInstance(input, false);
bitmap = decoder.decodeRegion(new Rect(10, 10, 50, 50), null);
} catch (IOException e) {
Timber.e(e);
} catch (NullPointerException e) {
Timber.e(e);
}
try {
//Compress the Image
System.gc();
Runtime rt = Runtime.getRuntime();
long maxMemory = rt.freeMemory();
bitmap = MediaStore.Images.Media.getBitmap(contentResolver, imageUri);
int bitmapByteCount = BitmapCompat.getAllocationByteCount(bitmap);
long height = bitmap.getHeight();
long width = bitmap.getWidth();
long calHeight = (long) ((height * maxMemory) / (bitmapByteCount * 1.1));
long calWidth = (long) ((width * maxMemory) / (bitmapByteCount * 1.1));
scaled = Bitmap.createScaledBitmap(bitmap, (int) Math.min(width, calWidth), (int) Math.min(height, calHeight), true);
} catch (IOException e) {
Timber.e(e);
} catch (NullPointerException e) {
Timber.e(e);
scaled = bitmap;
}
return scaled;
}
/**
* Calculate the starting and ending bounds for the zoomed-in image.
* Also set the container view's offset as the origin for the
* bounds, since that's the origin for the positioning animation
* properties (X, Y).
* @param startBounds the global visible rectangle of the thumbnail
* @param finalBounds the global visible rectangle of the container view
* @param globalOffset the container view's offset
* @return scaled start bounds
*/
float adjustStartEndBounds(Rect startBounds, Rect finalBounds, Point globalOffset) {
thumbView.getGlobalVisibleRect(startBounds);
flContainer.getGlobalVisibleRect(finalBounds, globalOffset);
startBounds.offset(-globalOffset.x, -globalOffset.y);
finalBounds.offset(-globalOffset.x, -globalOffset.y);
// Adjust the start bounds to be the same aspect ratio as the final
// bounds using the "center crop" technique. This prevents undesirable
// stretching during the animation. Also calculate the start scaling
// factor (the end scaling factor is always 1.0).
float startScale;
if ((float) finalBounds.width() / finalBounds.height()
> (float) startBounds.width() / startBounds.height()) {
// Extend start bounds horizontally
startScale = (float) startBounds.height() / finalBounds.height();
float startWidth = startScale * finalBounds.width();
float deltaWidth = (startWidth - startBounds.width()) / 2;
startBounds.left -= deltaWidth;
startBounds.right += deltaWidth;
} else {
// Extend start bounds vertically
startScale = (float) startBounds.width() / finalBounds.width();
float startHeight = startScale * finalBounds.height();
float deltaHeight = (startHeight - startBounds.height()) / 2;
startBounds.top -= deltaHeight;
startBounds.bottom += deltaHeight;
}
return startScale;
}
}

View file

@ -1,92 +0,0 @@
package fr.free.nrw.commons.utils;
import android.os.Environment;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import timber.log.Timber;
public class FileUtils {
/**
* Read and return the content of a resource file as string.
*
* @param fileName asset file's path (e.g. "/queries/nearby_query.rq")
* @return the content of the file
*/
public static String readFromResource(String fileName) throws IOException {
StringBuilder buffer = new StringBuilder();
BufferedReader reader = null;
try {
InputStream inputStream = FileUtils.class.getResourceAsStream(fileName);
if (inputStream == null) {
throw new FileNotFoundException(fileName);
}
reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
String line;
while ((line = reader.readLine()) != null) {
buffer.append(line).append("\n");
}
} finally {
if (reader != null) {
reader.close();
}
}
return buffer.toString();
}
/**
* Deletes files.
* @param file context
*/
public static boolean deleteFile(File file) {
boolean deletedAll = true;
if (file != null) {
if (file.isDirectory()) {
String[] children = file.list();
for (String child : children) {
deletedAll = deleteFile(new File(file, child)) && deletedAll;
}
} else {
deletedAll = file.delete();
}
}
return deletedAll;
}
public static File createAndGetAppLogsFile(String logs) {
try {
File commonsAppDirectory = new File(Environment.getExternalStorageDirectory().toString() + "/CommonsApp");
if (!commonsAppDirectory.exists()) {
commonsAppDirectory.mkdir();
}
File logsFile = new File(commonsAppDirectory,"logs.txt");
if (logsFile.exists()) {
//old logs file is useless
logsFile.delete();
}
logsFile.createNewFile();
FileOutputStream outputStream = new FileOutputStream(logsFile);
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
outputStreamWriter.append(logs);
outputStreamWriter.close();
outputStream.flush();
outputStream.close();
return logsFile;
} catch (IOException ioe) {
Timber.e(ioe);
return null;
}
}
}

View file

@ -1,10 +1,27 @@
package fr.free.nrw.commons.utils; package fr.free.nrw.commons.utils;
import android.app.WallpaperManager;
import android.content.Context;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapRegionDecoder; import android.graphics.BitmapRegionDecoder;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.Rect; import android.graphics.Rect;
import android.net.Uri;
import android.support.annotation.Nullable;
import com.facebook.common.executors.CallerThreadExecutor;
import com.facebook.common.references.CloseableReference;
import com.facebook.datasource.DataSource;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.imagepipeline.core.ImagePipeline;
import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber;
import com.facebook.imagepipeline.image.CloseableImage;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder;
import java.io.IOException;
import fr.free.nrw.commons.R;
import timber.log.Timber; import timber.log.Timber;
/** /**
@ -132,4 +149,52 @@ public class ImageUtils {
return isImageDark; return isImageDark;
} }
/**
* Downloads the image from the URL and sets it as the phone's wallpaper
* Fails silently if download or setting wallpaper fails.
* @param context
* @param imageUrl
*/
public static void setWallpaperFromImageUrl(Context context, Uri imageUrl) {
Timber.d("Trying to set wallpaper from url %s", imageUrl.toString());
ImageRequest imageRequest = ImageRequestBuilder
.newBuilderWithSource(imageUrl)
.setAutoRotateEnabled(true)
.build();
ImagePipeline imagePipeline = Fresco.getImagePipeline();
final DataSource<CloseableReference<CloseableImage>>
dataSource = imagePipeline.fetchDecodedImage(imageRequest, context);
dataSource.subscribe(new BaseBitmapDataSubscriber() {
@Override
public void onNewResultImpl(@Nullable Bitmap bitmap) {
if (dataSource.isFinished() && bitmap != null){
Timber.d("Bitmap loaded from url %s", imageUrl.toString());
setWallpaper(context, Bitmap.createBitmap(bitmap));
dataSource.close();
}
}
@Override
public void onFailureImpl(DataSource dataSource) {
Timber.d("Error getting bitmap from image url %s", imageUrl.toString());
if (dataSource != null) {
dataSource.close();
}
}
}, CallerThreadExecutor.getInstance());
}
private static void setWallpaper(Context context, Bitmap bitmap) {
WallpaperManager wallpaperManager = WallpaperManager.getInstance(context);
try {
wallpaperManager.setBitmap(bitmap);
ViewUtil.showLongToast(context, context.getString(R.string.wallpaper_set_successfully));
} catch (IOException e) {
Timber.e(e,"Error setting wallpaper");
}
}
} }

View file

@ -0,0 +1,80 @@
package fr.free.nrw.commons.widget;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.widget.RemoteViews;
import com.prof.rssparser.Article;
import com.prof.rssparser.Parser;
import com.squareup.picasso.Picasso;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.util.ArrayList;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.R;
/**
* Implementation of App Widget functionality.
*/
public class PicOfDayAppWidget extends AppWidgetProvider {
static void updateAppWidget(Context context, AppWidgetManager appWidgetManager,
int appWidgetId) {
// Construct the RemoteViews object
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.pic_of_day_app_widget);
String urlString = BuildConfig.WIKIMEDIA_API_POTD;
Parser parser = new Parser();
parser.execute(urlString);
parser.onFinish(new Parser.OnTaskCompleted() {
@Override
public void onTaskCompleted(ArrayList<Article> list) {
String desc = list.get(list.size() - 1).getDescription();
if (desc != null) {
Document document = Jsoup.parse(desc);
Elements elements = document.select("img");
String imageUrl = elements.get(0).attr("src");
if (imageUrl != null && imageUrl.length() > 0) {
Picasso.get().load(imageUrl).into(views, R.id.appwidget_image, new int[]{appWidgetId});
}
}
}
@Override
public void onError() {
}
});
// Instruct the widget manager to update the widget
appWidgetManager.updateAppWidget(appWidgetId, views);
}
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
// There may be multiple widgets active, so update all of them
for (int appWidgetId : appWidgetIds) {
updateAppWidget(context, appWidgetManager, appWidgetId);
}
}
@Override
public void onEnabled(Context context) {
// Enter relevant functionality for when the first widget is created
}
@Override
public void onDisabled(Context context) {
// Enter relevant functionality for when the last widget is disabled
}
}

View file

@ -0,0 +1,16 @@
package fr.free.nrw.commons.wikidata;
public abstract class WikidataEditListener {
protected WikidataP18EditListener wikidataP18EditListener;
public abstract void onSuccessfulWikidataEdit();
public void setAuthenticationStateListener(WikidataP18EditListener wikidataP18EditListener) {
this.wikidataP18EditListener = wikidataP18EditListener;
}
public interface WikidataP18EditListener {
void onWikidataEditSuccessful();
}
}

View file

@ -0,0 +1,20 @@
package fr.free.nrw.commons.wikidata;
/**
* Listener for wikidata edits
*/
public class WikidataEditListenerImpl extends WikidataEditListener {
public WikidataEditListenerImpl() {
}
/**
* Fired when wikidata P18 edit is successful. If there's an active listener, then it is fired
*/
@Override
public void onSuccessfulWikidataEdit() {
if (wikidataP18EditListener != null) {
wikidataP18EditListener.onWikidataEditSuccessful();
}
}
}

View file

@ -0,0 +1,134 @@
package fr.free.nrw.commons.wikidata;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.SharedPreferences;
import java.util.Locale;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
import fr.free.nrw.commons.utils.ViewUtil;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import timber.log.Timber;
/**
* This class is meant to handle the Wikidata edits made through the app
* It will talk with MediaWikiApi to make necessary API calls, log the edits and fire listeners
* on successful edits
*/
@Singleton
public class WikidataEditService {
private final Context context;
private final MediaWikiApi mediaWikiApi;
private final WikidataEditListener wikidataEditListener;
private final SharedPreferences directPrefs;
@Inject
public WikidataEditService(Context context,
MediaWikiApi mediaWikiApi,
WikidataEditListener wikidataEditListener,
@Named("direct_nearby_upload_prefs") SharedPreferences directPrefs) {
this.context = context;
this.mediaWikiApi = mediaWikiApi;
this.wikidataEditListener = wikidataEditListener;
this.directPrefs = directPrefs;
}
/**
* Create a P18 claim and log the edit with custom tag
* @param wikidataEntityId
* @param fileName
*/
public void createClaimWithLogging(String wikidataEntityId, String fileName) {
if(wikidataEntityId == null
|| fileName == null) {
return;
}
editWikidataProperty(wikidataEntityId, fileName);
}
/**
* Edits the wikidata entity by adding the P18 property to it.
* Adding the P18 edit requires calling the wikidata API to create a claim against the entity
*
* @param wikidataEntityId
* @param fileName
*/
@SuppressLint("CheckResult")
private void editWikidataProperty(String wikidataEntityId, String fileName) {
Timber.d("Upload successful with wiki data entity id as %s", wikidataEntityId);
Timber.d("Attempting to edit Wikidata property %s", wikidataEntityId);
Observable.fromCallable(() -> {
String propertyValue = getFileName(fileName);
return mediaWikiApi.wikidatCreateClaim(wikidataEntityId, "P18", "value", propertyValue);
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(revisionId -> handleClaimResult(wikidataEntityId, revisionId), throwable -> {
Timber.e(throwable, "Error occurred while making claim");
ViewUtil.showLongToast(context, context.getString(R.string.wikidata_edit_failure));
});
}
private void handleClaimResult(String wikidataEntityId, String revisionId) {
if (revisionId != null) {
wikidataEditListener.onSuccessfulWikidataEdit();
showSuccessToast();
logEdit(revisionId);
} else {
Timber.d("Unable to make wiki data edit for entity %s", wikidataEntityId);
ViewUtil.showLongToast(context, context.getString(R.string.wikidata_edit_failure));
}
}
/**
* Log the Wikidata edit by adding Wikimedia Commons App tag to the edit
* @param revisionId
*/
@SuppressLint("CheckResult")
private void logEdit(String revisionId) {
Observable.fromCallable(() -> mediaWikiApi.addWikidataEditTag(revisionId))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(result -> {
if (result) {
Timber.d("Wikidata edit was tagged successfully");
} else {
Timber.d("Wikidata edit couldn't be tagged");
}
}, throwable -> {
Timber.e(throwable, "Error occurred while adding tag to the edit");
});
}
/**
* Show a success toast when the edit is made successfully
*/
private void showSuccessToast() {
String title = directPrefs.getString("Title", "");
String successStringTemplate = context.getString(R.string.successful_wikidata_edit);
String successMessage = String.format(Locale.getDefault(), successStringTemplate, title);
ViewUtil.showLongToast(context, successMessage);
}
/**
* Formats and returns the filename as accepted by the wiki base API
* https://www.mediawiki.org/wiki/Wikibase/API#wbcreateclaim
*
* @param fileName
* @return
*/
private String getFileName(String fileName) {
fileName = String.format("\"%s\"", fileName.replace("File:", ""));
Timber.d("Wikidata property name is %s", fileName);
return fileName;
}
}

View file

@ -0,0 +1,25 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black"
android:padding="@dimen/widget_margin"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/white"
android:textSize="20sp"
android:gravity="center"
android:layout_marginTop="10dp"
android:text="@string/app_widget_heading"/>
<ImageView
android:padding="15dp"
android:id="@+id/appwidget_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="@string/appwidget_img" />
</LinearLayout>

View file

@ -15,6 +15,10 @@
android:id="@+id/menu_download_current_image" android:id="@+id/menu_download_current_image"
android:title="@string/menu_download" android:title="@string/menu_download"
app:showAsAction="never" /> app:showAsAction="never" />
<item
android:id="@+id/menu_set_as_wallpaper"
android:title="@string/menu_set_wallpaper"
app:showAsAction="never" />
<item <item
android:id="@+id/menu_retry_current_image" android:id="@+id/menu_retry_current_image"
android:enabled="false" android:enabled="false"

View file

@ -14,7 +14,7 @@
<string name="login_success">Аҭалара қәҿиарала имҩаҧысит!</string> <string name="login_success">Аҭалара қәҿиарала имҩаҧысит!</string>
<string name="login_failed">Асистемахь аҭалараан агха!</string> <string name="login_failed">Асистемахь аҭалараан агха!</string>
<string name="upload_failed">Афаил ҧшаам. Даҽа фаилк шәахәаҧш.</string> <string name="upload_failed">Афаил ҧшаам. Даҽа фаилк шәахәаҧш.</string>
<string name="authentication_failed">Аутентификациа агха!</string> <string name="authentication_failed" fuzzy="true">Аутентификациа агха!</string>
<string name="uploading_started">Аҭагалара иалагоуп!</string> <string name="uploading_started">Аҭагалара иалагоуп!</string>
<string name="upload_completed_notification_title">%1$s иҭагалоуп!</string> <string name="upload_completed_notification_title">%1$s иҭагалоуп!</string>
<string name="upload_completed_notification_text">Шәақәыӷәӷәа иҭагалоу афаил ахәаҧшраз</string> <string name="upload_completed_notification_text">Шәақәыӷәӷәа иҭагалоу афаил ахәаҧшраз</string>

View file

@ -27,7 +27,7 @@
<string name="login_success">تم الدخول بشكل صحيح!</string> <string name="login_success">تم الدخول بشكل صحيح!</string>
<string name="login_failed">فشل تسجيل الدخول</string> <string name="login_failed">فشل تسجيل الدخول</string>
<string name="upload_failed">الملف غير موجود. فضلا اختر ملفا آخر.</string> <string name="upload_failed">الملف غير موجود. فضلا اختر ملفا آخر.</string>
<string name="authentication_failed">فشل الاستيقان!</string> <string name="authentication_failed" fuzzy="true">فشل الاستيقان!</string>
<string name="uploading_started">بدأ الرفع!</string> <string name="uploading_started">بدأ الرفع!</string>
<string name="upload_completed_notification_title">رُفع %1$s!</string> <string name="upload_completed_notification_title">رُفع %1$s!</string>
<string name="upload_completed_notification_text">انقر لعرض ملفك المرفوع</string> <string name="upload_completed_notification_text">انقر لعرض ملفك المرفوع</string>
@ -54,8 +54,6 @@
<string name="share_title_hint">العنوان</string> <string name="share_title_hint">العنوان</string>
<string name="share_description_hint">الوصف</string> <string name="share_description_hint">الوصف</string>
<string name="login_failed_network">لا يمكن تسجيل الدخول - فشل في شبكة الاتصال</string> <string name="login_failed_network">لا يمكن تسجيل الدخول - فشل في شبكة الاتصال</string>
<string name="login_failed_username">لا يمكن تسجيل الدخول - فضلا تحقق من اسم المستخدم</string>
<string name="login_failed_password">لا يمكن تسجيل الدخول - فضلا تحقق من كلمة السر</string>
<string name="login_failed_throttled">الكثير من المحاولات غير الناجحة. الرجاء المحاولة مرة أخرى في بضع دقائق.</string> <string name="login_failed_throttled">الكثير من المحاولات غير الناجحة. الرجاء المحاولة مرة أخرى في بضع دقائق.</string>
<string name="login_failed_blocked">عذراً، لقد تم منع هذا المستخدم على كومنز</string> <string name="login_failed_blocked">عذراً، لقد تم منع هذا المستخدم على كومنز</string>
<string name="login_failed_2fa_needed">يجب توفير رمز التحقق المزدوج.</string> <string name="login_failed_2fa_needed">يجب توفير رمز التحقق المزدوج.</string>

View file

@ -21,7 +21,7 @@
<string name="login_success">¡Identificación correuta!</string> <string name="login_success">¡Identificación correuta!</string>
<string name="login_failed">¡Falló l\'aniciu de sesión!</string> <string name="login_failed">¡Falló l\'aniciu de sesión!</string>
<string name="upload_failed">Nun s\'alcontró\'l ficheru. Tenta con otru.</string> <string name="upload_failed">Nun s\'alcontró\'l ficheru. Tenta con otru.</string>
<string name="authentication_failed">¡Falló la identificación!</string> <string name="authentication_failed">Falló la identificación, anicia sesión nuevamente</string>
<string name="uploading_started">Principió la xuba</string> <string name="uploading_started">Principió la xuba</string>
<string name="upload_completed_notification_title">¡%1$s xubíu!</string> <string name="upload_completed_notification_title">¡%1$s xubíu!</string>
<string name="upload_completed_notification_text">Toque pa ver la xuba</string> <string name="upload_completed_notification_text">Toque pa ver la xuba</string>
@ -49,8 +49,7 @@
<string name="add_title_toast">Apurre un títulu pa esti ficheru</string> <string name="add_title_toast">Apurre un títulu pa esti ficheru</string>
<string name="share_description_hint">Descripción</string> <string name="share_description_hint">Descripción</string>
<string name="login_failed_network">Nun se pudo aniciar sesión error de rede</string> <string name="login_failed_network">Nun se pudo aniciar sesión error de rede</string>
<string name="login_failed_username">Nun se pudo aniciar sesión por favor compruebe\'l so nome d\'usuariu</string> <string name="login_failed_wrong_credentials">Nun pudo aniciase sesión. Revisa\'l nome d\'usuariu y la contraseña</string>
<string name="login_failed_password">Nun se pudo aniciar sesión por favor compruebe la so contraseña</string>
<string name="login_failed_throttled">Demasiaos intentos incorreutos. Téntalo otra vuelta n\'unos minutos.</string> <string name="login_failed_throttled">Demasiaos intentos incorreutos. Téntalo otra vuelta n\'unos minutos.</string>
<string name="login_failed_blocked">Sentímoslo, esti usuariu ta bloquiáu en Commons</string> <string name="login_failed_blocked">Sentímoslo, esti usuariu ta bloquiáu en Commons</string>
<string name="login_failed_2fa_needed">Tienes de dar el códigu d\'identificación de dos factores.</string> <string name="login_failed_2fa_needed">Tienes de dar el códigu d\'identificación de dos factores.</string>
@ -84,6 +83,7 @@
<string name="categories_activity_title">Categoríes</string> <string name="categories_activity_title">Categoríes</string>
<string name="title_activity_settings">Configuración</string> <string name="title_activity_settings">Configuración</string>
<string name="title_activity_signup">Date d\'alta</string> <string name="title_activity_signup">Date d\'alta</string>
<string name="title_activity_featured_images">Imáxenes destacaes</string>
<string name="menu_about">Tocante a</string> <string name="menu_about">Tocante a</string>
<string name="about_license">La app de Wikimedia Commons ye software de códigu abiertu, creáu y calteníu por becaos y voluntarios de la comunidá de Wikimedia. La Fundación Wikimedia nun participa na creación, desendolcu nin caltenimientu de la app.</string> <string name="about_license">La app de Wikimedia Commons ye software de códigu abiertu, creáu y calteníu por becaos y voluntarios de la comunidá de Wikimedia. La Fundación Wikimedia nun participa na creación, desendolcu nin caltenimientu de la app.</string>
<string name="about_improve">Crea una nueva &lt;a href=\"https://github.com/commons-app/apps-android-commons/issues\"&gt;incidencia en GitHub&lt;/a&gt; pa informar de problemes y suxerencies.</string> <string name="about_improve">Crea una nueva &lt;a href=\"https://github.com/commons-app/apps-android-commons/issues\"&gt;incidencia en GitHub&lt;/a&gt; pa informar de problemes y suxerencies.</string>
@ -103,7 +103,7 @@
<string name="preference_license">Llicencia predeterminada</string> <string name="preference_license">Llicencia predeterminada</string>
<string name="use_previous">Usar un títulu/descripción anterior</string> <string name="use_previous">Usar un títulu/descripción anterior</string>
<string name="allow_gps">Llograr automáticamente l\'allugamientu actual</string> <string name="allow_gps">Llograr automáticamente l\'allugamientu actual</string>
<string name="allow_gps_summary">Recuperar l\'allugamientu actual pa ufiertar suxerencies de categoríes si la imaxe nun tien etiquetes xeográfiques</string> <string name="allow_gps_summary">Recupera la posición actual si la imaxe nun tien etiquetes xeográfiques, y marca la imaxe con ella. Atención: Esto revelará\'l to allugamientu actual.</string>
<string name="preference_theme">Mou nocherniegu</string> <string name="preference_theme">Mou nocherniegu</string>
<string name="preference_theme_summary">Usar tema escuru</string> <string name="preference_theme_summary">Usar tema escuru</string>
<string name="license_name_cc_by_sa_four"> Reconocimientu-CompartirIgual 4.0</string> <string name="license_name_cc_by_sa_four"> Reconocimientu-CompartirIgual 4.0</string>
@ -169,6 +169,8 @@
<string name="media_detail_media_title">Títulu del mediu</string> <string name="media_detail_media_title">Títulu del mediu</string>
<string name="media_detail_description">Descripción</string> <string name="media_detail_description">Descripción</string>
<string name="media_detail_description_explanation">Equí va la descripción del mediu. Esto pué ser llargo enforma, y necesitará espardese per delles llinies. Sicasí, esperamos que se vea bien.</string> <string name="media_detail_description_explanation">Equí va la descripción del mediu. Esto pué ser llargo enforma, y necesitará espardese per delles llinies. Sicasí, esperamos que se vea bien.</string>
<string name="media_detail_author">Autor</string>
<string name="media_detail_author_explanation">El nome d\'usuariu del autor de la imaxe destacada va equí.</string>
<string name="media_detail_uploaded_date">Data d\'unviu</string> <string name="media_detail_uploaded_date">Data d\'unviu</string>
<string name="media_detail_license">Llicencia</string> <string name="media_detail_license">Llicencia</string>
<string name="media_detail_coordinates">Coordenaes</string> <string name="media_detail_coordinates">Coordenaes</string>
@ -211,6 +213,7 @@
<string name="navigation_item_logout">Salir</string> <string name="navigation_item_logout">Salir</string>
<string name="navigation_item_info">Tutorial</string> <string name="navigation_item_info">Tutorial</string>
<string name="navigation_item_notification">Avisos</string> <string name="navigation_item_notification">Avisos</string>
<string name="navigation_item_featured_images">Destacada</string>
<string name="nearby_needs_permissions">Los sitios cercanos nun pueden amosase ensin los permisos d\'allugamientu</string> <string name="nearby_needs_permissions">Los sitios cercanos nun pueden amosase ensin los permisos d\'allugamientu</string>
<string name="no_description_found">nun s\'atoparon descripciones</string> <string name="no_description_found">nun s\'atoparon descripciones</string>
<string name="nearby_info_menu_commons_article">Páxina del ficheru en Commons</string> <string name="nearby_info_menu_commons_article">Páxina del ficheru en Commons</string>
@ -259,4 +262,20 @@
<string name="about_translate_proceed">Siguir</string> <string name="about_translate_proceed">Siguir</string>
<string name="about_translate_cancel">Encaboxar</string> <string name="about_translate_cancel">Encaboxar</string>
<string name="retry">Retentar</string> <string name="retry">Retentar</string>
<string name="showcase_view_got_it_button">Entendílo</string>
<string name="showcase_view_whole_nearby_activity">Estos son sitios cercanos a ti que precisen imaxes para ilustrar los sos artículos de Wikipedia</string>
<string name="showcase_view_list_icon">Tocando esti botón amuésase la llista d\'esos llugares</string>
<string name="showcase_view_plus_fab">Puedes xubir una imaxe pa cualquier sitiu dende la galería o la cámara</string>
<string name="no_images_found">Nun s\'alcontró nenguna imaxe</string>
<string name="error_loading_images">Asocedió un error al cargar les imáxenes.</string>
<string name="image_uploaded_by">Xubida por: %1$s</string>
<string name="share_app_title">Compartir app</string>
<string name="share_coordinates_not_present">Nun s\'especificaron les coordenaes al escoyer la imaxe</string>
<string name="error_fetching_nearby_places">Error al llograr los llugares cercanos.</string>
<string name="appwidget_img">Semeya del día</string>
<string name="app_widget_heading">Semeya del día</string>
<string name="successful_wikidata_edit">Añadióse correutamente la imaxe a %1$s en Wikidata.</string>
<string name="wikidata_edit_failure">Nun pudo anovase la entidá de Wikidata correspondiente.</string>
<string name="menu_set_wallpaper">Definir fondu</string>
<string name="wallpaper_set_successfully">Fondu definíu correutamente</string>
</resources> </resources>

View file

@ -16,7 +16,7 @@
<string name="login_success">Uspešno ste prijavljeni.</string> <string name="login_success">Uspešno ste prijavljeni.</string>
<string name="login_failed">Prijavljivanje nije uspelo.</string> <string name="login_failed">Prijavljivanje nije uspelo.</string>
<string name="upload_failed">Datoteka nije pronađena. Pokušajte sa drugom datotekom.</string> <string name="upload_failed">Datoteka nije pronađena. Pokušajte sa drugom datotekom.</string>
<string name="authentication_failed">Provera identiteta nije uspela.</string> <string name="authentication_failed" fuzzy="true">Provera identiteta nije uspela.</string>
<string name="uploading_started">Otpremanje je započeto.</string> <string name="uploading_started">Otpremanje je započeto.</string>
<string name="upload_completed_notification_title">Datoteka „%1$s“ je otpremljena.</string> <string name="upload_completed_notification_title">Datoteka „%1$s“ je otpremljena.</string>
<string name="upload_completed_notification_text">Tapnite da biste videli otpremanje</string> <string name="upload_completed_notification_text">Tapnite da biste videli otpremanje</string>
@ -43,8 +43,6 @@
<string name="share_title_hint">Naslov</string> <string name="share_title_hint">Naslov</string>
<string name="share_description_hint">Opis</string> <string name="share_description_hint">Opis</string>
<string name="login_failed_network">Ne mogu da vas prijavim mreža ne radi</string> <string name="login_failed_network">Ne mogu da vas prijavim mreža ne radi</string>
<string name="login_failed_username">Ne mogu da vas prijavim proverite svoje korisničko ime</string>
<string name="login_failed_password">Ne mogu da vas prijavim proverite svoju lozinku</string>
<string name="login_failed_throttled">Previše neuspešnih pokušaja. Probajte ponovo za nekoliko minuta.</string> <string name="login_failed_throttled">Previše neuspešnih pokušaja. Probajte ponovo za nekoliko minuta.</string>
<string name="login_failed_blocked">Nažalost, korisnik je blokiran na Ostavi</string> <string name="login_failed_blocked">Nažalost, korisnik je blokiran na Ostavi</string>
<string name="login_failed_2fa_needed">Morate uneti svoj dvofaktorski kod za autentifikaciju.</string> <string name="login_failed_2fa_needed">Morate uneti svoj dvofaktorski kod za autentifikaciju.</string>
@ -96,7 +94,7 @@
<string name="preference_license" fuzzy="true">Licenca</string> <string name="preference_license" fuzzy="true">Licenca</string>
<string name="use_previous">Koristi prethodan naslov/opis</string> <string name="use_previous">Koristi prethodan naslov/opis</string>
<string name="allow_gps">Automatski detektuj trenutnu lokaciju</string> <string name="allow_gps">Automatski detektuj trenutnu lokaciju</string>
<string name="allow_gps_summary">Primi trenutnu lokaciju da bi predložili kategoriju ako slika nije geografski označena</string> <string name="allow_gps_summary" fuzzy="true">Primi trenutnu lokaciju da bi predložili kategoriju ako slika nije geografski označena</string>
<string name="preference_theme">Noćni režim</string> <string name="preference_theme">Noćni režim</string>
<string name="preference_theme_summary">Koristiti tamnu temu</string> <string name="preference_theme_summary">Koristiti tamnu temu</string>
<string name="license_name_cc_by_sa_four">Autorstvo-Deliti pod istim uslovima 4.0</string> <string name="license_name_cc_by_sa_four">Autorstvo-Deliti pod istim uslovima 4.0</string>

View file

@ -23,7 +23,7 @@
<string name="login_success">Танышыу уңышлы үтте</string> <string name="login_success">Танышыу уңышлы үтте</string>
<string name="login_failed">Танылыу хатаһы</string> <string name="login_failed">Танылыу хатаһы</string>
<string name="upload_failed">Файл табылманы. Башҡа файлды эҙлә.</string> <string name="upload_failed">Файл табылманы. Башҡа файлды эҙлә.</string>
<string name="authentication_failed">Кем икәнегеҙ танылманы!</string> <string name="authentication_failed" fuzzy="true">Кем икәнегеҙ танылманы!</string>
<string name="uploading_started">Тейәү башланды!</string> <string name="uploading_started">Тейәү башланды!</string>
<string name="upload_completed_notification_title">%1$s тейәлде!</string> <string name="upload_completed_notification_title">%1$s тейәлде!</string>
<string name="upload_completed_notification_text">Ошонда баҫып тейәлгән файлды ҡара</string> <string name="upload_completed_notification_text">Ошонда баҫып тейәлгән файлды ҡара</string>
@ -50,8 +50,6 @@
<string name="add_title_toast">Был файлдың атамаһын күрһәт</string> <string name="add_title_toast">Был файлдың атамаһын күрһәт</string>
<string name="share_description_hint">Тасуирлама</string> <string name="share_description_hint">Тасуирлама</string>
<string name="login_failed_network">Инеп булмай - интернет хатаһы</string> <string name="login_failed_network">Инеп булмай - интернет хатаһы</string>
<string name="login_failed_username">Инмәнең - ҡулланыусы исемеңде тикшер</string>
<string name="login_failed_password">Инмәнең - серһуҙеңде тикшер</string>
<string name="login_failed_throttled">Күп тапҡыр яңылыштың. Зинһар, бер-нисә минуттан тағы ла инеп ҡара</string> <string name="login_failed_throttled">Күп тапҡыр яңылыштың. Зинһар, бер-нисә минуттан тағы ла инеп ҡара</string>
<string name="login_failed_blocked">Ғәфү итегеҙ, әммә был исемдәге ҡатнашыусыға Викискладҡа инеү тыйылған</string> <string name="login_failed_blocked">Ғәфү итегеҙ, әммә был исемдәге ҡатнашыусыға Викискладҡа инеү тыйылған</string>
<string name="login_failed_2fa_needed">Ике тапҡыр раҫлай торған шәхси кодты яҙырға кәрәк</string> <string name="login_failed_2fa_needed">Ике тапҡыр раҫлай торған шәхси кодты яҙырға кәрәк</string>
@ -102,7 +100,7 @@
<string name="preference_license">Нығытылған рөхсәтнамә</string> <string name="preference_license">Нығытылған рөхсәтнамә</string>
<string name="use_previous">Алдағы атама/һәрәтләмәне ҡулланыу</string> <string name="use_previous">Алдағы атама/һәрәтләмәне ҡулланыу</string>
<string name="allow_gps">Автомат рәүешендә сираттағы урынды алыу</string> <string name="allow_gps">Автомат рәүешендә сираттағы урынды алыу</string>
<string name="allow_gps_summary">Әгәр рәсемдең геотегтары булмаһа, категориялар үҙенән-үҙе тәҡдим ителһен өсөн сираттағы урынды алырға</string> <string name="allow_gps_summary" fuzzy="true">Әгәр рәсемдең геотегтары булмаһа, категориялар үҙенән-үҙе тәҡдим ителһен өсөн сираттағы урынды алырға</string>
<string name="preference_theme">Төнгө режим</string> <string name="preference_theme">Төнгө режим</string>
<string name="preference_theme_summary">Ҡараңғы теманы ҡулланыу</string> <string name="preference_theme_summary">Ҡараңғы теманы ҡулланыу</string>
<string name="license_name_cc_by_sa_four"> Attribution-ShareAlike 4.0</string> <string name="license_name_cc_by_sa_four"> Attribution-ShareAlike 4.0</string>

View file

@ -18,7 +18,7 @@
<string name="login_success">Успешно влизане.</string> <string name="login_success">Успешно влизане.</string>
<string name="login_failed">Неуспешно влизане!</string> <string name="login_failed">Неуспешно влизане!</string>
<string name="upload_failed">Файлът не е намерен. Моля, опитайте с друг файл.</string> <string name="upload_failed">Файлът не е намерен. Моля, опитайте с друг файл.</string>
<string name="authentication_failed">Неуспешен опит за удостоверяване!</string> <string name="authentication_failed" fuzzy="true">Неуспешен опит за удостоверяване!</string>
<string name="uploading_started">Качването започна!</string> <string name="uploading_started">Качването започна!</string>
<string name="upload_completed_notification_title">Файл %1$s е качен!</string> <string name="upload_completed_notification_title">Файл %1$s е качен!</string>
<string name="upload_completed_notification_text">Докоснете, за да видите качения файл</string> <string name="upload_completed_notification_text">Докоснете, за да видите качения файл</string>
@ -32,8 +32,6 @@
<string name="share_title_hint">Заглавие</string> <string name="share_title_hint">Заглавие</string>
<string name="share_description_hint">Описание</string> <string name="share_description_hint">Описание</string>
<string name="login_failed_network">Неуспешно влизане проблем в мрежата</string> <string name="login_failed_network">Неуспешно влизане проблем в мрежата</string>
<string name="login_failed_username">Неуспешно влизане моля проверете потребителското си име</string>
<string name="login_failed_password">Неуспешно влизане моля проверете паролата си</string>
<string name="share_upload_button">Качване</string> <string name="share_upload_button">Качване</string>
<string name="provider_modifications">Изменения</string> <string name="provider_modifications">Изменения</string>
<string name="menu_upload_single">Качване</string> <string name="menu_upload_single">Качване</string>

View file

@ -3,6 +3,7 @@
* Aftabuzzaman * Aftabuzzaman
* Bellayet * Bellayet
* Sankarshan * Sankarshan
* আফতাবুজ্জামান
--> -->
<resources> <resources>
<string name="crash_dialog_title">কমন্স ক্র্যাশ করেছে</string> <string name="crash_dialog_title">কমন্স ক্র্যাশ করেছে</string>

View file

@ -9,6 +9,7 @@
* Rasal Lia * Rasal Lia
* Sankarshan * Sankarshan
* Tauhid16 * Tauhid16
* আফতাবুজ্জামান
--> -->
<resources> <resources>
<string name="preference_category_appearance">অবয়ব</string> <string name="preference_category_appearance">অবয়ব</string>
@ -29,7 +30,7 @@
<string name="login_success">প্রবেশ সফল!</string> <string name="login_success">প্রবেশ সফল!</string>
<string name="login_failed">প্রবেশ ব্যর্থ :(</string> <string name="login_failed">প্রবেশ ব্যর্থ :(</string>
<string name="upload_failed">ফাইল পাওয়া যায়নি। আরেকটি ফাইল চেষ্টা করুন।</string> <string name="upload_failed">ফাইল পাওয়া যায়নি। আরেকটি ফাইল চেষ্টা করুন।</string>
<string name="authentication_failed">প্রমাণীকরণ ব্যর্থ হয়েছে!</string> <string name="authentication_failed">প্রমাণীকরণ ব্যর্থ হয়েছে, আবার প্রবেশ করুন</string>
<string name="uploading_started"> আপলোড আরম্ভ হয়েছে!</string> <string name="uploading_started"> আপলোড আরম্ভ হয়েছে!</string>
<string name="upload_completed_notification_title">%1$s আপলোড হয়েছে!</string> <string name="upload_completed_notification_title">%1$s আপলোড হয়েছে!</string>
<string name="upload_completed_notification_text">আপনার আপলোড দেখতে টোকা দিন</string> <string name="upload_completed_notification_text">আপনার আপলোড দেখতে টোকা দিন</string>
@ -57,8 +58,6 @@
<string name="add_title_toast">এই ফাইলটির জন্য একটি শিরোনাম প্রদান করুন</string> <string name="add_title_toast">এই ফাইলটির জন্য একটি শিরোনাম প্রদান করুন</string>
<string name="share_description_hint">বিবরণ</string> <string name="share_description_hint">বিবরণ</string>
<string name="login_failed_network">প্রবেশ করা যাচ্ছে না - নেটওয়ার্ক ব্যর্থতা</string> <string name="login_failed_network">প্রবেশ করা যাচ্ছে না - নেটওয়ার্ক ব্যর্থতা</string>
<string name="login_failed_username">প্রবেশ করা যাচ্ছে না - অনুগ্রহ করে আপনার ব্যবহারকারী নাম পরীক্ষা করুন।</string>
<string name="login_failed_password">প্রবেশ করা যাচ্ছে না - অনুগ্রহ করে আপনার পাসওয়ার্ড পরীক্ষা করুন</string>
<string name="login_failed_throttled">খুব বেশি অসফল প্রচেষ্টা। অনুগ্রহ করে কয়েক মিনিট পরে আবারও চেষ্টা করুন।</string> <string name="login_failed_throttled">খুব বেশি অসফল প্রচেষ্টা। অনুগ্রহ করে কয়েক মিনিট পরে আবারও চেষ্টা করুন।</string>
<string name="login_failed_blocked">দুঃখিত, এই ব্যবহারকারীকে কমন্সে বাধা দেয়া হয়েছে</string> <string name="login_failed_blocked">দুঃখিত, এই ব্যবহারকারীকে কমন্সে বাধা দেয়া হয়েছে</string>
<string name="login_failed_2fa_needed">অাপনাকে অবশ্যই অাপনার দু\'স্তরের সত্যায়নকরণ কোড দিতে হবে।</string> <string name="login_failed_2fa_needed">অাপনাকে অবশ্যই অাপনার দু\'স্তরের সত্যায়নকরণ কোড দিতে হবে।</string>
@ -112,7 +111,7 @@
<string name="preference_license">পূর্বনির্ধারিত লাইসেন্স</string> <string name="preference_license">পূর্বনির্ধারিত লাইসেন্স</string>
<string name="use_previous">পূর্ববর্তী শিরোনাম/বিবরণ ব্যবহার করুন</string> <string name="use_previous">পূর্ববর্তী শিরোনাম/বিবরণ ব্যবহার করুন</string>
<string name="allow_gps">স্বয়ংক্রিয়ভাবে বর্তমান অবস্থান পান</string> <string name="allow_gps">স্বয়ংক্রিয়ভাবে বর্তমান অবস্থান পান</string>
<string name="allow_gps_summary">বিষয়শ্রেণীর পরামর্শ দিতে বর্তমান অবস্থান পান যদি ছবিতে ভূ-ট্যাগ না থেকে থাকে</string> <string name="allow_gps_summary" fuzzy="true">বিষয়শ্রেণীর পরামর্শ দিতে বর্তমান অবস্থান পান যদি ছবিতে ভূ-ট্যাগ না থেকে থাকে</string>
<string name="preference_theme">রাত্রি মোড</string> <string name="preference_theme">রাত্রি মোড</string>
<string name="preference_theme_summary">কালো থিম ব্যবহার করুন</string> <string name="preference_theme_summary">কালো থিম ব্যবহার করুন</string>
<string name="license_name_cc_by_sa_four">অ্যাট্রিবিউশন-শেয়ারঅ্যালাইক .</string> <string name="license_name_cc_by_sa_four">অ্যাট্রিবিউশন-শেয়ারঅ্যালাইক .</string>
@ -262,4 +261,5 @@
<string name="showcase_view_got_it_button">বুঝেছি!</string> <string name="showcase_view_got_it_button">বুঝেছি!</string>
<string name="no_images_found">কোন চিত্র পাওয়া যায়নি!</string> <string name="no_images_found">কোন চিত্র পাওয়া যায়নি!</string>
<string name="image_uploaded_by">আপলোড করেছেন: %1$s</string> <string name="image_uploaded_by">আপলোড করেছেন: %1$s</string>
<string name="error_fetching_nearby_places">কাছাকাছি স্থানগুলি আনতে ত্রুটি।</string>
</resources> </resources>

View file

@ -3,6 +3,7 @@
* Dishual * Dishual
* Fohanno * Fohanno
* Fulup * Fulup
* Gwendal
* Gwenn-Ael * Gwenn-Ael
* Y-M D * Y-M D
--> -->
@ -24,7 +25,7 @@
<string name="login_success">Kevreet oc\'h !</string> <string name="login_success">Kevreet oc\'h !</string>
<string name="login_failed">Kudenn gevreañ !</string> <string name="login_failed">Kudenn gevreañ !</string>
<string name="upload_failed">N\'eo ket bet kavet ar restr. Klask gant unan all.</string> <string name="upload_failed">N\'eo ket bet kavet ar restr. Klask gant unan all.</string>
<string name="authentication_failed">Dilesadur c\'hwitet!</string> <string name="authentication_failed" fuzzy="true">Dilesadur c\'hwitet!</string>
<string name="uploading_started">Kroget da enporzhiañ!</string> <string name="uploading_started">Kroget da enporzhiañ!</string>
<string name="upload_completed_notification_title">%1$s bet enporzhiet !</string> <string name="upload_completed_notification_title">%1$s bet enporzhiet !</string>
<string name="upload_completed_notification_text">Pouezit evit gwelet hoc\'h enporzhiadenn</string> <string name="upload_completed_notification_text">Pouezit evit gwelet hoc\'h enporzhiadenn</string>
@ -52,8 +53,6 @@
<string name="add_title_toast">Roit un titl d\'ar restr-mañ, mar plij</string> <string name="add_title_toast">Roit un titl d\'ar restr-mañ, mar plij</string>
<string name="share_description_hint">Deskrivadur</string> <string name="share_description_hint">Deskrivadur</string>
<string name="login_failed_network">Ne c\'haller ket kevreañ - rouedad sac\'het</string> <string name="login_failed_network">Ne c\'haller ket kevreañ - rouedad sac\'het</string>
<string name="login_failed_username">Ne c\'haller ket kevreañ - Gwiriit hoc\'h anv implijer, mar plij</string>
<string name="login_failed_password">Ne c\'haller ket kevreañ - Gwiriit ho ker tremen, mar plij</string>
<string name="login_failed_throttled">Re a daolioù-esae. Klaskit en-dro a-benn ur pennadig amzer.</string> <string name="login_failed_throttled">Re a daolioù-esae. Klaskit en-dro a-benn ur pennadig amzer.</string>
<string name="login_failed_blocked">Hon digarezit, prennet eo bet an implijer-mañ e Commons</string> <string name="login_failed_blocked">Hon digarezit, prennet eo bet an implijer-mañ e Commons</string>
<string name="login_failed_2fa_needed">Rankout a rit reiñ ho kod dilesa gant daou faktor.</string> <string name="login_failed_2fa_needed">Rankout a rit reiñ ho kod dilesa gant daou faktor.</string>
@ -104,7 +103,7 @@
<string name="preference_license">Aotre-implijout dre ziouer</string> <string name="preference_license">Aotre-implijout dre ziouer</string>
<string name="use_previous">Ober gant an titl/deskrivadur kent</string> <string name="use_previous">Ober gant an titl/deskrivadur kent</string>
<string name="allow_gps">Tapout al lec\'hiadur red ent emgefre</string> <string name="allow_gps">Tapout al lec\'hiadur red ent emgefre</string>
<string name="allow_gps_summary">Kavout al lec\'hiadur red evit pourchas kinnigoù rummadoù ma n\'eo ket douarlec\'hiet ar skeudenn.</string> <string name="allow_gps_summary" fuzzy="true">Kavout al lec\'hiadur red evit pourchas kinnigoù rummadoù ma n\'eo ket douarlec\'hiet ar skeudenn.</string>
<string name="preference_theme">Mod noz</string> <string name="preference_theme">Mod noz</string>
<string name="preference_theme_summary">Ober gant an tem teñval</string> <string name="preference_theme_summary">Ober gant an tem teñval</string>
<string name="license_name_cc_by_sa_four">Deroadur-RannañHeñvel 4.0</string> <string name="license_name_cc_by_sa_four">Deroadur-RannañHeñvel 4.0</string>
@ -219,4 +218,5 @@
<string name="nearby_location_has_not_changed">N\'eo ket cheñchet al lec\'hiadur.</string> <string name="nearby_location_has_not_changed">N\'eo ket cheñchet al lec\'hiadur.</string>
<string name="get_directions">Kaout urzhioù</string> <string name="get_directions">Kaout urzhioù</string>
<string name="read_article">Lenn ar pennad</string> <string name="read_article">Lenn ar pennad</string>
<string name="appwidget_img">Skeudenn an deiz</string>
</resources> </resources>

View file

@ -16,7 +16,7 @@
<string name="login_success">Prijavljivanje uspješno!</string> <string name="login_success">Prijavljivanje uspješno!</string>
<string name="login_failed">Prijavljivanje nije uspjelo!</string> <string name="login_failed">Prijavljivanje nije uspjelo!</string>
<string name="upload_failed">Datoteka nije pronađena. Pokušajte drugu.</string> <string name="upload_failed">Datoteka nije pronađena. Pokušajte drugu.</string>
<string name="authentication_failed">Provjera identiteta nije uspjela!</string> <string name="authentication_failed" fuzzy="true">Provjera identiteta nije uspjela!</string>
<string name="uploading_started">Postavljanje je započelo!</string> <string name="uploading_started">Postavljanje je započelo!</string>
<string name="upload_completed_notification_title">Datoteka %1$s je postavljena!</string> <string name="upload_completed_notification_title">Datoteka %1$s je postavljena!</string>
<string name="upload_completed_notification_text">Dodirnite da biste vidjeli datoteku</string> <string name="upload_completed_notification_text">Dodirnite da biste vidjeli datoteku</string>
@ -43,8 +43,6 @@
<string name="share_title_hint">Naslov</string> <string name="share_title_hint">Naslov</string>
<string name="share_description_hint">Opis</string> <string name="share_description_hint">Opis</string>
<string name="login_failed_network">Ne mogu Vas prijaviti mreža ne radi</string> <string name="login_failed_network">Ne mogu Vas prijaviti mreža ne radi</string>
<string name="login_failed_username">Ne mogu Vas prijaviti provjerite svoje korisničko ime</string>
<string name="login_failed_password">Ne mogu Vas prijaviti provjerite svoje lozinku</string>
<string name="login_failed_throttled">Napravili ste previše grešaka u prijavi. Pokušajte ponovo za nekoliko minuta.</string> <string name="login_failed_throttled">Napravili ste previše grešaka u prijavi. Pokušajte ponovo za nekoliko minuta.</string>
<string name="login_failed_blocked">Žao nam je, korisnik je blokiran na Commonsu</string> <string name="login_failed_blocked">Žao nam je, korisnik je blokiran na Commonsu</string>
<string name="login_failed_2fa_needed">Morate upisati kôd za potvrdu u 2 koraka.</string> <string name="login_failed_2fa_needed">Morate upisati kôd za potvrdu u 2 koraka.</string>
@ -95,7 +93,7 @@
<string name="preference_license" fuzzy="true">Licenca</string> <string name="preference_license" fuzzy="true">Licenca</string>
<string name="use_previous">Koristi prethodni naziv/opis</string> <string name="use_previous">Koristi prethodni naziv/opis</string>
<string name="allow_gps">Automatski dobavi trenutnu lokaciju</string> <string name="allow_gps">Automatski dobavi trenutnu lokaciju</string>
<string name="allow_gps_summary">Dobavi trenutnu lokaciju za davanje prijedloga o kategorijama ako nema geooznaku</string> <string name="allow_gps_summary" fuzzy="true">Dobavi trenutnu lokaciju za davanje prijedloga o kategorijama ako nema geooznaku</string>
<string name="preference_theme">Noćni režim</string> <string name="preference_theme">Noćni režim</string>
<string name="preference_theme_summary">Koristi tamnu temu</string> <string name="preference_theme_summary">Koristi tamnu temu</string>
<string name="license_name_cc_by_sa_four">Autorstvo-Dijeliti pod istim uslovima 4.0</string> <string name="license_name_cc_by_sa_four">Autorstvo-Dijeliti pod istim uslovima 4.0</string>

View file

@ -23,7 +23,7 @@
<string name="login_success">S\'ha iniciat sessió correctament!</string> <string name="login_success">S\'ha iniciat sessió correctament!</string>
<string name="login_failed">Error en iniciar la sessió!</string> <string name="login_failed">Error en iniciar la sessió!</string>
<string name="upload_failed">No s\'ha trobat el fitxer. Proveu-ho amb un altre fitxer.</string> <string name="upload_failed">No s\'ha trobat el fitxer. Proveu-ho amb un altre fitxer.</string>
<string name="authentication_failed">L\'autenticació ha fallat!</string> <string name="authentication_failed">Lautenticació ha fallat. Torneu a provar diniciar una sessió.</string>
<string name="uploading_started">Ha començat la càrrega!</string> <string name="uploading_started">Ha començat la càrrega!</string>
<string name="upload_completed_notification_title">Sha pujat %1$s.</string> <string name="upload_completed_notification_title">Sha pujat %1$s.</string>
<string name="upload_completed_notification_text">Prem per veure la teva càrrega</string> <string name="upload_completed_notification_text">Prem per veure la teva càrrega</string>
@ -50,8 +50,6 @@
<string name="share_title_hint">Títol</string> <string name="share_title_hint">Títol</string>
<string name="share_description_hint">Descripció</string> <string name="share_description_hint">Descripció</string>
<string name="login_failed_network">No s\'ha pogut iniciar la sessió error de xarxa</string> <string name="login_failed_network">No s\'ha pogut iniciar la sessió error de xarxa</string>
<string name="login_failed_username">No s\'ha pogut iniciar la sessió si et plau comprova el teu nom d\'usuari</string>
<string name="login_failed_password">No sha pogut iniciar la sessió. Comproveu la vostra contrasenya</string>
<string name="login_failed_throttled">Massa intents erronis Proveu-ho de nou d\'aquí uns minuts.</string> <string name="login_failed_throttled">Massa intents erronis Proveu-ho de nou d\'aquí uns minuts.</string>
<string name="login_failed_blocked">Ho sentim, aquest usuari ha estat blocat a Commons</string> <string name="login_failed_blocked">Ho sentim, aquest usuari ha estat blocat a Commons</string>
<string name="login_failed_2fa_needed">Heu de proporcionar el vostre codi d\'autenticació de dos factors.</string> <string name="login_failed_2fa_needed">Heu de proporcionar el vostre codi d\'autenticació de dos factors.</string>
@ -192,4 +190,5 @@
<string name="notifications_thank_you_edit">Gràcies per fer una modificació</string> <string name="notifications_thank_you_edit">Gràcies per fer una modificació</string>
<string name="notifications_mention" fuzzy="true">%1$s us ha mencionat a %2$s.</string> <string name="notifications_mention" fuzzy="true">%1$s us ha mencionat a %2$s.</string>
<string name="about_faq" fuzzy="true">Preguntes freqüents</string> <string name="about_faq" fuzzy="true">Preguntes freqüents</string>
<string name="no_images_found">No sha trobat cap imatge.</string>
</resources> </resources>

View file

@ -33,7 +33,7 @@
<string name="login_success">Přihlášení uspělo!</string> <string name="login_success">Přihlášení uspělo!</string>
<string name="login_failed">Přihlášení se nezdařilo!</string> <string name="login_failed">Přihlášení se nezdařilo!</string>
<string name="upload_failed">Soubor nebyl nalezen. Prosím, zkuste jiný soubor.</string> <string name="upload_failed">Soubor nebyl nalezen. Prosím, zkuste jiný soubor.</string>
<string name="authentication_failed">Ověření se nezdařilo!</string> <string name="authentication_failed">Ověření se nezdařilo, prosím přihlaste se znovu</string>
<string name="uploading_started">Nahrávání začalo!</string> <string name="uploading_started">Nahrávání začalo!</string>
<string name="upload_completed_notification_title">%1$s nahráno!</string> <string name="upload_completed_notification_title">%1$s nahráno!</string>
<string name="upload_completed_notification_text">Klepnutím zobrazíte upload</string> <string name="upload_completed_notification_text">Klepnutím zobrazíte upload</string>
@ -61,8 +61,6 @@
<string name="add_title_toast">Vložte prosím název tohoto souboru</string> <string name="add_title_toast">Vložte prosím název tohoto souboru</string>
<string name="share_description_hint">Popis</string> <string name="share_description_hint">Popis</string>
<string name="login_failed_network">Nelze se přihlásit - selhání sítě</string> <string name="login_failed_network">Nelze se přihlásit - selhání sítě</string>
<string name="login_failed_username">Nelze se přihlásit - prosím zkontrolujte své uživatelské jméno</string>
<string name="login_failed_password">Nelze se přihlásit - zkontrolujte prosím své heslo</string>
<string name="login_failed_throttled">Příliš mnoho neúspěšných pokusů. Zkuste to prosím znovu za několik minut.</string> <string name="login_failed_throttled">Příliš mnoho neúspěšných pokusů. Zkuste to prosím znovu za několik minut.</string>
<string name="login_failed_blocked">Omlouváme se, tento uživatel byl na Commons zablokován</string> <string name="login_failed_blocked">Omlouváme se, tento uživatel byl na Commons zablokován</string>
<string name="login_failed_2fa_needed">Prosím vložte kód pro své dvoufázové ověření.</string> <string name="login_failed_2fa_needed">Prosím vložte kód pro své dvoufázové ověření.</string>
@ -115,7 +113,7 @@
<string name="preference_license">Výchozí licence</string> <string name="preference_license">Výchozí licence</string>
<string name="use_previous">Použít předchozí název a popis</string> <string name="use_previous">Použít předchozí název a popis</string>
<string name="allow_gps">Automaticky získat aktuální polohu</string> <string name="allow_gps">Automaticky získat aktuální polohu</string>
<string name="allow_gps_summary">Nabídnout kategorie na základě aktuální polohy (pokud není obrázek opatřen souřadnicemi)</string> <string name="allow_gps_summary" fuzzy="true">Nabídnout kategorie na základě aktuální polohy (pokud není obrázek opatřen souřadnicemi)</string>
<string name="preference_theme">Noční režim</string> <string name="preference_theme">Noční režim</string>
<string name="preference_theme_summary">Použít tmavý režim</string> <string name="preference_theme_summary">Použít tmavý režim</string>
<string name="license_name_cc_by_sa_four">Uveďte autora-Zachovejte licenci 4.0</string> <string name="license_name_cc_by_sa_four">Uveďte autora-Zachovejte licenci 4.0</string>

View file

@ -14,7 +14,7 @@
<string name="login_success">Ùdałi logòwanié!</string> <string name="login_success">Ùdałi logòwanié!</string>
<string name="login_failed">Logòwanié nie darzëło sã!</string> <string name="login_failed">Logòwanié nie darzëło sã!</string>
<string name="upload_failed">Felënk lopka. Proszã spróbòwac znowa.</string> <string name="upload_failed">Felënk lopka. Proszã spróbòwac znowa.</string>
<string name="authentication_failed">Fela ùdowierzaniô!</string> <string name="authentication_failed" fuzzy="true">Fela ùdowierzaniô!</string>
<string name="uploading_started">Wladënk zrëszony!</string> <string name="uploading_started">Wladënk zrëszony!</string>
<string name="upload_completed_notification_title">%1$s wladowóné!</string> <string name="upload_completed_notification_title">%1$s wladowóné!</string>
<string name="upload_completed_notification_text">Tkni, abë òbôczëc ladowóny lopk</string> <string name="upload_completed_notification_text">Tkni, abë òbôczëc ladowóny lopk</string>
@ -41,8 +41,6 @@
<string name="share_title_hint">Titel</string> <string name="share_title_hint">Titel</string>
<string name="share_description_hint">Òpisënk</string> <string name="share_description_hint">Òpisënk</string>
<string name="login_failed_network">Ni mòże sã wlogòwac - fela sécë</string> <string name="login_failed_network">Ni mòże sã wlogòwac - fela sécë</string>
<string name="login_failed_username">Ni mòże sã wlogòwac - sprôwdzë miono brëkòwnika</string>
<string name="login_failed_password">Ni mòże sã wlogòwac - sprôwdzë parolã</string>
<string name="login_failed_throttled">Za wiele nieùdałich prób wlogòwaniô. Spróbùjë znowa za czile minut.</string> <string name="login_failed_throttled">Za wiele nieùdałich prób wlogòwaniô. Spróbùjë znowa za czile minut.</string>
<string name="login_failed_blocked">Nen brëkòwnik òstôł zablokòwóny na Commons</string> <string name="login_failed_blocked">Nen brëkòwnik òstôł zablokòwóny na Commons</string>
<string name="login_failed_2fa_needed">Mùszisz wpisac swój kòd dlô dwafaktorowi aùtorizacëji.</string> <string name="login_failed_2fa_needed">Mùszisz wpisac swój kòd dlô dwafaktorowi aùtorizacëji.</string>

View file

@ -5,18 +5,25 @@
* Robin Owain * Robin Owain
--> -->
<resources> <resources>
<string name="preference_category_appearance">Ymddangosiad</string>
<string name="preference_category_general">Cyffredinol</string>
<string name="preference_category_feedback">Adborth</string>
<string name="preference_category_location">Lleoliad</string>
<string name="app_name">Comin Wicimedia</string> <string name="app_name">Comin Wicimedia</string>
<string name="bullet"></string>
<string name="menu_settings">Gosodiadau</string> <string name="menu_settings">Gosodiadau</string>
<string name="username">Enw defnyddiwr</string> <string name="username">Enw defnyddiwr</string>
<string name="password">Cyfrinair</string> <string name="password">Cyfrinair</string>
<string name="login_credential">Mewngofnodwch i\'ch cyfri Comin Beta</string>
<string name="login">Mewngofnodi</string> <string name="login">Mewngofnodi</string>
<string name="forgot_password">Anghofiwyd y Cyfrinair?</string>
<string name="signup">Cofrestru</string> <string name="signup">Cofrestru</string>
<string name="logging_in_title">Wrthi\'n mewngofnodi</string> <string name="logging_in_title">Wrthi\'n mewngofnodi</string>
<string name="logging_in_message">Disgwyliwch…</string> <string name="logging_in_message">Disgwyliwch…</string>
<string name="login_success">Llwyddodd y mewngofnodi!</string> <string name="login_success">Llwyddodd y mewngofnodi!</string>
<string name="login_failed">Methodd y mewngofnodi!</string> <string name="login_failed">Methodd y mewngofnodi!</string>
<string name="upload_failed">Ni chafwyd hyd i\'r ffeil. Ceisiwch un arall.</string> <string name="upload_failed">Ni chafwyd hyd i\'r ffeil. Ceisiwch un arall.</string>
<string name="authentication_failed">Methodd y dilysu!</string> <string name="authentication_failed">Methodd y dilysu! Mewngofnodwch eto.</string>
<string name="uploading_started">Dechreuodd yr uwchlwytho!</string> <string name="uploading_started">Dechreuodd yr uwchlwytho!</string>
<string name="upload_completed_notification_title">Uwchlwythwyd %1$s!</string> <string name="upload_completed_notification_title">Uwchlwythwyd %1$s!</string>
<string name="upload_completed_notification_text">Tapiwch i weld eich uwchlwythiad</string> <string name="upload_completed_notification_text">Tapiwch i weld eich uwchlwythiad</string>
@ -41,10 +48,10 @@
<string name="menu_share">Rhannu</string> <string name="menu_share">Rhannu</string>
<string name="menu_open_in_browser">Agor yn y Porwr</string> <string name="menu_open_in_browser">Agor yn y Porwr</string>
<string name="share_title_hint">Teitl</string> <string name="share_title_hint">Teitl</string>
<string name="add_title_toast">Rhowch deitl i\'r ffeil</string>
<string name="share_description_hint">Disgrifiad</string> <string name="share_description_hint">Disgrifiad</string>
<string name="login_failed_network">Yn methu mewngofnodi - methodd y rhwydwaith</string> <string name="login_failed_network">Yn methu mewngofnodi - methodd y rhwydwaith</string>
<string name="login_failed_username">Yn methu mewngofnodi - gwirwch eich enw defnyddiwr</string> <string name="login_failed_wrong_credentials">Methwyd mewngofnodi - gwirwch eich enw defnyddiwr a\'ch cyfrinair</string>
<string name="login_failed_password">Yn methu mewngofnodi - gwirwch eich cyfrinair</string>
<string name="login_failed_throttled">Cafwyd gormod o ymgeision aflwyddiannus. Oedwch ennyd cyn ceisio eto.</string> <string name="login_failed_throttled">Cafwyd gormod o ymgeision aflwyddiannus. Oedwch ennyd cyn ceisio eto.</string>
<string name="login_failed_blocked">Ymddiheurwn. Mae\'r defnyddiwr hwn wedi ei flocio ar Gomin Wikimedia</string> <string name="login_failed_blocked">Ymddiheurwn. Mae\'r defnyddiwr hwn wedi ei flocio ar Gomin Wikimedia</string>
<string name="login_failed_2fa_needed">Mae\'n rhaid i chi roi eich cod adnabod 2 ffactor.</string> <string name="login_failed_2fa_needed">Mae\'n rhaid i chi roi eich cod adnabod 2 ffactor.</string>
@ -56,6 +63,7 @@
<string name="categories_search_text_hint">Archwilio\'r categorïau</string> <string name="categories_search_text_hint">Archwilio\'r categorïau</string>
<string name="menu_save_categories">Cadw</string> <string name="menu_save_categories">Cadw</string>
<string name="refresh_button">Ailgyrchu</string> <string name="refresh_button">Ailgyrchu</string>
<string name="display_list_button">Rhestr</string>
<string name="gps_disabled">Ataliwyd GPS ar eich dyfais. Ydych chi am ei droi\'n weithredol?</string> <string name="gps_disabled">Ataliwyd GPS ar eich dyfais. Ydych chi am ei droi\'n weithredol?</string>
<string name="enable_gps">Gweithredu\'r GPS</string> <string name="enable_gps">Gweithredu\'r GPS</string>
<string name="contributions_subtitle_zero">Heb uwchlwytho eto</string> <string name="contributions_subtitle_zero">Heb uwchlwytho eto</string>
@ -77,11 +85,12 @@
<string name="categories_activity_title">Categorïau</string> <string name="categories_activity_title">Categorïau</string>
<string name="title_activity_settings">Gosodiadau</string> <string name="title_activity_settings">Gosodiadau</string>
<string name="title_activity_signup">Cofrestru</string> <string name="title_activity_signup">Cofrestru</string>
<string name="title_activity_featured_images">Delweddau nodwedd</string>
<string name="menu_about">Amdanom</string> <string name="menu_about">Amdanom</string>
<string name="about_license">Ap Cynnwys Agored a grewyd ac a gefnogir gan wirfoddolwyr cymuned Wicimedia yw ap Comin Wicimedia. Does a wnelo Sefydliad Wicimedia ddim byd ag e (ei greu, ei gynnal na\'i ddatblygu).</string> <string name="about_license">Ap Cynnwys Agored a grewyd ac a gefnogir gan wirfoddolwyr cymuned Wicimedia yw ap Comin Wicimedia. Does a wnelo Sefydliad Wicimedia ddim byd ag e (ei greu, ei gynnal na\'i ddatblygu).</string>
<string name="about_improve">\n\nCrewch &lt;a href=\"https://github.com/commons-app/apps-android-commons/issues\"&gt;ymholiad GitHub&lt;/a&gt; os oes gennych fyg, broblem neu awgrym.</string> <string name="about_improve">\n\nCrewch &lt;a href=\"https://github.com/commons-app/apps-android-commons/issues\"&gt;ymholiad GitHub&lt;/a&gt; os oes gennych fyg, broblem neu awgrym.</string>
<string name="about_privacy_policy" fuzzy="true">&lt;a href=\"https://github.com/commons-app/apps-android-commons/wiki/Privacy-policy\"&gt;Polisi Preifatrwydd&lt;/a&gt;</string> <string name="about_privacy_policy">&lt;u&gt;Polisi preifatrwydd&lt;/u&gt;</string>
<string name="about_credits" fuzzy="true">&lt;a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\"&gt;Clod&lt;/a&gt;</string> <string name="about_credits">Clod a bri</string>
<string name="title_activity_about">Amdanom</string> <string name="title_activity_about">Amdanom</string>
<string name="menu_feedback">Danfonwch Adborth (drwy Ebost)</string> <string name="menu_feedback">Danfonwch Adborth (drwy Ebost)</string>
<string name="no_email_client">Dim ebost client wedi\'i ganfod</string> <string name="no_email_client">Dim ebost client wedi\'i ganfod</string>
@ -93,10 +102,10 @@
<string name="share_license_summary">Caiff y ddelwedd hon ei thrwyddedu yn ôl termau\'r drwydded %1$s.</string> <string name="share_license_summary">Caiff y ddelwedd hon ei thrwyddedu yn ôl termau\'r drwydded %1$s.</string>
<string name="media_upload_policy">Wrth gynnig y llun yma, rwy\'n datgan mai fy ngwaith i ydyw ac nad yw\'n cynnwys unrhyw beth dan hawlfrain, na hunlun, a\'i fod yn cadw at &lt;a href=\"https://commons.wikimedia.org/wiki/Commons:Policies_and_guidelines\"&gt;Bolisiau Comin Wicimedia&lt;/a&gt;.</string> <string name="media_upload_policy">Wrth gynnig y llun yma, rwy\'n datgan mai fy ngwaith i ydyw ac nad yw\'n cynnwys unrhyw beth dan hawlfrain, na hunlun, a\'i fod yn cadw at &lt;a href=\"https://commons.wikimedia.org/wiki/Commons:Policies_and_guidelines\"&gt;Bolisiau Comin Wicimedia&lt;/a&gt;.</string>
<string name="menu_download">Lawrlwytho</string> <string name="menu_download">Lawrlwytho</string>
<string name="preference_license" fuzzy="true">Trwydded</string> <string name="preference_license">Trwydded Ddiofyn (\'default\')</string>
<string name="use_previous">Defnydiwch y teitl/disgrifiad blaenorol</string> <string name="use_previous">Defnydiwch y teitl/disgrifiad blaenorol</string>
<string name="allow_gps">Defnyddiwch y lleoliad cyfredol</string> <string name="allow_gps">Defnyddiwch y lleoliad cyfredol</string>
<string name="allow_gps_summary">Canfyddwch eich lleoliad, er mwyn i ni gynnig categori (os nad ydych wedi nodi\'r cyfesurynnau).</string> <string name="allow_gps_summary">Adfer eich lleoliad presennol os nad yw\'r ddelwedd yn cynnwys cyfesurynnau. Bydd hyn yn datgelu eich lleoliad chi!</string>
<string name="preference_theme">Modd fin nos</string> <string name="preference_theme">Modd fin nos</string>
<string name="preference_theme_summary">Defnyddiwch thema tywyll</string> <string name="preference_theme_summary">Defnyddiwch thema tywyll</string>
<string name="license_name_cc_by_sa_four"> Attribution-ShareAlike 4.0</string> <string name="license_name_cc_by_sa_four"> Attribution-ShareAlike 4.0</string>
@ -122,9 +131,19 @@
<string name="tutorial_1_text">Cyhelir llawer o luniau ar Gomin Wicimedia sy\'n cael eu defnyddio ar Wicipedia.</string> <string name="tutorial_1_text">Cyhelir llawer o luniau ar Gomin Wicimedia sy\'n cael eu defnyddio ar Wicipedia.</string>
<string name="tutorial_1_subtext">Mae eich lluniau\'n gymorth i addysgu pobl drwy\'r byd mawr crwn!</string> <string name="tutorial_1_subtext">Mae eich lluniau\'n gymorth i addysgu pobl drwy\'r byd mawr crwn!</string>
<string name="tutorial_2_text">Uwchlwythwch lluniau a dynnoch eich hun:</string> <string name="tutorial_2_text">Uwchlwythwch lluniau a dynnoch eich hun:</string>
<string name="tutorial_2_subtext" fuzzy="true">- Natur (blodau, anifeiliaid, mynyddoedd)\n- Pethau defnyddiol (beic, tren, gorsaf drenau)\n- Enwogion (beirdd, athletwyr, blogwyr)</string> <string name="tutorial_2_subtext">Gwrthrychau byd natur (blodau, anifeiliaid, mynyddoedd)\n- Gwrthrychau defnyddiol (beics, trenau, gorsafoedd trenau)\n- Enwogion (beirdd, athletwyr, blogwyr)</string>
<string name="tutorial_2_subtext_1">Gwrthrychau byd natur (blodau, anifeiliaid, mynyddoedd)</string>
<string name="tutorial_2_subtext_2">Gwrthrychau defnyddiol (beics, trenau, gorsafoedd trenau)</string>
<string name="tutorial_2_subtext_3">Enwogion (beirdd, athletwyr, blogwyr)</string>
<string name="tutorial_3_text">Peidiwch ag uwchlwytho:</string> <string name="tutorial_3_text">Peidiwch ag uwchlwytho:</string>
<string name="tutorial_3_subtext">- hunanluniau ohonoch chi na\'ch ffrindiau\n- lluniau a gawsoch o\'r we\n- sgrinluniau o apiau masnachol</string> <string name="tutorial_3_subtext">- hunanluniau ohonoch chi na\'ch ffrindiau\n- lluniau a gawsoch o\'r we\n- sgrinluniau o apiau masnachol</string>
<string name="tutorial_3_subtext_1">Hunanluniau neu luniau o\'ch ffrindiau</string>
<string name="tutorial_3_subtext_2">Lluniau a lawrlwythwyd o\'r we gennych</string>
<string name="tutorial_3_subtext_3">Sgrinluniau o aps</string>
<string name="tutorial_4_text">Enghraifft o uwchlwythiad:</string>
<string name="tutorial_4_subtext_1">Teitl:Tŷ Opera Sydney</string>
<string name="tutorial_4_subtext_2">Disgrifiad: Golygfa o Dŷ Opera Sydney o ochr arall y bae</string>
<string name="tutorial_4_subtext_3">Categoriau: Tŷ Opera Sydney o\'r gorllewin</string>
<string name="welcome_wikipedia_text">Cyfranwch luniau. Cynorthwywch Wicipedia i roi bywyd yn yr erthyglau!</string> <string name="welcome_wikipedia_text">Cyfranwch luniau. Cynorthwywch Wicipedia i roi bywyd yn yr erthyglau!</string>
<string name="welcome_wikipedia_subtext">Mae\'r delweddau ar Wicipedia\'n dod o\nGomin Wikimedia.</string> <string name="welcome_wikipedia_subtext">Mae\'r delweddau ar Wicipedia\'n dod o\nGomin Wikimedia.</string>
<string name="welcome_copyright_text">Mae eich delweddau\'n cynorthwyo i addysgu pobl ledled y byd.</string> <string name="welcome_copyright_text">Mae eich delweddau\'n cynorthwyo i addysgu pobl ledled y byd.</string>
@ -137,6 +156,32 @@
<string name="detail_description_empty">Dim disgrifiad</string> <string name="detail_description_empty">Dim disgrifiad</string>
<string name="detail_license_empty">Trwydded anhysbys</string> <string name="detail_license_empty">Trwydded anhysbys</string>
<string name="menu_refresh">Adnewyddu</string> <string name="menu_refresh">Adnewyddu</string>
<string name="ok">Iawn</string>
<string name="title_activity_nearby">Lleoedd Cyfagos</string>
<string name="no_nearby">Ni chafwyd hyd i leoedd cyfagos</string>
<string name="warning">Rhybudd</string>
<string name="yes">Ydw</string>
<string name="no">Nac ydw</string>
<string name="media_detail_title">Teitl</string> <string name="media_detail_title">Teitl</string>
<string name="media_detail_media_title">teitl y cyfrwng</string>
<string name="media_detail_description">Disgrifiad</string> <string name="media_detail_description">Disgrifiad</string>
<string name="media_detail_author">Awdur</string>
<string name="media_detail_uploaded_date">Dyddiad yr uwchlwythiad</string>
<string name="media_detail_license">Trwydded</string>
<string name="media_detail_coordinates">Cyfesurynnau</string>
<string name="media_detail_coordinates_empty">Dim</string>
<string name="nearby_info_menu_wikidata_article">Eitem Wicidata</string>
<string name="nearby_info_menu_wikipedia_article">Erthygl Wicipedia</string>
<string name="login_to_your_account">Mewngofnodwch i\'ch cyfri</string>
<string name="send_log_file">Danfonwch y ffeil log</string>
<string name="view_browser">Gweld yn y porwr</string>
<string name="nearby_location_has_not_changed">Nid yw\'r lleoliad wedi newid.</string>
<string name="nearby_location_not_available">Nid yw\'r lleoliad ar gael.</string>
<string name="about_translate_proceed">Parhau</string>
<string name="about_translate_cancel">Canslo</string>
<string name="retry">Ailgeisio</string>
<string name="showcase_view_got_it_button">Gwnaed!</string>
<string name="appwidget_img">Llun y Dydd</string>
<string name="app_widget_heading">Llun y Dydd</string>
<string name="successful_wikidata_edit">Mae %1$s o luniau wedi\'u hychwanegu ar Wicidata!</string>
</resources> </resources>

View file

@ -26,7 +26,7 @@
<string name="login_success">Du er nu logget på!</string> <string name="login_success">Du er nu logget på!</string>
<string name="login_failed">Det mislykkedes at logge på!</string> <string name="login_failed">Det mislykkedes at logge på!</string>
<string name="upload_failed">Filen blev ikke fundet. Forsøg med en anden fil.</string> <string name="upload_failed">Filen blev ikke fundet. Forsøg med en anden fil.</string>
<string name="authentication_failed">Godkendelse mislykkedes!</string> <string name="authentication_failed" fuzzy="true">Godkendelse mislykkedes!</string>
<string name="uploading_started">Overførsel begyndt!</string> <string name="uploading_started">Overførsel begyndt!</string>
<string name="upload_completed_notification_title">%1$s overført!</string> <string name="upload_completed_notification_title">%1$s overført!</string>
<string name="upload_completed_notification_text">Tryk for at få vist din upload</string> <string name="upload_completed_notification_text">Tryk for at få vist din upload</string>
@ -54,8 +54,6 @@
<string name="add_title_toast">Angiv venligt en titel for denne fil</string> <string name="add_title_toast">Angiv venligt en titel for denne fil</string>
<string name="share_description_hint">Beskrivelse</string> <string name="share_description_hint">Beskrivelse</string>
<string name="login_failed_network">Kan ikke logge på - netværksfejl</string> <string name="login_failed_network">Kan ikke logge på - netværksfejl</string>
<string name="login_failed_username">Ude af stand til at logge på - tjek venligst dit brugernavn</string>
<string name="login_failed_password">Ude af stand til at logge på - tjek venligst din adgangskode</string>
<string name="login_failed_throttled">Alt for mange mislykkede forsøg. Prøv igen om et par minutter.</string> <string name="login_failed_throttled">Alt for mange mislykkede forsøg. Prøv igen om et par minutter.</string>
<string name="login_failed_blocked">Beklager, denne bruger er blevet blokeret på Commons</string> <string name="login_failed_blocked">Beklager, denne bruger er blevet blokeret på Commons</string>
<string name="login_failed_2fa_needed">Du skal angive din tofaktorgodkendelseskode.</string> <string name="login_failed_2fa_needed">Du skal angive din tofaktorgodkendelseskode.</string>
@ -107,7 +105,7 @@
<string name="preference_license">Standardlicens</string> <string name="preference_license">Standardlicens</string>
<string name="use_previous">Brug forrige titel/beskrivelse</string> <string name="use_previous">Brug forrige titel/beskrivelse</string>
<string name="allow_gps">Hent automatisk nuværende placering</string> <string name="allow_gps">Hent automatisk nuværende placering</string>
<string name="allow_gps_summary">Hent nuværende placering for at tilbyde kategoriforslag hvis billedet ikke er geografisk mærket</string> <string name="allow_gps_summary" fuzzy="true">Hent nuværende placering for at tilbyde kategoriforslag hvis billedet ikke er geografisk mærket</string>
<string name="preference_theme">Nat-tilstand</string> <string name="preference_theme">Nat-tilstand</string>
<string name="preference_theme_summary">Brug mørkt tema</string> <string name="preference_theme_summary">Brug mørkt tema</string>
<string name="license_name_cc_by_sa_four"> Attribution-ShareAlike 4.0</string> <string name="license_name_cc_by_sa_four"> Attribution-ShareAlike 4.0</string>

View file

@ -25,7 +25,7 @@
<string name="login_success">Anmeldung erfolgreich!</string> <string name="login_success">Anmeldung erfolgreich!</string>
<string name="login_failed">Anmeldung fehlgeschlagen!</string> <string name="login_failed">Anmeldung fehlgeschlagen!</string>
<string name="upload_failed">Datei nicht gefunden. Bitte versuche es mit einer anderen.</string> <string name="upload_failed">Datei nicht gefunden. Bitte versuche es mit einer anderen.</string>
<string name="authentication_failed">Authentifizierung fehlgeschlagen!</string> <string name="authentication_failed">Authentifizierung fehlgeschlagen. Bitte erneut anmelden.</string>
<string name="uploading_started">Hochladen gestartet!</string> <string name="uploading_started">Hochladen gestartet!</string>
<string name="upload_completed_notification_title">„%1$s“ hochgeladen!</string> <string name="upload_completed_notification_title">„%1$s“ hochgeladen!</string>
<string name="upload_completed_notification_text">Tippe, um deinen Upload anzusehen</string> <string name="upload_completed_notification_text">Tippe, um deinen Upload anzusehen</string>
@ -53,8 +53,7 @@
<string name="add_title_toast">Bitte gib einen Titel für diese Datei an</string> <string name="add_title_toast">Bitte gib einen Titel für diese Datei an</string>
<string name="share_description_hint">Beschreibung</string> <string name="share_description_hint">Beschreibung</string>
<string name="login_failed_network">Anmeldung fehlgeschlagen Netzwerkfehler</string> <string name="login_failed_network">Anmeldung fehlgeschlagen Netzwerkfehler</string>
<string name="login_failed_username">Anmeldung fehlgeschlagen Bitte Benutzernamen überprüfen</string> <string name="login_failed_wrong_credentials">Anmeldung fehlgeschlagen. Bitte Benutzernamen und Passwort überprüfen.</string>
<string name="login_failed_password">Anmeldung fehlgeschlagen Bitte Passwort überprüfen</string>
<string name="login_failed_throttled">Zu viele erfolglose Versuche. Bitte in einigen Minuten erneut versuchen.</string> <string name="login_failed_throttled">Zu viele erfolglose Versuche. Bitte in einigen Minuten erneut versuchen.</string>
<string name="login_failed_blocked">Dieser Benutzer wurde leider auf Commons gesperrt</string> <string name="login_failed_blocked">Dieser Benutzer wurde leider auf Commons gesperrt</string>
<string name="login_failed_2fa_needed">Du musst deinen Code zur Zwei-Faktor-Authentifizierung angeben.</string> <string name="login_failed_2fa_needed">Du musst deinen Code zur Zwei-Faktor-Authentifizierung angeben.</string>
@ -108,7 +107,7 @@
<string name="preference_license">Standardlizenz</string> <string name="preference_license">Standardlizenz</string>
<string name="use_previous">Vorherige(n) Titel/Beschreibung verwenden</string> <string name="use_previous">Vorherige(n) Titel/Beschreibung verwenden</string>
<string name="allow_gps">Aktuellen Standort automatisch abrufen</string> <string name="allow_gps">Aktuellen Standort automatisch abrufen</string>
<string name="allow_gps_summary">Ruft den aktuellen Standort ab, um Kategorievorschläge anzubieten, falls das Bild keine Geotags hat.</string> <string name="allow_gps_summary">Ruft den aktuellen Standort ab, falls das Bild nicht georeferenziert ist und markiert es. Warnung: Diese Aktion verrät deinen aktuellen Standort.</string>
<string name="preference_theme">Nachtmodus</string> <string name="preference_theme">Nachtmodus</string>
<string name="preference_theme_summary">Dunkles Thema verwenden</string> <string name="preference_theme_summary">Dunkles Thema verwenden</string>
<string name="license_name_cc_by_sa_four">Attribution-ShareAlike 4.0</string> <string name="license_name_cc_by_sa_four">Attribution-ShareAlike 4.0</string>
@ -278,4 +277,11 @@
<string name="image_uploaded_by">Hochgeladen von: %1$s</string> <string name="image_uploaded_by">Hochgeladen von: %1$s</string>
<string name="share_app_title">App teilen</string> <string name="share_app_title">App teilen</string>
<string name="share_coordinates_not_present">Während der Bildauswahl wurden keine Koordinaten angegeben</string> <string name="share_coordinates_not_present">Während der Bildauswahl wurden keine Koordinaten angegeben</string>
<string name="error_fetching_nearby_places">Fehler beim Abrufen der Orte in der Nähe.</string>
<string name="appwidget_img">Bild des Tages</string>
<string name="app_widget_heading">Bild des Tages</string>
<string name="successful_wikidata_edit">Bild erfolgreich nach %1$s auf Wikidata hinzugefügt!</string>
<string name="wikidata_edit_failure">Fehler bei der Aktualisierung des dazugehörigen Wikidata-Objekts!</string>
<string name="menu_set_wallpaper">Hintergrundbild festlegen</string>
<string name="wallpaper_set_successfully">Hintergrundbild erfolgreich festgelegt!</string>
</resources> </resources>

View file

@ -13,6 +13,7 @@
<string name="preference_category_general">Bıngeh</string> <string name="preference_category_general">Bıngeh</string>
<string name="preference_category_location">Lokasyon</string> <string name="preference_category_location">Lokasyon</string>
<string name="app_name">Commons</string> <string name="app_name">Commons</string>
<string name="bullet"></string>
<string name="menu_settings">Eyari</string> <string name="menu_settings">Eyari</string>
<string name="username">Namey karberi</string> <string name="username">Namey karberi</string>
<string name="password">Parola</string> <string name="password">Parola</string>
@ -24,7 +25,7 @@
<string name="login_success">Cıkewtış hewl bi.</string> <string name="login_success">Cıkewtış hewl bi.</string>
<string name="login_failed">Nidekeweya de</string> <string name="login_failed">Nidekeweya de</string>
<string name="upload_failed">Dosya nêvineya. Dosyê da bine bıcerebnê.</string> <string name="upload_failed">Dosya nêvineya. Dosyê da bine bıcerebnê.</string>
<string name="authentication_failed">Tesdiq kerdış nebı</string> <string name="authentication_failed" fuzzy="true">Tesdiq kerdış nebı</string>
<string name="uploading_started">Barkerdış sertera!</string> <string name="uploading_started">Barkerdış sertera!</string>
<string name="upload_completed_notification_title">%1$s bıbar!</string> <string name="upload_completed_notification_title">%1$s bıbar!</string>
<string name="upload_completed_notification_text">Barkerdışê xo pıro bıde.</string> <string name="upload_completed_notification_text">Barkerdışê xo pıro bıde.</string>
@ -51,8 +52,6 @@
<string name="share_title_hint">Sername</string> <string name="share_title_hint">Sername</string>
<string name="share_description_hint">Şınasnayış</string> <string name="share_description_hint">Şınasnayış</string>
<string name="login_failed_network">Xırabiya kewten-network xeta</string> <string name="login_failed_network">Xırabiya kewten-network xeta</string>
<string name="login_failed_username">Ronıştışo abeno - Namey karberi ye xo kontrol kerë</string>
<string name="login_failed_password">Ronıştışo nêabeno - Parolay xo kontrol kerë</string>
<string name="login_failed_throttled">Şıma xeylê rayi kerd ke cı kewê, a ser nêvıst. Şıma rê zehmet 2-3 deqey ra tepeya reyna bıcerrebnên.</string> <string name="login_failed_throttled">Şıma xeylê rayi kerd ke cı kewê, a ser nêvıst. Şıma rê zehmet 2-3 deqey ra tepeya reyna bıcerrebnên.</string>
<string name="login_failed_blocked">Qısur mewni rê, Karber commons dı bloqe biyo.</string> <string name="login_failed_blocked">Qısur mewni rê, Karber commons dı bloqe biyo.</string>
<string name="login_failed_generic">Nidekeweya de</string> <string name="login_failed_generic">Nidekeweya de</string>
@ -84,7 +83,8 @@
<string name="title_activity_signup">Qeyd be</string> <string name="title_activity_signup">Qeyd be</string>
<string name="menu_about">Heq te cı</string> <string name="menu_about">Heq te cı</string>
<string name="about_improve">Qandê yew &lt;a href=\"https://github.com/commons-app/apps-android-commons/issues\"&gt;GitHub-cıkewtış&lt;/a&gt;ê neweyi rê rapor û teklifan bıaferne.</string> <string name="about_improve">Qandê yew &lt;a href=\"https://github.com/commons-app/apps-android-commons/issues\"&gt;GitHub-cıkewtış&lt;/a&gt;ê neweyi rê rapor û teklifan bıaferne.</string>
<string name="about_privacy_policy" fuzzy="true">&lt;a href=\"https://github.com/commons-app/apps-android-commons/wiki/Privacy-policy\"&gt;Politikay nımıtışi&lt;/a&gt;</string> <string name="about_privacy_policy">&lt;u&gt;Politikaya nımıtışi&lt;/u&gt;</string>
<string name="about_credits">&lt;u&gt;İştırakkerdoği&lt;/u&gt;</string>
<string name="title_activity_about">Heq te cı</string> <string name="title_activity_about">Heq te cı</string>
<string name="menu_feedback">Peyd rışten bırış (E-posta ra)</string> <string name="menu_feedback">Peyd rışten bırış (E-posta ra)</string>
<string name="no_email_client">E-posta eyar nêbi</string> <string name="no_email_client">E-posta eyar nêbi</string>
@ -92,7 +92,7 @@
<string name="menu_retry_upload">Anciya bıcerrebne</string> <string name="menu_retry_upload">Anciya bıcerrebne</string>
<string name="menu_cancel_upload">Bıtexelne</string> <string name="menu_cancel_upload">Bıtexelne</string>
<string name="menu_download">Ron</string> <string name="menu_download">Ron</string>
<string name="preference_license" fuzzy="true">Lisans</string> <string name="preference_license">Lisanso hesebiyaye</string>
<string name="license_name_cc_by_sa"> Attribution-ShareAlike 3.0</string> <string name="license_name_cc_by_sa"> Attribution-ShareAlike 3.0</string>
<string name="license_name_cc_by">Attribution 3.0</string> <string name="license_name_cc_by">Attribution 3.0</string>
<string name="license_name_cc0">CC0</string> <string name="license_name_cc0">CC0</string>
@ -127,6 +127,8 @@
<string name="yes">E</string> <string name="yes">E</string>
<string name="no"></string> <string name="no"></string>
<string name="media_detail_title">Sername</string> <string name="media_detail_title">Sername</string>
<string name="media_detail_description">Şınasnayış</string>
<string name="media_detail_author">Nuştekar</string>
<string name="media_detail_license">Lisans</string> <string name="media_detail_license">Lisans</string>
<string name="media_detail_coordinates">Koordinati</string> <string name="media_detail_coordinates">Koordinati</string>
<string name="welcome_image_tulip">Korbıze</string> <string name="welcome_image_tulip">Korbıze</string>

View file

@ -29,7 +29,7 @@
<string name="login_success">Επιτυχής σύνδεση!</string> <string name="login_success">Επιτυχής σύνδεση!</string>
<string name="login_failed">Η είσοδος απέτυχε!</string> <string name="login_failed">Η είσοδος απέτυχε!</string>
<string name="upload_failed">Το αρχείο δεν βρέθηκε. Παρακαλώ δοκιμάστε ένα άλλο αρχείο.</string> <string name="upload_failed">Το αρχείο δεν βρέθηκε. Παρακαλώ δοκιμάστε ένα άλλο αρχείο.</string>
<string name="authentication_failed">Απέτυχε ο έλεγχος ταυτότητας!</string> <string name="authentication_failed">Απέτυχε ο έλεγχος ταυτότητας, παρακαλώ συνδεθείτε ξανά</string>
<string name="uploading_started">Η αποστολή ξεκίνησε!</string> <string name="uploading_started">Η αποστολή ξεκίνησε!</string>
<string name="upload_completed_notification_title">%1$s επιφορτώθηκε!</string> <string name="upload_completed_notification_title">%1$s επιφορτώθηκε!</string>
<string name="upload_completed_notification_text">Πατήστε για να προβάλλετε την αποστολή</string> <string name="upload_completed_notification_text">Πατήστε για να προβάλλετε την αποστολή</string>
@ -57,8 +57,7 @@
<string name="add_title_toast">Παρακαλώ παρέχετε ένα τίτλο για αυτό το αρχείο</string> <string name="add_title_toast">Παρακαλώ παρέχετε ένα τίτλο για αυτό το αρχείο</string>
<string name="share_description_hint">Περιγραφή</string> <string name="share_description_hint">Περιγραφή</string>
<string name="login_failed_network">Δεν είναι δυνατή η σύνδεση - αποτυχία του δικτύου</string> <string name="login_failed_network">Δεν είναι δυνατή η σύνδεση - αποτυχία του δικτύου</string>
<string name="login_failed_username">Δεν είναι δυνατή η σύνδεση - ελέγξτε το όνομα χρήστη σας</string> <string name="login_failed_wrong_credentials">Αποτυχία σύνδεσης - παρακαλώ ελέγξτε το όνομα χρήστη και τον κωδικό σας</string>
<string name="login_failed_password">Δεν είναι δυνατή η σύνδεση - παρακαλούμε ελέγξτε τον κωδικό σας</string>
<string name="login_failed_throttled">Πάρα πολλές ανεπιτυχείς προσπάθειες. Παρακαλώ δοκιμάστε ξανά σε λίγα λεπτά.</string> <string name="login_failed_throttled">Πάρα πολλές ανεπιτυχείς προσπάθειες. Παρακαλώ δοκιμάστε ξανά σε λίγα λεπτά.</string>
<string name="login_failed_blocked">Συγνώμη, αυτός ο χρήστης έχει αποκλειστεί από τα Commons</string> <string name="login_failed_blocked">Συγνώμη, αυτός ο χρήστης έχει αποκλειστεί από τα Commons</string>
<string name="login_failed_2fa_needed">Πρέπει να δώσετε τον κωδικό πιστοποίησης με δύο παράγοντες</string> <string name="login_failed_2fa_needed">Πρέπει να δώσετε τον κωδικό πιστοποίησης με δύο παράγοντες</string>
@ -112,7 +111,7 @@
<string name="preference_license">Προεπιλεγμένη άδεια</string> <string name="preference_license">Προεπιλεγμένη άδεια</string>
<string name="use_previous">Χρήση προηγούμενου τίτλου/περιγραφής</string> <string name="use_previous">Χρήση προηγούμενου τίτλου/περιγραφής</string>
<string name="allow_gps">Αυτόματη ανάκτηση τρέχουσας θέσης</string> <string name="allow_gps">Αυτόματη ανάκτηση τρέχουσας θέσης</string>
<string name="allow_gps_summary">Ανάκτηση τρέχουσας τοποθεσία για να σας προσφέρουμε προτάσεις κατηγοριών αν η εικόνα δεν είναι γεωσεσημασμένη.</string> <string name="allow_gps_summary">Ανακτά την τρέχουσα τοποθεσία εάν η εικόνα δεν είναι γεωσεσημασμένη, και τις γεωσημάνσεις με αυτή. Προειδοποίηση: Αυτό θα αποκαλύψει την τρέχουσα τοποθεσία σας.</string>
<string name="preference_theme">Νυχτερινή λειτουργία</string> <string name="preference_theme">Νυχτερινή λειτουργία</string>
<string name="preference_theme_summary">Χρήση σκοτεινού θέματος</string> <string name="preference_theme_summary">Χρήση σκοτεινού θέματος</string>
<string name="license_name_cc_by_sa_four"> Attribution-ShareAlike 4.0</string> <string name="license_name_cc_by_sa_four"> Attribution-ShareAlike 4.0</string>
@ -282,4 +281,11 @@
<string name="image_uploaded_by">Ανέβηκε από: %1$s</string> <string name="image_uploaded_by">Ανέβηκε από: %1$s</string>
<string name="share_app_title">Κοινοποίηση εφαρμογής</string> <string name="share_app_title">Κοινοποίηση εφαρμογής</string>
<string name="share_coordinates_not_present">Οι συντεταγμένες δεν ορίστηκαν κατά την διάρκεια της επιλογής εικόνας</string> <string name="share_coordinates_not_present">Οι συντεταγμένες δεν ορίστηκαν κατά την διάρκεια της επιλογής εικόνας</string>
<string name="error_fetching_nearby_places">Σφάλμα κατά την εύρεση κοντινών μερών.</string>
<string name="appwidget_img">Φωτογραφία της Ημέρας</string>
<string name="app_widget_heading">Φωτογραφία της Ημέρας</string>
<string name="successful_wikidata_edit">Η εικόνα προστέθηκε επιτυχώς στο %1$s στο Wikidata!</string>
<string name="wikidata_edit_failure">Αποτυχία ενημέρωσης της αντιστοιχούσας οντότητας του Wikidata!</string>
<string name="menu_set_wallpaper">Ρύθμιση ταπετσαρίας</string>
<string name="wallpaper_set_successfully">Η ταπετσαρία ρυθμίστηκε επιτυχώς!</string>
</resources> </resources>

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Authors: <!-- Authors:
* 2axterix2 * 2axterix2
* Adjen
* Benfutbol10 * Benfutbol10
* Fitoschido * Fitoschido
* Jduranboger * Jduranboger
@ -29,7 +30,7 @@
<string name="login_success">Acceso correcto.</string> <string name="login_success">Acceso correcto.</string>
<string name="login_failed">Acceso fallido.</string> <string name="login_failed">Acceso fallido.</string>
<string name="upload_failed">No se encontró el archivo. Prueba con otro.</string> <string name="upload_failed">No se encontró el archivo. Prueba con otro.</string>
<string name="authentication_failed">Falló la autenticación.</string> <string name="authentication_failed">Falló la autenticación; prueba a acceder otra vez</string>
<string name="uploading_started">Ha comenzado la carga.</string> <string name="uploading_started">Ha comenzado la carga.</string>
<string name="upload_completed_notification_title">Se ha cargado %1$s.</string> <string name="upload_completed_notification_title">Se ha cargado %1$s.</string>
<string name="upload_completed_notification_text">Pulsa para ver tu subida</string> <string name="upload_completed_notification_text">Pulsa para ver tu subida</string>
@ -57,8 +58,7 @@
<string name="add_title_toast">Proporciona un título para este archivo</string> <string name="add_title_toast">Proporciona un título para este archivo</string>
<string name="share_description_hint">Descripción</string> <string name="share_description_hint">Descripción</string>
<string name="login_failed_network">No se pudo iniciar sesión: falla de red</string> <string name="login_failed_network">No se pudo iniciar sesión: falla de red</string>
<string name="login_failed_username">No se pudo iniciar sesión: revisa tu nombre de usuario</string> <string name="login_failed_wrong_credentials">No se puede acceder. Revisa el nombre de usuario y la contraseña</string>
<string name="login_failed_password">No se pudo iniciar sesión: revisa tu contraseña</string>
<string name="login_failed_throttled">Demasiados intentos fallidos. Inténtalo de nuevo en unos minutos.</string> <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_blocked">Lo sentimos, este usuario ha sido bloqueado en Commons</string>
<string name="login_failed_2fa_needed">Debes proporcionar tu código de auntenticación de dos factores.</string> <string name="login_failed_2fa_needed">Debes proporcionar tu código de auntenticación de dos factores.</string>
@ -112,7 +112,7 @@
<string name="preference_license">Licencia predeterminada</string> <string name="preference_license">Licencia predeterminada</string>
<string name="use_previous">Usar título/descripción anteriores</string> <string name="use_previous">Usar título/descripción anteriores</string>
<string name="allow_gps">Obtener ubicación actual automáticamente</string> <string name="allow_gps">Obtener ubicación actual automáticamente</string>
<string name="allow_gps_summary">Recuperar ubicación actual para ofrecer sugerencias de categorías si la imagen no tiene etiquetas geográficas</string> <string name="allow_gps_summary" fuzzy="true">Recuperar ubicación actual para ofrecer sugerencias de categorías si la imagen no tiene etiquetas geográficas</string>
<string name="preference_theme">Modo nocturno</string> <string name="preference_theme">Modo nocturno</string>
<string name="preference_theme_summary">Usar tema oscuro</string> <string name="preference_theme_summary">Usar tema oscuro</string>
<string name="license_name_cc_by_sa_four"> Atribución-CompartirIgual 4.0</string> <string name="license_name_cc_by_sa_four"> Atribución-CompartirIgual 4.0</string>
@ -275,4 +275,7 @@
<string name="image_uploaded_by">Cargada por: %1$s</string> <string name="image_uploaded_by">Cargada por: %1$s</string>
<string name="share_app_title">Compartir aplicación</string> <string name="share_app_title">Compartir aplicación</string>
<string name="share_coordinates_not_present">No se especificaron las coordenadas al seleccionar la imagen</string> <string name="share_coordinates_not_present">No se especificaron las coordenadas al seleccionar la imagen</string>
<string name="error_fetching_nearby_places">Error al recuperar los lugares cercanos.</string>
<string name="appwidget_img">Foto del día</string>
<string name="app_widget_heading">Foto del día</string>
</resources> </resources>

View file

@ -25,7 +25,7 @@
<string name="login_success">Saio hasiera egina</string> <string name="login_success">Saio hasiera egina</string>
<string name="login_failed">Saio hasieran akatsa!</string> <string name="login_failed">Saio hasieran akatsa!</string>
<string name="upload_failed">Fitxategia ez da aurkitu. Mesedez saiatu beste batekin.</string> <string name="upload_failed">Fitxategia ez da aurkitu. Mesedez saiatu beste batekin.</string>
<string name="authentication_failed">Autentifikazioan akatsa!</string> <string name="authentication_failed" fuzzy="true">Autentifikazioan akatsa!</string>
<string name="uploading_started">Igoera hasi da!</string> <string name="uploading_started">Igoera hasi da!</string>
<string name="upload_completed_notification_title">%1$s igotzen!</string> <string name="upload_completed_notification_title">%1$s igotzen!</string>
<string name="upload_completed_notification_text">Ukitu igotakoa ikusteko</string> <string name="upload_completed_notification_text">Ukitu igotakoa ikusteko</string>
@ -48,8 +48,6 @@
<string name="share_title_hint">Izenburua</string> <string name="share_title_hint">Izenburua</string>
<string name="share_description_hint">Deskribapena</string> <string name="share_description_hint">Deskribapena</string>
<string name="login_failed_network">Ezin izan da sartu - sarean akatsa</string> <string name="login_failed_network">Ezin izan da sartu - sarean akatsa</string>
<string name="login_failed_username">Ezin izan da sartu - ziurtatu ezazu zure erabiltzaile izena</string>
<string name="login_failed_password">Ezin izan da sartu - ziurta ezazu zure pasahitza</string>
<string name="login_failed_throttled">Sartzeko saiakera txar gehiegi. Mesedez saiatu zaitez minutu batzuk barru.</string> <string name="login_failed_throttled">Sartzeko saiakera txar gehiegi. Mesedez saiatu zaitez minutu batzuk barru.</string>
<string name="login_failed_blocked">Barka, baina erabiltzaile hau blokeatuta dago Commonsen</string> <string name="login_failed_blocked">Barka, baina erabiltzaile hau blokeatuta dago Commonsen</string>
<string name="login_failed_generic">Saio hasieran akatsa</string> <string name="login_failed_generic">Saio hasieran akatsa</string>

View file

@ -28,7 +28,7 @@
<string name="login_success">ورود موفق!</string> <string name="login_success">ورود موفق!</string>
<string name="login_failed">ورود ناموفق!</string> <string name="login_failed">ورود ناموفق!</string>
<string name="upload_failed">پرونده یافت نشد لطفاً پرونده دیگری را امتحان کنید.</string> <string name="upload_failed">پرونده یافت نشد لطفاً پرونده دیگری را امتحان کنید.</string>
<string name="authentication_failed">تأیید اعتبار انجام نشد!</string> <string name="authentication_failed" fuzzy="true">تأیید اعتبار انجام نشد!</string>
<string name="uploading_started">بارگذاری آغاز شد!</string> <string name="uploading_started">بارگذاری آغاز شد!</string>
<string name="upload_completed_notification_title">%1$s بارگذاری شد!</string> <string name="upload_completed_notification_title">%1$s بارگذاری شد!</string>
<string name="upload_completed_notification_text">برای دیدن بارگذاریتان بر روی صفحه انگشت بزنید</string> <string name="upload_completed_notification_text">برای دیدن بارگذاریتان بر روی صفحه انگشت بزنید</string>
@ -56,8 +56,6 @@
<string name="add_title_toast">لطفاً نامی را برای این پرونده انتخاب کنید</string> <string name="add_title_toast">لطفاً نامی را برای این پرونده انتخاب کنید</string>
<string name="share_description_hint">توضیحات</string> <string name="share_description_hint">توضیحات</string>
<string name="login_failed_network">قادر به ورود نیست - شکست شبکه‌ای</string> <string name="login_failed_network">قادر به ورود نیست - شکست شبکه‌ای</string>
<string name="login_failed_username">ناتوانی در ورود - لطفاً نام کاربریتان را بررسی کنید</string>
<string name="login_failed_password">ناتوانی در ورود - لطفاً گذرواژه‌یتان را بررسی کنید</string>
<string name="login_failed_throttled">تلاش ناموفق بیش از حد. لطفاً چند دقیقهٔ دیگر دوباره تلاش کنید</string> <string name="login_failed_throttled">تلاش ناموفق بیش از حد. لطفاً چند دقیقهٔ دیگر دوباره تلاش کنید</string>
<string name="login_failed_blocked">پوزش، کاربر در ویکی‌انبار بسته شده‌است</string> <string name="login_failed_blocked">پوزش، کاربر در ویکی‌انبار بسته شده‌است</string>
<string name="login_failed_2fa_needed">باید تأیید دومرحله‌ای را فعال کنید.</string> <string name="login_failed_2fa_needed">باید تأیید دومرحله‌ای را فعال کنید.</string>
@ -106,7 +104,7 @@
<string name="preference_license">مجوز پیش‌فرض</string> <string name="preference_license">مجوز پیش‌فرض</string>
<string name="use_previous">از عنوان/توضیحات پیشین استفاده کنید</string> <string name="use_previous">از عنوان/توضیحات پیشین استفاده کنید</string>
<string name="allow_gps">دریافت خودکار موقعیت کنونی</string> <string name="allow_gps">دریافت خودکار موقعیت کنونی</string>
<string name="allow_gps_summary">درحال دریافت موقعیت برای پیشنهاد رده در صورتی که برچسب جغرافیایی وجود نداشته باشد.</string> <string name="allow_gps_summary" fuzzy="true">درحال دریافت موقعیت برای پیشنهاد رده در صورتی که برچسب جغرافیایی وجود نداشته باشد.</string>
<string name="preference_theme">حالت شبانه</string> <string name="preference_theme">حالت شبانه</string>
<string name="preference_theme_summary">استفاده از حالت تیره</string> <string name="preference_theme_summary">استفاده از حالت تیره</string>
<string name="license_name_cc_by_sa_four">CC Attribution-ShareAlike 4.0</string> <string name="license_name_cc_by_sa_four">CC Attribution-ShareAlike 4.0</string>

View file

@ -2,10 +2,11 @@
<!-- Authors: <!-- Authors:
* Nike * Nike
* Olli * Olli
* Pyscowicz
* Silvonen * Silvonen
--> -->
<resources> <resources>
<string name="crash_dialog_title">Commons app on kaatunut</string> <string name="crash_dialog_title">Commons on kaatunut</string>
<string name="crash_dialog_text">Pahoittelemme, virhe tapahtui.</string> <string name="crash_dialog_text">Pahoittelemme, virhe tapahtui.</string>
<string name="crash_dialog_comment_prompt">Kerro meille mitä teit äsken, sähköpostitse. Se auttaa meitä korjaamaan ongelman!</string> <string name="crash_dialog_comment_prompt">Kerro meille mitä teit äsken, sähköpostitse. Se auttaa meitä korjaamaan ongelman!</string>
<string name="crash_dialog_ok_toast">Kiitos!</string> <string name="crash_dialog_ok_toast">Kiitos!</string>

View file

@ -30,7 +30,7 @@
<string name="login_success">Kirjautuminen onnistui!</string> <string name="login_success">Kirjautuminen onnistui!</string>
<string name="login_failed">Kirjautuminen epäonnistui!</string> <string name="login_failed">Kirjautuminen epäonnistui!</string>
<string name="upload_failed">Tiedostoa ei löytynyt. Yritä toista tiedostoa.</string> <string name="upload_failed">Tiedostoa ei löytynyt. Yritä toista tiedostoa.</string>
<string name="authentication_failed">Tunnistautuminen epäonnistui!</string> <string name="authentication_failed" fuzzy="true">Tunnistautuminen epäonnistui!</string>
<string name="uploading_started">Tallentaminen aloitettiin!</string> <string name="uploading_started">Tallentaminen aloitettiin!</string>
<string name="upload_completed_notification_title">%1$s tallennettiin!</string> <string name="upload_completed_notification_title">%1$s tallennettiin!</string>
<string name="upload_completed_notification_text">Napauta katsoaksesi tallennusta</string> <string name="upload_completed_notification_text">Napauta katsoaksesi tallennusta</string>
@ -58,8 +58,6 @@
<string name="add_title_toast">Anna tälle tiedostolle otsikko</string> <string name="add_title_toast">Anna tälle tiedostolle otsikko</string>
<string name="share_description_hint">Kuvaus</string> <string name="share_description_hint">Kuvaus</string>
<string name="login_failed_network">Kirjautuminen epäonnistui - verkkovirhe</string> <string name="login_failed_network">Kirjautuminen epäonnistui - verkkovirhe</string>
<string name="login_failed_username">Kirjautuminen epäonnistui - tarkista käyttäjätunnus</string>
<string name="login_failed_password">Kirjautuminen epäonnistui - tarkista salasanasi</string>
<string name="login_failed_throttled">Liikaa epäonnistuneita yrityksiä. Yritä uudelleen parin minuutin kuluttua.</string> <string name="login_failed_throttled">Liikaa epäonnistuneita yrityksiä. Yritä uudelleen parin minuutin kuluttua.</string>
<string name="login_failed_blocked">Pahoittelut, tämä käyttäjä on estetty Commonsissa</string> <string name="login_failed_blocked">Pahoittelut, tämä käyttäjä on estetty Commonsissa</string>
<string name="login_failed_2fa_needed">Anna kaksivaiheisen tunnistuksen koodi.</string> <string name="login_failed_2fa_needed">Anna kaksivaiheisen tunnistuksen koodi.</string>
@ -112,7 +110,7 @@
<string name="preference_license">Oletuslisenssi</string> <string name="preference_license">Oletuslisenssi</string>
<string name="use_previous">Käytä edellistä otsikkoa/kuvausta</string> <string name="use_previous">Käytä edellistä otsikkoa/kuvausta</string>
<string name="allow_gps">Hae tämänhetkinen sijainti automaattisesti</string> <string name="allow_gps">Hae tämänhetkinen sijainti automaattisesti</string>
<string name="allow_gps_summary">Nouda nykyinen sijainti asettaaksesi käyttöön luokkaehdotuksia jos kuva ei ole paikkamerkitty</string> <string name="allow_gps_summary" fuzzy="true">Nouda nykyinen sijainti asettaaksesi käyttöön luokkaehdotuksia jos kuva ei ole paikkamerkitty</string>
<string name="preference_theme">Yötila</string> <string name="preference_theme">Yötila</string>
<string name="preference_theme_summary">Käytä tummaa teemaa</string> <string name="preference_theme_summary">Käytä tummaa teemaa</string>
<string name="license_name_cc_by_sa_four">Nimeä-JaaSamoin 4.0</string> <string name="license_name_cc_by_sa_four">Nimeä-JaaSamoin 4.0</string>
@ -162,8 +160,8 @@
<string name="detail_description_empty">Ei kuvausta</string> <string name="detail_description_empty">Ei kuvausta</string>
<string name="detail_license_empty">Tuntematon lisenssi</string> <string name="detail_license_empty">Tuntematon lisenssi</string>
<string name="menu_refresh">Päivitä</string> <string name="menu_refresh">Päivitä</string>
<string name="read_storage_permission_rationale">Vaadittu oikeus: Ulkoisen tallennustilan luku. Appi ei voi päästä galleriaasi ilman tätä oikeutta.</string> <string name="read_storage_permission_rationale">Vaadittu oikeus: Ulkoisen tallennustilan luku. Sovellus ei voi päästä galleriaasi ilman tätä oikeutta.</string>
<string name="write_storage_permission_rationale">Vaadittava lupa: Kirjoita ulkoiseen tallennustilaan. Appi ei voi päästä kameraasi ilman tätä oikeutta.</string> <string name="write_storage_permission_rationale">Vaadittu oikeus: Kirjoita ulkoiseen tallennustilaan. Sovellus ei voi päästä kameraasi ilman tätä oikeutta.</string>
<string name="location_permission_rationale">Valinnainen lupa: Saada tämänhetkinen sijainti loukkasuosituksia varten.</string> <string name="location_permission_rationale">Valinnainen lupa: Saada tämänhetkinen sijainti loukkasuosituksia varten.</string>
<string name="ok">OK</string> <string name="ok">OK</string>
<string name="title_activity_nearby">Lähellä olevat paikat</string> <string name="title_activity_nearby">Lähellä olevat paikat</string>
@ -262,4 +260,8 @@
<string name="about_translate_proceed">Jatka</string> <string name="about_translate_proceed">Jatka</string>
<string name="about_translate_cancel">Peruuta</string> <string name="about_translate_cancel">Peruuta</string>
<string name="retry">Yritä uudelleen</string> <string name="retry">Yritä uudelleen</string>
<string name="showcase_view_got_it_button">Selvä!</string>
<string name="no_images_found">Kuvia ei löytynyt!</string>
<string name="image_uploaded_by">Tallentanut: %1$s</string>
<string name="share_app_title">Jaa sovellus</string>
</resources> </resources>

View file

@ -12,7 +12,7 @@
<string name="logging_in_message" fuzzy="true">Vinarliga bíða…</string> <string name="logging_in_message" fuzzy="true">Vinarliga bíða…</string>
<string name="login_success">Innritan væleydnað!</string> <string name="login_success">Innritan væleydnað!</string>
<string name="login_failed">Innritan miseydnaðist</string> <string name="login_failed">Innritan miseydnaðist</string>
<string name="authentication_failed">Góðkenning miseydnaðist!</string> <string name="authentication_failed" fuzzy="true">Góðkenning miseydnaðist!</string>
<string name="uploading_started">Upplóting er byrjað!</string> <string name="uploading_started">Upplóting er byrjað!</string>
<string name="upload_completed_notification_title">%1$s er lagt út!</string> <string name="upload_completed_notification_title">%1$s er lagt út!</string>
<string name="upload_completed_notification_text">Trýst fyri at síggja tað sum tú legði út</string> <string name="upload_completed_notification_text">Trýst fyri at síggja tað sum tú legði út</string>
@ -34,8 +34,6 @@
<string name="share_title_hint">Heiti</string> <string name="share_title_hint">Heiti</string>
<string name="share_description_hint">Frágreiðing</string> <string name="share_description_hint">Frágreiðing</string>
<string name="login_failed_network">Ómøguligt at rita inn - feilur í netsambandinum</string> <string name="login_failed_network">Ómøguligt at rita inn - feilur í netsambandinum</string>
<string name="login_failed_username">Ómøguligt at rita inn - vinarliga eftirkanna títt brúkaranavn</string>
<string name="login_failed_password">Ómøguligt at rita inn - vinarliga kanna eftir, um títt loyniorð er rætt</string>
<string name="login_failed_throttled" fuzzy="true">Ov nógv miseydnaðar royndir. Vinarliga royn aftur um fáir minuttir</string> <string name="login_failed_throttled" fuzzy="true">Ov nógv miseydnaðar royndir. Vinarliga royn aftur um fáir minuttir</string>
<string name="login_failed_blocked">Haldið okkum tilgóðar, hesin brúkari er blivin sperraður á Commons</string> <string name="login_failed_blocked">Haldið okkum tilgóðar, hesin brúkari er blivin sperraður á Commons</string>
<string name="login_failed_generic">Login miseydnaðist</string> <string name="login_failed_generic">Login miseydnaðist</string>

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Authors: <!-- Authors:
* Fitoschido * Fitoschido
* Friday83260
* Gomoko * Gomoko
* Happy13241 * Happy13241
* Jean-Frédéric * Jean-Frédéric
@ -37,7 +38,7 @@
<string name="login_success">Connexion réussie !</string> <string name="login_success">Connexion réussie !</string>
<string name="login_failed">Échec de la connexion !</string> <string name="login_failed">Échec de la connexion !</string>
<string name="upload_failed">Fichier non trouvé. Veuillez en essayer un autre.</string> <string name="upload_failed">Fichier non trouvé. Veuillez en essayer un autre.</string>
<string name="authentication_failed">Échec de lauthentification !</string> <string name="authentication_failed">Échec de l\'authentification, veuillez vous reconnecter s\'il vous plait</string>
<string name="uploading_started">Téléversement démarré !</string> <string name="uploading_started">Téléversement démarré !</string>
<string name="upload_completed_notification_title">%1$s téléversés !</string> <string name="upload_completed_notification_title">%1$s téléversés !</string>
<string name="upload_completed_notification_text">Appuyer pour voir votre téléversement</string> <string name="upload_completed_notification_text">Appuyer pour voir votre téléversement</string>
@ -65,8 +66,7 @@
<string name="add_title_toast">Veuillez donner un titre à ce fichier</string> <string name="add_title_toast">Veuillez donner un titre à ce fichier</string>
<string name="share_description_hint">Description</string> <string name="share_description_hint">Description</string>
<string name="login_failed_network">Impossible de se connecter — panne de réseau</string> <string name="login_failed_network">Impossible de se connecter — panne de réseau</string>
<string name="login_failed_username">Impossible de se connecter — veuillez vérifier votre nom dutilisateur</string> <string name="login_failed_wrong_credentials">Impossible de se connecter — veuillez vérifier votre nom dutilisateur et votre mot de passe</string>
<string name="login_failed_password">Impossible de se connecter — veuillez vérifier votre mot de passe</string>
<string name="login_failed_throttled">Trop de tentatives infructueuses. Veuillez réessayer dans quelques minutes.</string> <string name="login_failed_throttled">Trop de tentatives infructueuses. Veuillez réessayer dans quelques minutes.</string>
<string name="login_failed_blocked">Désolé, cet utilisateur a été bloqué dans Commons</string> <string name="login_failed_blocked">Désolé, cet utilisateur a été bloqué dans Commons</string>
<string name="login_failed_2fa_needed">Vous devez fournir votre code dauthentification à deux facteurs.</string> <string name="login_failed_2fa_needed">Vous devez fournir votre code dauthentification à deux facteurs.</string>
@ -120,7 +120,7 @@
<string name="preference_license">Licence par défaut</string> <string name="preference_license">Licence par défaut</string>
<string name="use_previous">Utiliser le titre ou la description précédent</string> <string name="use_previous">Utiliser le titre ou la description précédent</string>
<string name="allow_gps">Obtenir automatiquement lemplacement actuel</string> <string name="allow_gps">Obtenir automatiquement lemplacement actuel</string>
<string name="allow_gps_summary">Récupérer lemplacement actuel pour proposer des suggestions de catégorie si limage nest pas marquée géographiquement</string> <string name="allow_gps_summary">Récupère lemplacement actuel si limage nest pas marquée géographiquement, et marque géographiquement limage avec. Avertissement : ceci dévoilera votre emplacement actuel.</string>
<string name="preference_theme">Mode de nuit</string> <string name="preference_theme">Mode de nuit</string>
<string name="preference_theme_summary">Utiliser un thème sombre</string> <string name="preference_theme_summary">Utiliser un thème sombre</string>
<string name="license_name_cc_by_sa_four"> Attribution-ShareAlike 4.0</string> <string name="license_name_cc_by_sa_four"> Attribution-ShareAlike 4.0</string>
@ -289,4 +289,11 @@
<string name="image_uploaded_by">Importé par:%1$s</string> <string name="image_uploaded_by">Importé par:%1$s</string>
<string name="share_app_title">Partager les applications</string> <string name="share_app_title">Partager les applications</string>
<string name="share_coordinates_not_present">Les coordonnées n\'ont pas été spécifiées pendant la sélection de l\'image</string> <string name="share_coordinates_not_present">Les coordonnées n\'ont pas été spécifiées pendant la sélection de l\'image</string>
<string name="error_fetching_nearby_places">Erreur durant l\'exploration du voisinage.</string>
<string name="appwidget_img">Image du jour</string>
<string name="app_widget_heading">Image du jour</string>
<string name="successful_wikidata_edit">Image bien ajoutée à %1$s sur Wikidata!</string>
<string name="wikidata_edit_failure">Échec de la mise à jour de l\'entité Wikidata correspondante!</string>
<string name="menu_set_wallpaper">Définir le papier-peint</string>
<string name="wallpaper_set_successfully">Papier-peint configuré avec succès!</string>
</resources> </resources>

View file

@ -15,7 +15,7 @@
<string name="login_success">Uunmeldin hää loket!</string> <string name="login_success">Uunmeldin hää loket!</string>
<string name="login_failed">Bi\'t uunmeldin as wat skiaf gingen.</string> <string name="login_failed">Bi\'t uunmeldin as wat skiaf gingen.</string>
<string name="upload_failed">Datei ei fünjen. Ferschük det mä en ööder datei.</string> <string name="upload_failed">Datei ei fünjen. Ferschük det mä en ööder datei.</string>
<string name="authentication_failed">Dü küdst ei gudkäänd wurd!</string> <string name="authentication_failed" fuzzy="true">Dü küdst ei gudkäänd wurd!</string>
<string name="uploading_started">Huuchschüüren hää begand!</string> <string name="uploading_started">Huuchschüüren hää begand!</string>
<string name="upload_completed_notification_title">%1$s huuchschüürd!</string> <string name="upload_completed_notification_title">%1$s huuchschüürd!</string>
<string name="upload_completed_notification_text">Tipe, am din huuchschüürd bil uuntulukin</string> <string name="upload_completed_notification_text">Tipe, am din huuchschüürd bil uuntulukin</string>
@ -42,8 +42,6 @@
<string name="share_title_hint">Tiitel</string> <string name="share_title_hint">Tiitel</string>
<string name="share_description_hint">Beskriiwang</string> <string name="share_description_hint">Beskriiwang</string>
<string name="login_failed_network">Bi\'t uunmeldin as wat skiaf gingen - näätwerk-feeler</string> <string name="login_failed_network">Bi\'t uunmeldin as wat skiaf gingen - näätwerk-feeler</string>
<string name="login_failed_username">Bi\'t uunmeldin as wat skiaf gingen - luke ans efter di brükernööm</string>
<string name="login_failed_password">Bi\'t uunmeldin as wat skiaf gingen - luke ans efter det paaswurd</string>
<string name="login_failed_throttled">Tu fölsis fersoocht saner lok. Ferschük det uun hög minüüten noch ans nei.</string> <string name="login_failed_throttled">Tu fölsis fersoocht saner lok. Ferschük det uun hög minüüten noch ans nei.</string>
<string name="login_failed_blocked">Didiar brüker as üüb Commons speret wurden.</string> <string name="login_failed_blocked">Didiar brüker as üüb Commons speret wurden.</string>
<string name="login_failed_2fa_needed">Dü skel dan code för\'t tau-straal-gudkäänen (2FA) uundu.</string> <string name="login_failed_2fa_needed">Dü skel dan code för\'t tau-straal-gudkäänen (2FA) uundu.</string>
@ -94,7 +92,7 @@
<string name="preference_license" fuzzy="true">Lisens</string> <string name="preference_license" fuzzy="true">Lisens</string>
<string name="use_previous">Ual tiitel/beskriiwang brük</string> <string name="use_previous">Ual tiitel/beskriiwang brük</string>
<string name="allow_gps">Aktuel plak automaatisk ufrep</string> <string name="allow_gps">Aktuel plak automaatisk ufrep</string>
<string name="allow_gps_summary">Rept di aktuel plak uf, am kategoriin föörtuslauen, wan det bil nian geotags hää.</string> <string name="allow_gps_summary" fuzzy="true">Rept di aktuel plak uf, am kategoriin föörtuslauen, wan det bil nian geotags hää.</string>
<string name="preference_theme">Naacht muude</string> <string name="preference_theme">Naacht muude</string>
<string name="preference_theme_summary">Jonk skak brük</string> <string name="preference_theme_summary">Jonk skak brük</string>
<string name="license_name_cc_by_sa_four">Attribution-ShareAlike 4.0</string> <string name="license_name_cc_by_sa_four">Attribution-ShareAlike 4.0</string>

View file

@ -27,7 +27,7 @@
<string name="login_success">Accedeu correctamente!</string> <string name="login_success">Accedeu correctamente!</string>
<string name="login_failed">Erro durante o inición de sesión!</string> <string name="login_failed">Erro durante o inición de sesión!</string>
<string name="upload_failed">Ficheiro non atopado. Por favor, probe con outro.</string> <string name="upload_failed">Ficheiro non atopado. Por favor, probe con outro.</string>
<string name="authentication_failed">Erro durante a autenticación!</string> <string name="authentication_failed" fuzzy="true">Erro durante a autenticación!</string>
<string name="uploading_started">A carga comezou!</string> <string name="uploading_started">A carga comezou!</string>
<string name="upload_completed_notification_title">Cargouse \"%1$s\"!</string> <string name="upload_completed_notification_title">Cargouse \"%1$s\"!</string>
<string name="upload_completed_notification_text">Prema para ollar a súa carga</string> <string name="upload_completed_notification_text">Prema para ollar a súa carga</string>
@ -55,8 +55,7 @@
<string name="add_title_toast">Por favor, proporcione un título para este ficheiro</string> <string name="add_title_toast">Por favor, proporcione un título para este ficheiro</string>
<string name="share_description_hint">Descrición</string> <string name="share_description_hint">Descrición</string>
<string name="login_failed_network">Erro ao acceder ao sistema: Fallou a rede</string> <string name="login_failed_network">Erro ao acceder ao sistema: Fallou a rede</string>
<string name="login_failed_username">Erro ao acceder ao sistema: Comprobe o seu nome de usuario</string> <string name="login_failed_wrong_credentials">Non se pode acceder. Revise o nome de usuario e o contrasinal</string>
<string name="login_failed_password">Erro ao acceder ao sistema: Comprobe o seu contrasinal</string>
<string name="login_failed_throttled">Demasiados intentos incorrectos. Inténteo de novo nuns minutos.</string> <string name="login_failed_throttled">Demasiados intentos incorrectos. Inténteo de novo nuns minutos.</string>
<string name="login_failed_blocked">Sentímolo, este usuario está bloqueado en Commons</string> <string name="login_failed_blocked">Sentímolo, este usuario está bloqueado en Commons</string>
<string name="login_failed_2fa_needed">Debe proporcionar o seu código de autenticación de dous factores.</string> <string name="login_failed_2fa_needed">Debe proporcionar o seu código de autenticación de dous factores.</string>
@ -110,7 +109,7 @@
<string name="preference_license">Licenza por defecto</string> <string name="preference_license">Licenza por defecto</string>
<string name="use_previous">Usar o título ou a descrición anterior</string> <string name="use_previous">Usar o título ou a descrición anterior</string>
<string name="allow_gps">Obter automaticamente a localización actual</string> <string name="allow_gps">Obter automaticamente a localización actual</string>
<string name="allow_gps_summary">Obter a localización actual para ofrecer suxestións de categoría se a imaxe non está xeolocalizada</string> <string name="allow_gps_summary" fuzzy="true">Obter a localización actual para ofrecer suxestións de categoría se a imaxe non está xeolocalizada</string>
<string name="preference_theme">Modo nocturno</string> <string name="preference_theme">Modo nocturno</string>
<string name="preference_theme_summary">Usar tema escuro</string> <string name="preference_theme_summary">Usar tema escuro</string>
<string name="license_name_cc_by_sa_four"> Recoñecemento-CompartirIgual 4.0</string> <string name="license_name_cc_by_sa_four"> Recoñecemento-CompartirIgual 4.0</string>
@ -269,4 +268,16 @@
<string name="about_translate_proceed">Proceder</string> <string name="about_translate_proceed">Proceder</string>
<string name="about_translate_cancel">Cancelar</string> <string name="about_translate_cancel">Cancelar</string>
<string name="retry">Reintentar</string> <string name="retry">Reintentar</string>
<string name="showcase_view_got_it_button">Entendido!</string>
<string name="showcase_view_whole_nearby_activity">Hai sitios preto de vostede que precisan fotos para ilustrar os seus artigos de Wikipedia</string>
<string name="showcase_view_list_icon">Premendo neste botón aparecerá unha lista destes lugares</string>
<string name="showcase_view_plus_fab">Pode cargar unha imaxe de calquera lugar dende a galería ou a cámara</string>
<string name="no_images_found">Non se atopou ningunha imaxeǃ</string>
<string name="error_loading_images">Houbo un erro ó subir as imaxes.</string>
<string name="image_uploaded_by">Subida porː %1$s</string>
<string name="share_app_title">Compartir a aplicación</string>
<string name="share_coordinates_not_present">Non se indicaron as coordenadas ó seleccionar a imaxe</string>
<string name="error_fetching_nearby_places">Erro ó procurar os lugares próximos.</string>
<string name="menu_set_wallpaper">Definir imaxe de fondo</string>
<string name="wallpaper_set_successfully">A imaxe de fondo configurouse correctamenteǃ</string>
</resources> </resources>

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