This commit is contained in:
misaochan 2018-06-06 18:59:37 +10:00
commit a458b8ad06
149 changed files with 2572 additions and 1220 deletions

View file

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

View file

@ -1,3 +1,7 @@
## Title (required)
Fixes #{GitHub issue number and title (Please do not forget adding title) }
## Description (required)
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)}
_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

@ -11,7 +11,6 @@ dependencies {
implementation 'fr.avianey.com.viewpagerindicator:library:2.4.1.1@aar'
implementation 'in.yuvi:http.fluent:1.3'
implementation 'com.github.chrisbanes:PhotoView:2.0.0'
implementation 'com.android.volley:volley:1.0.0'
implementation 'ch.acra:acra:4.9.2'
implementation 'org.mediawiki:api:1.3'
implementation 'commons-codec:commons-codec:1.10'
@ -20,7 +19,7 @@ dependencies {
implementation 'com.jakewharton.timber:timber:4.5.1'
implementation 'info.debatty:java-string-similarity:0.24'
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.1.0'
implementation ('com.mapbox.mapboxsdk:mapbox-android-sdk:5.4.1@aar'){
transitive=true
}
@ -69,10 +68,16 @@ dependencies {
testImplementation 'com.nhaarman:mockito-kotlin:1.5.0'
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 'com.squareup.okhttp3:mockwebserver:3.8.1'
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"
releaseImplementation "com.squareup.leakcanary:leakcanary-android-no-op:$LEAK_CANARY"
@ -117,7 +122,7 @@ android {
buildTypes {
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.
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt', 'proguard-glide.txt'
}
debug {
applicationIdSuffix ".debug"
@ -130,6 +135,7 @@ android {
productFlavors {
prod {
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", "IMAGE_URL_BASE", "\"https://upload.wikimedia.org/wikipedia/commons\""
buildConfigField "String", "HOME_URL", "\"https://commons.wikimedia.org/wiki/\""
@ -146,6 +152,7 @@ android {
beta {
// What values do we need to hit the BETA versions of the site / api ?
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", "IMAGE_URL_BASE", "\"https://upload.beta.wmflabs.org/wikipedia/commons\""
buildConfigField "String", "HOME_URL", "\"https://commons.wikimedia.beta.wmflabs.org/wiki/\""

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
-keep class org.apache.http.** { *; }
-dontwarn org.apache.http.**
-keep class fr.free.nrw.commons.upload.MwVolleyApi$Page {*;}
-keep class android.support.v7.widget.ShareActionProvider { *; }

View file

@ -15,6 +15,7 @@
<uses-permission android:name="android.permission.MANAGE_DOCUMENTS" />
<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.SET_WALLPAPER"/>
<!-- Needed only if your app targets Android 5.0 (API level 21) or higher. -->
<uses-feature android:name="android.hardware.location.gps" />
@ -26,10 +27,10 @@
android:theme="@style/LightAppTheme"
android:supportsRtl="true" >
<activity android:name="org.acra.CrashReportDialog"
android:theme="@android:style/Theme.Dialog"
android:launchMode="singleInstance"
android:excludeFromRecents="true"
android:finishOnTaskLaunch="true" />
android:theme="@android:style/Theme.Dialog"
android:launchMode="singleInstance"
android:excludeFromRecents="true"
android:finishOnTaskLaunch="true" />
<activity android:name=".auth.LoginActivity">
<intent-filter>

View file

@ -1,6 +1,5 @@
package fr.free.nrw.commons;
import android.app.Application;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.sqlite.SQLiteDatabase;
@ -21,13 +20,14 @@ import java.io.File;
import javax.inject.Inject;
import javax.inject.Named;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.category.CategoryDao;
import fr.free.nrw.commons.contributions.ContributionDao;
import fr.free.nrw.commons.data.DBOpenHelper;
import fr.free.nrw.commons.di.ApplicationlessInjection;
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.schedulers.Schedulers;
import timber.log.Timber;

View file

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

View file

@ -271,11 +271,11 @@ public class LoginActivity extends AccountAuthenticatorActivity {
showMessageAndCancelDialog(R.string.login_failed_network);
} else if (result.toLowerCase(Locale.getDefault()).contains("nosuchuser".toLowerCase()) || result.toLowerCase().contains("noname".toLowerCase())) {
// Matches nosuchuser, nosuchusershort, noname
showMessageAndCancelDialog(R.string.login_failed_username);
showMessageAndCancelDialog(R.string.login_failed_wrong_credentials);
emptySensitiveEditFields();
} else if (result.toLowerCase(Locale.getDefault()).contains("wrongpassword".toLowerCase())) {
// Matches wrongpassword, wrongpasswordempty
showMessageAndCancelDialog(R.string.login_failed_password);
showMessageAndCancelDialog(R.string.login_failed_wrong_credentials);
emptySensitiveEditFields();
} else if (result.toLowerCase(Locale.getDefault()).contains("throttle".toLowerCase())) {
// Matches unknown throttle error codes

View file

@ -7,18 +7,25 @@ import java.util.ArrayList;
import java.util.Arrays;
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;
@Singleton
public class CacheController {
private final GpsCategoryModel gpsCategoryModel;
private final QuadTree<List<String>> quadTree;
private double x, y;
private QuadTree<List<String>> quadTree;
private double xMinus, xPlus, yMinus, yPlus;
private static final int EARTH_RADIUS = 6378137;
public CacheController() {
@Inject
CacheController(GpsCategoryModel gpsCategoryModel) {
this.gpsCategoryModel = gpsCategoryModel;
quadTree = new QuadTree<>(-180, -90, +180, +90);
}
@ -31,8 +38,8 @@ public class CacheController {
public void cacheCategory() {
List<String> pointCatList = new ArrayList<>();
if (MwVolleyApi.GpsCatExists.getGpsCatExists()) {
pointCatList.addAll(MwVolleyApi.getGpsCat());
if (gpsCategoryModel.getGpsCatExists()) {
pointCatList.addAll(gpsCategoryModel.getCategoryList());
Timber.d("Categories being cached: %s", pointCatList);
} else {
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
public void convertCoordRange() {
private void convertCoordRange() {
//Position, decimal degrees
double lat = y;
double lon = x;

View file

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

View file

@ -224,4 +224,14 @@ public class CategoryImagesListFragment extends DaggerFragment {
public ListAdapter 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 String decimalCoords;
private boolean isMultiple;
private String wikiDataEntityId;
public Contribution(Uri contentUri, String filename, Uri localUri, String imageUrl, Date timestamp,
int state, long dataLength, Date dateUploaded, long transferred,
@ -222,4 +223,17 @@ public class Contribution extends Media {
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);
}
public void handleImagePicked(int requestCode, Intent data, boolean isDirectUpload) {
public void handleImagePicked(int requestCode, Intent data, boolean isDirectUpload, String wikiDataEntityId) {
FragmentActivity activity = fragment.getActivity();
Timber.d("handleImagePicked() called with onActivityResult()");
Intent shareIntent = new Intent(activity, ShareActivity.class);
@ -102,9 +102,6 @@ public class ContributionController {
shareIntent.setType(activity.getContentResolver().getType(imageData));
shareIntent.putExtra(EXTRA_STREAM, imageData);
shareIntent.putExtra(EXTRA_SOURCE, SOURCE_GALLERY);
if (isDirectUpload) {
shareIntent.putExtra("isDirectUpload", true);
}
break;
case SELECT_FROM_CAMERA:
//FIXME: Find out appropriate mime type
@ -113,9 +110,6 @@ public class ContributionController {
shareIntent.setType("image/jpeg");
shareIntent.putExtra(EXTRA_STREAM, lastGeneratedCaptureUri);
shareIntent.putExtra(EXTRA_SOURCE, SOURCE_CAMERA);
if (isDirectUpload) {
shareIntent.putExtra("isDirectUpload", true);
}
break;
default:
@ -123,6 +117,10 @@ public class ContributionController {
}
Timber.i("Image selected");
try {
shareIntent.putExtra("isDirectUpload", isDirectUpload);
if (wikiDataEntityId != null && !wikiDataEntityId.equals("")) {
shareIntent.putExtra("wikiDataEntityId", wikiDataEntityId);
}
activity.startActivity(shareIntent);
} catch (SecurityException e) {
Timber.e(e, "Security Exception");

View file

@ -127,7 +127,7 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment {
if (resultCode == RESULT_OK) {
Timber.d("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
requestCode, resultCode, data);
controller.handleImagePicked(requestCode, data, false);
controller.handleImagePicked(requestCode, data, false, null);
} else {
Timber.e("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
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.MediaWikiImageView;
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.delete.DeleteTask;
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.upload.FileProcessor;
import fr.free.nrw.commons.settings.SettingsFragment;
@Singleton
@Component(modules = {
CommonsApplicationModule.class,
NetworkingModule.class,
AndroidInjectionModule.class,
AndroidSupportInjectionModule.class,
ActivityBuilderModule.class,
@ -47,6 +48,8 @@ public interface CommonsApplicationComponent extends AndroidInjector<Application
void inject(PlaceRenderer placeRenderer);
void inject(FileProcessor fileProcessor);
@Component.Builder
@SuppressWarnings({"WeakerAccess", "unused"})
interface Builder {

View file

@ -13,17 +13,18 @@ import javax.inject.Singleton;
import dagger.Module;
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.SessionManager;
import fr.free.nrw.commons.caching.CacheController;
import fr.free.nrw.commons.data.DBOpenHelper;
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.nearby.NearbyPlaces;
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 fr.free.nrw.commons.contributions.ContributionsContentProvider.CONTRIBUTION_AUTHORITY;
@ -33,7 +34,6 @@ import static fr.free.nrw.commons.modifications.ModificationsContentProvider.MOD
@SuppressWarnings({"WeakerAccess", "unused"})
public class CommonsApplicationModule {
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;
@ -117,37 +117,12 @@ public class CommonsApplicationModule {
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
@Singleton
public LocationServiceManager provideLocationServiceManager(Context 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
@Singleton
public DBOpenHelper provideDBOpenHelper(Context context) {
@ -165,4 +140,10 @@ public class CommonsApplicationModule {
public LruCache<String, String> provideLruCache() {
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

@ -284,6 +284,7 @@ public class LocationServiceManager implements LocationListener {
LOCATION_SIGNIFICANTLY_CHANGED, //Went out of borders of nearby markers
LOCATION_SLIGHTLY_CHANGED, //User might be walking or driving
LOCATION_NOT_CHANGED,
PERMISSION_JUST_GRANTED
PERMISSION_JUST_GRANTED,
MAP_UPDATED
}
}

View file

@ -35,6 +35,9 @@ import java.util.Locale;
import javax.inject.Inject;
import javax.inject.Provider;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import fr.free.nrw.commons.License;
import fr.free.nrw.commons.LicenseList;
import fr.free.nrw.commons.Media;
@ -56,16 +59,16 @@ import static android.widget.Toast.LENGTH_SHORT;
public class MediaDetailFragment extends CommonsDaggerSupportFragment {
private boolean editable;
private boolean isFeaturedMedia;
private boolean isCategoryImage;
private MediaDetailPagerFragment.MediaDetailProvider detailProvider;
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();
Bundle state = new Bundle();
state.putBoolean("editable", editable);
state.putBoolean("isFeaturedMedia", isFeaturedMedia);
state.putBoolean("isCategoryImage", isCategoryImage);
state.putInt("index", index);
state.putInt("listIndex", 0);
state.putInt("listTop", 0);
@ -128,7 +131,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
super.onSaveInstanceState(outState);
outState.putInt("index", index);
outState.putBoolean("editable", editable);
outState.putBoolean("isFeaturedMedia", isFeaturedMedia);
outState.putBoolean("isCategoryImage", isCategoryImage);
getScrollPosition();
outState.putInt("listTop", initialListTop);
@ -144,12 +147,12 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
if (savedInstanceState != null) {
editable = savedInstanceState.getBoolean("editable");
isFeaturedMedia = savedInstanceState.getBoolean("isFeaturedMedia");
isCategoryImage = savedInstanceState.getBoolean("isCategoryImage");
index = savedInstanceState.getInt("index");
initialListTop = savedInstanceState.getInt("listTop");
} else {
editable = getArguments().getBoolean("editable");
isFeaturedMedia = getArguments().getBoolean("isFeaturedMedia");
isCategoryImage = getArguments().getBoolean("isCategoryImage");
index = getArguments().getInt("index");
initialListTop = 0;
}
@ -161,7 +164,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
ButterKnife.bind(this,view);
if (isFeaturedMedia){
if (isCategoryImage){
authorLayout.setVisibility(VISIBLE);
} else {
authorLayout.setVisibility(GONE);
@ -328,7 +331,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
if (!TextUtils.isEmpty(licenseLink(media))) {
openWebBrowser(licenseLink(media));
} else {
if(isFeaturedMedia) {
if(isCategoryImage) {
Timber.d("Unable to fetch license URL for %s", media.getLicense());
} else {
Toast toast = Toast.makeText(getContext(), getString(R.string.null_url), Toast.LENGTH_SHORT);
@ -503,8 +506,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
if (media.getRequestedDeletion()){
delete.setVisibility(GONE);
nominatedForDeletion.setVisibility(VISIBLE);
}
else{
} else if (!isCategoryImage) {
delete.setVisibility(VISIBLE);
nominatedForDeletion.setVisibility(GONE);
}

View file

@ -2,9 +2,11 @@ package fr.free.nrw.commons.media;
import android.annotation.SuppressLint;
import android.app.DownloadManager;
import android.app.WallpaperManager;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.DataSetObserver;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
@ -26,6 +28,8 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import java.io.IOException;
import butterknife.BindView;
import butterknife.ButterKnife;
import javax.inject.Inject;
@ -38,12 +42,15 @@ import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.contributions.ContributionsActivity;
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
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.content.Context.DOWNLOAD_SERVICE;
import static android.content.Intent.ACTION_VIEW;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.widget.Toast.LENGTH_SHORT;
import static com.mapbox.mapboxsdk.Mapbox.getApplicationContext;
public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment implements ViewPager.OnPageChangeListener {
@ -140,6 +147,10 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
// Download
downloadMedia(m);
return true;
case R.id.menu_set_as_wallpaper:
// Set wallpaper
setWallpaper(m);
return true;
case R.id.menu_retry_current_image:
// Retry
((ContributionsActivity) getActivity()).retryUpload(pager.getCurrentItem());
@ -155,6 +166,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.
* 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.mediawiki.api.ApiResult;
import org.mediawiki.api.MWApi;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import java.io.IOException;
@ -62,6 +64,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
private static final String THUMB_SIZE = "640";
private AbstractHttpClient httpClient;
private MWApi api;
private MWApi wikidataApi;
private Context context;
private SharedPreferences defaultPreferences;
private SharedPreferences categoryPreferences;
@ -69,6 +72,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
public ApacheHttpClientMediaWikiApi(Context context,
String apiURL,
String wikidatApiURL,
SharedPreferences defaultPreferences,
SharedPreferences categoryPreferences,
Gson gson) {
@ -82,6 +86,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
params.setParameter(CoreProtocolPNames.USER_AGENT, getUserAgent());
httpClient = new DefaultHttpClient(cm, params);
api = new MWApi(apiURL, httpClient);
wikidataApi = new MWApi(wikidatApiURL, httpClient);
this.defaultPreferences = defaultPreferences;
this.categoryPreferences = categoryPreferences;
this.gson = gson;
@ -206,6 +211,15 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
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
public boolean fileExistsWithName(String fileName) throws IOException {
return api.action("query")
@ -351,6 +365,98 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
}).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
@NonNull
public Observable<String> searchTitles(String title, int searchCatsLimit) {
@ -444,8 +550,8 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
.param("notprop", "list")
.param("format", "xml")
.param("meta", "notifications")
// .param("meta", "notifications")
.param("notformat", "model")
.param("notwikis", "wikidatawiki|commonswiki|enwiki")
.get()
.getNode("/api/query/notifications/list");
} catch (IOException e) {
@ -586,6 +692,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
String resultStatus = result.getString("/api/upload/@result");
if (!resultStatus.equals("Success")) {
String errorCode = result.getString("/api/error/@code");
Timber.e(errorCode);
return new UploadResult(resultStatus, errorCode);
} else {
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 getWikidataCsrfToken() throws IOException;
String getCentralAuthToken() throws IOException;
boolean fileExistsWithName(String fileName) throws IOException;
boolean pageExists(String pageName) throws IOException;
@ -49,6 +53,12 @@ public interface MediaWikiApi {
@Nullable
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
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

@ -42,11 +42,13 @@ import butterknife.ButterKnife;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.location.LatLng;
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.theme.NavigationBaseActivity;
import fr.free.nrw.commons.utils.NetworkUtils;
import fr.free.nrw.commons.utils.UriSerializer;
import fr.free.nrw.commons.utils.ViewUtil;
import fr.free.nrw.commons.wikidata.WikidataEditListener;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
@ -55,8 +57,12 @@ import timber.log.Timber;
import uk.co.deanwild.materialshowcaseview.IShowcaseListener;
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;
@ -76,6 +82,8 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
LocationServiceManager locationManager;
@Inject
NearbyController nearbyController;
@Inject WikidataEditListener wikidataEditListener;
@Inject
@Named("application_preferences") SharedPreferences applicationPrefs;
private LatLng curLatLng;
@ -110,6 +118,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
initBottomSheetBehaviour();
initDrawer();
wikidataEditListener.setAuthenticationStateListener(this);
}
private void resumeFragment() {
@ -219,7 +228,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
//Still need to check if GPS is enabled
checkGps();
lastKnownLocation = locationManager.getLKL();
refreshView(LocationServiceManager.LocationChangeType.PERMISSION_JUST_GRANTED);
refreshView(PERMISSION_JUST_GRANTED);
} else {
//If permission not granted, go to page that says Nearby Places cannot be displayed
hideProgressBar();
@ -279,7 +288,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
private void checkLocationPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (locationManager.isLocationPermissionGranted()) {
refreshView(LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED);
refreshView(LOCATION_SIGNIFICANTLY_CHANGED);
} else {
// Should we show an explanation?
if (locationManager.isPermissionExplanationRequired(this)) {
@ -305,7 +314,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
}
}
} else {
refreshView(LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED);
refreshView(LOCATION_SIGNIFICANTLY_CHANGED);
}
}
@ -314,7 +323,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 1) {
Timber.d("User is back from Settings page");
refreshView(LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED);
refreshView(LOCATION_SIGNIFICANTLY_CHANGED);
}
}
@ -373,8 +382,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
@Override
public void onReceive(Context context, Intent intent) {
if (NetworkUtils.isInternetConnectionEstablished(NearbyActivity.this)) {
refreshView(LocationServiceManager
.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED);
refreshView(LOCATION_SIGNIFICANTLY_CHANGED);
} else {
ViewUtil.showLongToast(NearbyActivity.this, getString(R.string.no_internet));
}
@ -390,7 +398,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
*
* @param locationChangeType defines if location shanged significantly or slightly
*/
private void refreshView(LocationServiceManager.LocationChangeType locationChangeType) {
private void refreshView(LocationChangeType locationChangeType) {
if (lockNearbyView) {
return;
}
@ -403,12 +411,13 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
registerLocationUpdates();
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;
}
curLatLng = lastLocation;
if (locationChangeType.equals(LocationServiceManager.LocationChangeType.PERMISSION_JUST_GRANTED)) {
if (locationChangeType.equals(PERMISSION_JUST_GRANTED)) {
curLatLng = lastKnownLocation;
}
@ -417,8 +426,9 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
return;
}
if (locationChangeType.equals(LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED)
|| locationChangeType.equals(LocationServiceManager.LocationChangeType.PERMISSION_JUST_GRANTED)) {
if (locationChangeType.equals(LOCATION_SIGNIFICANTLY_CHANGED)
|| locationChangeType.equals(PERMISSION_JUST_GRANTED)
|| locationChangeType.equals(MAP_UPDATED)) {
progressBar.setVisibility(View.VISIBLE);
//TODO: This hack inserts curLatLng before populatePlaces is called (see #1440). Ideally a proper fix should be found
@ -440,7 +450,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
progressBar.setVisibility(View.GONE);
});
} else if (locationChangeType
.equals(LocationServiceManager.LocationChangeType.LOCATION_SLIGHTLY_CHANGED)) {
.equals(LOCATION_SLIGHTLY_CHANGED)) {
Gson gson = new GsonBuilder()
.registerTypeAdapter(Uri.class, new UriSerializer())
.create();
@ -685,12 +695,12 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
@Override
public void onLocationChangedSignificantly(LatLng latLng) {
refreshView(LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED);
refreshView(LOCATION_SIGNIFICANTLY_CHANGED);
}
@Override
public void onLocationChangedSlightly(LatLng latLng) {
refreshView(LocationServiceManager.LocationChangeType.LOCATION_SLIGHTLY_CHANGED);
refreshView(LOCATION_SLIGHTLY_CHANGED);
}
public void prepareViewsForSheetPosition(int bottomSheetState) {
@ -700,4 +710,9 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
private void showErrorMessage(String message) {
ViewUtil.showLongToast(NearbyActivity.this, message);
}
@Override
public void onWikidataEditSuccessful() {
refreshView(MAP_UPDATED);
}
}

View file

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

View file

@ -731,6 +731,7 @@ public class NearbyMapFragment extends DaggerFragment {
editor.putString("Title", place.getName());
editor.putString("Desc", place.getLongDescription());
editor.putString("Category", place.getCategory());
editor.putString("WikiDataEntityId", place.getWikiDataEntityId());
editor.apply();
}
@ -766,7 +767,7 @@ public class NearbyMapFragment extends DaggerFragment {
if (resultCode == RESULT_OK) {
Timber.d("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
requestCode, resultCode, data);
controller.handleImagePicked(requestCode, data, true);
controller.handleImagePicked(requestCode, data, true, directPrefs.getString("WikiDataEntityId", null));
} else {
Timber.e("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
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.location.LatLng;
import fr.free.nrw.commons.utils.FileUtils;
import fr.free.nrw.commons.upload.FileUtils;
import timber.log.Timber;
public class NearbyPlaces {

View file

@ -3,6 +3,7 @@ package fr.free.nrw.commons.nearby;
import android.graphics.Bitmap;
import android.net.Uri;
import android.support.annotation.DrawableRes;
import android.support.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
@ -50,6 +51,20 @@ public class Place {
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() {
return !(siteLinks == null || Uri.EMPTY.equals(siteLinks.getWikipediaLink()));
}

View file

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

View file

@ -1,6 +1,7 @@
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.View;
import android.view.ViewGroup;
@ -8,17 +9,23 @@ import android.widget.ImageView;
import android.widget.TextView;
import com.borjabravo.readmoretextview.ReadMoreTextView;
import com.bumptech.glide.RequestBuilder;
import com.pedrogomez.renderers.Renderer;
import butterknife.BindView;
import butterknife.ButterKnife;
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.
*/
public class NotificationRenderer extends Renderer<Notification> {
private RequestBuilder<PictureDrawable> requestBuilder;
@BindView(R.id.title) ReadMoreTextView title;
@BindView(R.id.time) TextView time;
@BindView(R.id.icon) ImageView icon;
@ -41,23 +48,32 @@ public class NotificationRenderer extends Renderer<Notification> {
protected View inflate(LayoutInflater layoutInflater, ViewGroup viewGroup) {
View inflatedView = layoutInflater.inflate(R.layout.item_notification, viewGroup, false);
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;
}
@Override
public void render() {
Notification notification = getContent();
String str = notification.notificationText.trim();
str = str.concat(" ");
title.setText(str);
setTitle(notification.notificationText);
time.setText(notification.date);
switch (notification.notificationType) {
case THANK_YOU_EDIT:
icon.setImageResource(R.drawable.ic_edit_black_24dp);
break;
default:
icon.setImageResource(R.drawable.round_icon_unknown);
}
requestBuilder.load(notification.iconUrl).into(icon);
}
/**
* Cleans up the notification text and sets it as the title
* 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{

View file

@ -16,12 +16,13 @@ import javax.annotation.Nullable;
import fr.free.nrw.commons.BuildConfig;
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;
public class NotificationUtils {
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) {
if (document == null || !document.hasAttributes()) {
@ -31,6 +32,32 @@ public class NotificationUtils {
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) {
Element element = (Element) document;
String type = element.getAttribute("type");
@ -68,10 +95,17 @@ public class NotificationUtils {
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) {
return isCommonsNotification(node)
&& !getNotificationType(node).equals(UNKNOWN)
&& !getNotificationType(node).equals(THANK_YOU_EDIT);
return (isCommonsNotification(node)
|| isWikidataNotification(node)
|| isWikipediaNotification(node))
&& !getNotificationType(node).equals(UNKNOWN);
}
public static boolean isBundledNotification(Node document) {
@ -97,7 +131,7 @@ public class NotificationUtils {
switch (type) {
case THANK_YOU_EDIT:
notificationText = context.getString(R.string.notifications_thank_you_edit);
notificationText = getThankYouEditDescription(document);
break;
case EDIT_USER_TALK:
notificationText = getNotificationText(document);
@ -146,6 +180,16 @@ public class NotificationUtils {
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) {
String format = "%s%s";
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.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
@ -24,8 +21,6 @@ import android.support.v4.content.FileProvider;
import android.widget.Toast;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
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.Utils;
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 {

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.Nullable;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
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 java.math.BigInteger;
import java.nio.channels.FileChannel;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import timber.log.Timber;
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
* 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));
}
/**
* 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 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
implements MediaDetailPagerFragment.MediaDetailProvider,
AdapterView.OnItemClickListener,

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

@ -91,7 +91,7 @@ public class UploadController {
* @param decimalCoords the coordinates in decimal. (e.g. "37.51136|-77.602615")
* @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;
//TODO: Modify this to include coords
@ -101,6 +101,7 @@ public class UploadController {
contribution.setTag("mimeType", mimeType);
contribution.setSource(source);
contribution.setWikiDataEntityId(wikiDataEntityId);
//Calls the next overloaded method
startUpload(contribution, onComplete);

View file

@ -18,6 +18,7 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -36,6 +37,12 @@ import fr.free.nrw.commons.contributions.ContributionsContentProvider;
import fr.free.nrw.commons.modifications.ModificationsContentProvider;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
import fr.free.nrw.commons.mwapi.UploadResult;
import fr.free.nrw.commons.utils.ViewUtil;
import fr.free.nrw.commons.wikidata.WikidataEditListener;
import fr.free.nrw.commons.wikidata.WikidataEditService;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import timber.log.Timber;
public class UploadService extends HandlerService<Contribution> {
@ -49,8 +56,8 @@ public class UploadService extends HandlerService<Contribution> {
public static final String EXTRA_CAMPAIGN = EXTRA_PREFIX + ".campaign";
@Inject MediaWikiApi mwApi;
@Inject WikidataEditService wikidataEditService;
@Inject SessionManager sessionManager;
@Inject @Named("default_preferences") SharedPreferences prefs;
@Inject ContributionDao contributionDao;
private NotificationManager notificationManager;
@ -137,6 +144,7 @@ public class UploadService extends HandlerService<Contribution> {
@Override
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) {
case ACTION_UPLOAD_FILE:
@ -253,6 +261,7 @@ public class UploadService extends HandlerService<Contribution> {
if (!resultStatus.equals("Success")) {
showFailedNotification(contribution);
} else {
wikidataEditService.createClaimWithLogging(contribution.getWikiDataEntityId(), filename);
contribution.setFilename(uploadResult.getCanonicalFilename());
contribution.setImageUrl(uploadResult.getImageUrl());
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,12 +1,34 @@
package fr.free.nrw.commons.utils;
import android.app.WallpaperManager;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapRegionDecoder;
import android.graphics.Color;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Build;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
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 static com.mapbox.mapboxsdk.Mapbox.getApplicationContext;
/**
* Created by bluesir9 on 3/10/17.
*/
@ -132,4 +154,52 @@ public class ImageUtils {
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,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

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

View file

@ -54,8 +54,6 @@
<string name="share_title_hint">العنوان</string>
<string name="share_description_hint">الوصف</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_blocked">عذراً، لقد تم منع هذا المستخدم على كومنز</string>
<string name="login_failed_2fa_needed">يجب توفير رمز التحقق المزدوج.</string>

View file

@ -49,8 +49,7 @@
<string name="add_title_toast">Apurre un títulu pa esti ficheru</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_username">Nun se pudo aniciar sesión por favor compruebe\'l so nome d\'usuariu</string>
<string name="login_failed_password">Nun se pudo aniciar sesión por favor compruebe la so contraseña</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_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_2fa_needed">Tienes de dar el códigu d\'identificación de dos factores.</string>
@ -273,4 +272,6 @@
<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="menu_set_wallpaper">Definir fondu</string>
<string name="wallpaper_set_successfully">Fondu definíu correutamente</string>
</resources>

View file

@ -43,8 +43,6 @@
<string name="share_title_hint">Naslov</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_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_blocked">Nažalost, korisnik je blokiran na Ostavi</string>
<string name="login_failed_2fa_needed">Morate uneti svoj dvofaktorski kod za autentifikaciju.</string>

View file

@ -50,8 +50,6 @@
<string name="add_title_toast">Был файлдың атамаһын күрһәт</string>
<string name="share_description_hint">Тасуирлама</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_blocked">Ғәфү итегеҙ, әммә был исемдәге ҡатнашыусыға Викискладҡа инеү тыйылған</string>
<string name="login_failed_2fa_needed">Ике тапҡыр раҫлай торған шәхси кодты яҙырға кәрәк</string>

View file

@ -32,8 +32,6 @@
<string name="share_title_hint">Заглавие</string>
<string name="share_description_hint">Описание</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="provider_modifications">Изменения</string>
<string name="menu_upload_single">Качване</string>

View file

@ -57,8 +57,6 @@
<string name="add_title_toast">এই ফাইলটির জন্য একটি শিরোনাম প্রদান করুন</string>
<string name="share_description_hint">বিবরণ</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_blocked">দুঃখিত, এই ব্যবহারকারীকে কমন্সে বাধা দেয়া হয়েছে</string>
<string name="login_failed_2fa_needed">অাপনাকে অবশ্যই অাপনার দু\'স্তরের সত্যায়নকরণ কোড দিতে হবে।</string>

View file

@ -52,8 +52,6 @@
<string name="add_title_toast">Roit un titl d\'ar restr-mañ, mar plij</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_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_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>

View file

@ -43,8 +43,6 @@
<string name="share_title_hint">Naslov</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_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_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>

View file

@ -50,8 +50,6 @@
<string name="share_title_hint">Títol</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_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_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>
@ -192,4 +190,5 @@
<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="about_faq" fuzzy="true">Preguntes freqüents</string>
<string name="no_images_found">No sha trobat cap imatge.</string>
</resources>

View file

@ -61,8 +61,6 @@
<string name="add_title_toast">Vložte prosím název tohoto souboru</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_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_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>

View file

@ -41,8 +41,6 @@
<string name="share_title_hint">Titel</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_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_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>

View file

@ -43,8 +43,6 @@
<string name="share_title_hint">Teitl</string>
<string name="share_description_hint">Disgrifiad</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_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_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>

View file

@ -54,8 +54,6 @@
<string name="add_title_toast">Angiv venligt en titel for denne fil</string>
<string name="share_description_hint">Beskrivelse</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_blocked">Beklager, denne bruger er blevet blokeret på Commons</string>
<string name="login_failed_2fa_needed">Du skal angive din tofaktorgodkendelseskode.</string>

View file

@ -53,8 +53,7 @@
<string name="add_title_toast">Bitte gib einen Titel für diese Datei an</string>
<string name="share_description_hint">Beschreibung</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_password">Anmeldung fehlgeschlagen Bitte Passwort überprüfen</string>
<string name="login_failed_wrong_credentials">Anmeldung fehlgeschlagen. Bitte Benutzernamen und Passwort überprüfen.</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_2fa_needed">Du musst deinen Code zur Zwei-Faktor-Authentifizierung angeben.</string>
@ -279,4 +278,6 @@
<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="error_fetching_nearby_places">Fehler beim Abrufen der Orte in der Nähe.</string>
<string name="menu_set_wallpaper">Hintergrundbild festlegen</string>
<string name="wallpaper_set_successfully">Hintergrundbild erfolgreich festgelegt!</string>
</resources>

View file

@ -52,8 +52,6 @@
<string name="share_title_hint">Sername</string>
<string name="share_description_hint">Şınasnayış</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_blocked">Qısur mewni rê, Karber commons dı bloqe biyo.</string>
<string name="login_failed_generic">Nidekeweya de</string>

View file

@ -57,8 +57,7 @@
<string name="add_title_toast">Παρακαλώ παρέχετε ένα τίτλο για αυτό το αρχείο</string>
<string name="share_description_hint">Περιγραφή</string>
<string name="login_failed_network">Δεν είναι δυνατή η σύνδεση - αποτυχία του δικτύου</string>
<string name="login_failed_username">Δεν είναι δυνατή η σύνδεση - ελέγξτε το όνομα χρήστη σας</string>
<string name="login_failed_password">Δεν είναι δυνατή η σύνδεση - παρακαλούμε ελέγξτε τον κωδικό σας</string>
<string name="login_failed_wrong_credentials">Αποτυχία σύνδεσης - παρακαλώ ελέγξτε το όνομα χρήστη και τον κωδικό σας</string>
<string name="login_failed_throttled">Πάρα πολλές ανεπιτυχείς προσπάθειες. Παρακαλώ δοκιμάστε ξανά σε λίγα λεπτά.</string>
<string name="login_failed_blocked">Συγνώμη, αυτός ο χρήστης έχει αποκλειστεί από τα Commons</string>
<string name="login_failed_2fa_needed">Πρέπει να δώσετε τον κωδικό πιστοποίησης με δύο παράγοντες</string>
@ -283,4 +282,6 @@
<string name="share_app_title">Κοινοποίηση εφαρμογής</string>
<string name="share_coordinates_not_present">Οι συντεταγμένες δεν ορίστηκαν κατά την διάρκεια της επιλογής εικόνας</string>
<string name="error_fetching_nearby_places">Σφάλμα κατά την εύρεση κοντινών μερών.</string>
<string name="menu_set_wallpaper">Ρύθμιση ταπετσαρίας</string>
<string name="wallpaper_set_successfully">Η ταπετσαρία ρυθμίστηκε επιτυχώς!</string>
</resources>

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Authors:
* 2axterix2
* Adjen
* Benfutbol10
* Fitoschido
* Jduranboger
@ -57,8 +58,7 @@
<string name="add_title_toast">Proporciona un título para este archivo</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_username">No se pudo iniciar sesión: revisa tu nombre de usuario</string>
<string name="login_failed_password">No se pudo iniciar sesión: revisa tu contraseña</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_throttled">Demasiados intentos fallidos. Inténtalo de nuevo en unos minutos.</string>
<string name="login_failed_blocked">Lo sentimos, este usuario ha sido bloqueado en Commons</string>
<string name="login_failed_2fa_needed">Debes proporcionar tu código de auntenticación de dos factores.</string>

View file

@ -48,8 +48,6 @@
<string name="share_title_hint">Izenburua</string>
<string name="share_description_hint">Deskribapena</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_blocked">Barka, baina erabiltzaile hau blokeatuta dago Commonsen</string>
<string name="login_failed_generic">Saio hasieran akatsa</string>

View file

@ -56,8 +56,6 @@
<string name="add_title_toast">لطفاً نامی را برای این پرونده انتخاب کنید</string>
<string name="share_description_hint">توضیحات</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_blocked">پوزش، کاربر در ویکی‌انبار بسته شده‌است</string>
<string name="login_failed_2fa_needed">باید تأیید دومرحله‌ای را فعال کنید.</string>

View file

@ -2,10 +2,11 @@
<!-- Authors:
* Nike
* Olli
* Pyscowicz
* Silvonen
-->
<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_comment_prompt">Kerro meille mitä teit äsken, sähköpostitse. Se auttaa meitä korjaamaan ongelman!</string>
<string name="crash_dialog_ok_toast">Kiitos!</string>

View file

@ -58,8 +58,6 @@
<string name="add_title_toast">Anna tälle tiedostolle otsikko</string>
<string name="share_description_hint">Kuvaus</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_blocked">Pahoittelut, tämä käyttäjä on estetty Commonsissa</string>
<string name="login_failed_2fa_needed">Anna kaksivaiheisen tunnistuksen koodi.</string>
@ -162,8 +160,8 @@
<string name="detail_description_empty">Ei kuvausta</string>
<string name="detail_license_empty">Tuntematon lisenssi</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="write_storage_permission_rationale">Vaadittava lupa: Kirjoita ulkoiseen tallennustilaan. Appi ei voi päästä kameraasi 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">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="ok">OK</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_cancel">Peruuta</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>

View file

@ -34,8 +34,6 @@
<string name="share_title_hint">Heiti</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_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_blocked">Haldið okkum tilgóðar, hesin brúkari er blivin sperraður á Commons</string>
<string name="login_failed_generic">Login miseydnaðist</string>

View file

@ -65,8 +65,7 @@
<string name="add_title_toast">Veuillez donner un titre à ce fichier</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_username">Impossible de se connecter — veuillez vérifier votre nom dutilisateur</string>
<string name="login_failed_password">Impossible de se connecter — veuillez vérifier votre mot de passe</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_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_2fa_needed">Vous devez fournir votre code dauthentification à deux facteurs.</string>
@ -290,4 +289,6 @@
<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="error_fetching_nearby_places">Erreur durant l\'exploration du voisinage.</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>

View file

@ -42,8 +42,6 @@
<string name="share_title_hint">Tiitel</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_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_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>

View file

@ -55,8 +55,6 @@
<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="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_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_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>

View file

@ -34,8 +34,6 @@
<string name="share_title_hint">Poʻo Inoa</string>
<string name="share_description_hint">ʻike ʻAno</string>
<string name="login_failed_network">Hiki ʻole ke ʻeʻe - hāʻule pūnaewele</string>
<string name="login_failed_username">Hiki ʻole ke ʻeʻe - hōʻoiaʻiʻo i kāu inoa mea hoʻohana ke ʻoluʻolu</string>
<string name="login_failed_password">Hiki ʻole ke ʻeʻe - hōʻoiaʻiʻo i kāu ʻōlelo hūnā ke ʻoluʻolu</string>
<string name="login_failed_throttled" fuzzy="true">Hoʻāʻo ʻeʻe ʻole he nui kā. E ʻoluʻolu, e hana hou i ka wā hou aku</string>
<string name="login_failed_blocked">E kala mai, ua pale ʻia kēia mea hoʻohana ma ke Kahilehulehu</string>
<string name="login_failed_generic">ʻule ka ʻeʻena</string>

View file

@ -53,8 +53,6 @@
<string name="share_title_hint">शीर्षक</string>
<string name="share_description_hint">विवरण</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_blocked">क्षमा करें, यह सदस्य कॉमन्स में अवरोधित है</string>
<string name="login_failed_2fa_needed">आपको अपना दो कारक प्रमाणन कोड प्रदान करना होगा।</string>

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Authors:
* MaGa
-->
<resources>
<string name="crash_dialog_title">Aplikacija Zajednički poslužitelj je prestala s radom</string>
<string name="crash_dialog_text">Nešto je krenulo po krivu!</string>
<string name="crash_dialog_comment_prompt">Napišite nam što radite i podijelite s nama putem elektroničke pošte. Pomoći će nam da to popravimo!</string>
<string name="crash_dialog_ok_toast">Hvala Vam!</string>
</resources>

View file

@ -0,0 +1,275 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Authors:
* MaGa
-->
<resources>
<string name="preference_category_appearance">Izgled</string>
<string name="preference_category_general">Opće</string>
<string name="preference_category_feedback">Povratna informacija</string>
<string name="preference_category_location">Lokacija</string>
<string name="app_name">Zajednički poslužitelj</string>
<string name="bullet"></string>
<string name="menu_settings">Postavke</string>
<string name="username">Suradničko ime</string>
<string name="password">Zaporka</string>
<string name="login_credential">Prijavite se na Commons beta račun</string>
<string name="login">Prijavi se</string>
<string name="forgot_password">Zaboravljena zaporka?</string>
<string name="signup">Otvori račun</string>
<string name="logging_in_title">Prijava</string>
<string name="logging_in_message">Molimo pričekajte ...</string>
<string name="login_success">Prijava uspješna!</string>
<string name="login_failed">Prijava neuspješna!</string>
<string name="upload_failed">Datoteka nije pronađena. Molimo probajte drugu.</string>
<string name="authentication_failed">Autentifikacija neuspješna!</string>
<string name="uploading_started">Postavljanje započeto!</string>
<string name="upload_completed_notification_title">%1$s postavljeno!</string>
<string name="upload_completed_notification_text">Dodirnite da biste vidjeli datoteku</string>
<string name="upload_progress_notification_title_start">Počinje postavljanje %1$s</string>
<string name="upload_progress_notification_title_in_progress">Postavljanje %1$s</string>
<string name="upload_progress_notification_title_finishing">Završeno postavljanje %1$s</string>
<string name="upload_failed_notification_title">Postavljanje %1$s neuspješno</string>
<string name="upload_failed_notification_subtitle">Dodirnite da biste vidjeli</string>
<plurals name="uploads_pending_notification_indicator">
<item quantity="one">Postavlja se %1$d datoteka</item>
<item quantity="other">Postavljaju se %1$d datoteke</item>
</plurals>
<string name="title_activity_contributions">Moja nedavja postavljanja</string>
<string name="contribution_state_queued">U redu čekanja</string>
<string name="contribution_state_failed">Neuspješno</string>
<string name="contribution_state_in_progress">%1$d%% postavljeno</string>
<string name="contribution_state_starting">Postavljanje</string>
<string name="menu_from_gallery">Iz galerije</string>
<string name="menu_from_camera">Napravi sliku</string>
<string name="menu_nearby">U blizini</string>
<string name="provider_contributions">Moja postavljanja</string>
<string name="menu_share">Podijeli</string>
<string name="menu_open_in_browser">Pogledaj u pregledniku</string>
<string name="share_title_hint">Naziv</string>
<string name="add_title_toast">Molimo imenujte ovu datoteku</string>
<string name="share_description_hint">Opis</string>
<string name="login_failed_network">Prijava nije moguća - mrežna pogrješka</string>
<string name="login_failed_wrong_credentials">Prijava nije moguća - molimo provjerite suradničko ime i zaportku</string>
<string name="login_failed_throttled">Previše neuspješnih pokušaja, molimo probajte opet za par minuta.</string>
<string name="login_failed_blocked">Ispričavamo se, ovaj je suradnik blokiran na Zajendičkom poslužitelju</string>
<string name="login_failed_2fa_needed">Morate upisati autetifikacijski kôd od dva faktora</string>
<string name="login_failed_generic">Prijava neuspješna</string>
<string name="share_upload_button">Postavljanje</string>
<string name="multiple_share_base_title">Imenujte ovaj set</string>
<string name="provider_modifications">Promjene</string>
<string name="menu_upload_single">Postavljanje</string>
<string name="categories_search_text_hint">Pretraži kategorije</string>
<string name="menu_save_categories">Spremi</string>
<string name="refresh_button">Osvježi</string>
<string name="display_list_button">Popis</string>
<string name="gps_disabled">GPS je onemogućen na Vašem uređaju. Želite li ga omogućiti?</string>
<string name="enable_gps">Omogući GPS</string>
<string name="contributions_subtitle_zero">Nemate još postavljenih datoteka</string>
<plurals name="contributions_subtitle">
<item quantity="zero">\@string/contributions_subtitle_zero</item>
<item quantity="one">%1$d postavljena datoteka</item>
<item quantity="other">%1$d postavljene datoteke</item>
</plurals>
<plurals name="starting_multiple_uploads">
<item quantity="one">Započeto %1$d postavljanje</item>
<item quantity="other">Započeta %1$d postavljanja</item>
</plurals>
<plurals name="multiple_uploads_title">
<item quantity="one">%1$d postavljanje</item>
<item quantity="other">%1$d postavljanja</item>
</plurals>
<string name="categories_not_found">Nema kategorija koje odgovoraju upitu %1$s</string>
<string name="categories_skip_explanation">Dodajte slikama kategorije kako bi se lakše pronašle. Da biste ih dodali, započnite s upisivanjem.</string>
<string name="categories_activity_title">Kategorije</string>
<string name="title_activity_settings">Postavke</string>
<string name="title_activity_signup">Otvori račun</string>
<string name="title_activity_featured_images">Izabrane slike</string>
<string name="menu_about">O</string>
<string name="about_license">Aplikacija The Wikimedia Commons je aplikacija otvorenog kôda koju razvijaju i održavaju volonteri Wikimedijine zajednice. Zaklada Wikimedija nije uključena u stvaranje, razvoj ili održavanje ove aplikacije.</string>
<string name="about_improve">Da biste prijavili poteškoću ili dali prijedlog, stvorite &lt;a href=\"https://github.com/commons-app/apps-android-commons/issues\"&gt;novi zahtjev na GitHubu&lt;/a&gt;.</string>
<string name="about_privacy_policy">&lt;u&gt;Politika privatnosti&lt;/u&gt;</string>
<string name="about_credits">&lt;u&gt;Zasluge&lt;/u&gt;</string>
<string name="title_activity_about">O</string>
<string name="menu_feedback">Pošaljite povratnu informaciju (putem elektroničke pošte)</string>
<string name="no_email_client">Klijent za elektroničku poštu nije instaliran</string>
<string name="provider_categories">Nedavno rabljene kategorije</string>
<string name="waiting_first_sync">Pričekajte za prvu sinkronizaciju...</string>
<string name="no_uploads_yet">Nemate još postavljenih slika.</string>
<string name="menu_retry_upload">Pokušaj ponovo</string>
<string name="menu_cancel_upload">Odustani</string>
<string name="share_license_summary">Ova će slika biti licencirana pod %1$s</string>
<string name="media_upload_policy">Slanjem ove slike izjavljujem da je ona moje djelo i ne sadrži materijale zaštićene autorskim pravom ili selfije, te da je u skladu sa &lt;a href=\"https://commons.wikimedia.org/wiki/Commons:Policies_and_guidelines\"&gt;smjernicama Zajedničkog poslužitelja&lt;/a&gt;.</string>
<string name="menu_download">Preuzmi</string>
<string name="preference_license">Podrazumijevana licencija</string>
<string name="use_previous">Rabite prethodni naziv/opis</string>
<string name="allow_gps">Automatski pribavi trenutačnu lokaciju</string>
<string name="allow_gps_summary">Ako slika nema oznaku lokacije, pribavi trenutačnu lokaciju kako bi se mogle ponuditi kategorije</string>
<string name="preference_theme">Noćni način</string>
<string name="preference_theme_summary">Rabi tamnu temu</string>
<string name="license_name_cc_by_sa_four"> Imenovanje-Dijeli pod istim uvjetima 4.0</string>
<string name="license_name_cc_by_four"> Imenovanje 4.0</string>
<string name="license_name_cc_by_sa"> Imenovanje-Dijeli pod istim uvjetima 3.0</string>
<string name="license_name_cc_by"> Imenovanje 3.0</string>
<string name="license_name_cc0">CC0</string>
<string name="license_name_cc_by_sa_3_0">CC BY-SA 3.0</string>
<string name="license_name_cc_by_sa_3_0_at">CC BY-SA 3.0 (Austrija)</string>
<string name="license_name_cc_by_sa_3_0_de">CC BY-SA 3.0 (Njemačka)</string>
<string name="license_name_cc_by_sa_3_0_ee">CC BY-SA 3.0 (Estonija)</string>
<string name="license_name_cc_by_sa_3_0_es">CC BY-SA 3.0 (Španjolska)</string>
<string name="license_name_cc_by_sa_3_0_hr">CC BY-SA 3.0 (Hrvatska)</string>
<string name="license_name_cc_by_sa_3_0_lu">CC BY-SA 3.0 (Luksemburg)</string>
<string name="license_name_cc_by_sa_3_0_nl">CC BY-SA 3.0 (Nizozemska)</string>
<string name="license_name_cc_by_sa_3_0_no">CC BY-SA 3.0 (Norveška)</string>
<string name="license_name_cc_by_sa_3_0_pl">CC BY-SA 3.0 (Poljska)</string>
<string name="license_name_cc_by_sa_3_0_ro">CC BY-SA 3.0 (Rumunjska)</string>
<string name="license_name_cc_by_3_0">CC BY 3.0</string>
<string name="license_name_cc_by_sa_4_0">CC BY-SA 4.0</string>
<string name="license_name_cc_by_4_0">CC BY 4.0</string>
<string name="license_name_cc_zero">CC Zero</string>
<string name="tutorial_1_text">Na Zajedničkom poslužitelju se nalazi većina slika rabljena na Wikipediji.</string>
<string name="tutorial_1_subtext">Vaše slike pomažu u edukaciji ljudi diljem svijeta!</string>
<string name="tutorial_2_text">Molimo postavite slike koje su u cijelosti Vaše djelo:</string>
<string name="tutorial_2_subtext">Objekti iz prirode (cvijeće, životinje, planine)\n• Korisni objekti (bicikla, željezničke postaje)\n• Poznate osobe (Vaš gradonačelnik, olimpijski sportaš kojeg ste sreli)</string>
<string name="tutorial_2_subtext_1">Objekti iz prirode (cvijeće, životinje, planine)</string>
<string name="tutorial_2_subtext_2">Korisni objekti (bicikla, željezničke postaje)</string>
<string name="tutorial_2_subtext_3">Poznate osobe (Vaš gradonačelnik, olimpijski sportaš kojeg ste sreli)</string>
<string name="tutorial_3_text">Molimo NE postavljajte:</string>
<string name="tutorial_3_subtext">- selfije ili slike Vaših prijatelja\n- slike koje ste preuzeli s interneta\n- snimke ekrana zaštićenih aplikacija</string>
<string name="tutorial_3_subtext_1">Selfije ili slike Vaših prijatelja</string>
<string name="tutorial_3_subtext_2">Slike koje ste preuzeli s interneta</string>
<string name="tutorial_3_subtext_3">Snimke ekrana zaštićenih aplikacija</string>
<string name="tutorial_4_text">Primjer postavljanja:</string>
<string name="tutorial_4_subtext">- Naziv: Sydneyska opera\n- Opis: Sydneyska opera viđena iz zaljeva\n- Kategorije: Sydney Opera House from the west, Sydney Opera House remote views</string>
<string name="tutorial_4_subtext_1">Naziv: Sydneyska opera</string>
<string name="tutorial_4_subtext_2">Opis: Sydneyska opera viđena iz zaljeva</string>
<string name="tutorial_4_subtext_3">Kategorije: Sydney Opera House from the west, Sydney Opera House remote views</string>
<string name="welcome_wikipedia_text">Dijelite Vaše slike. Pomozite da članci na Wikipediji zažive!</string>
<string name="welcome_wikipedia_subtext">Slike na wikipediji su sa Zajedničkog poslužitelja.</string>
<string name="welcome_copyright_text">Vaše slike pomažu u edukaciji ljudi diljem svijeta.</string>
<string name="welcome_copyright_subtext">Izbjegavajte materijale s autorskim pravima koje ste pronašli na internetu (slike plakata, naslovnice knjiga, i slično).</string>
<string name="welcome_final_text">Jeste li razumjeli?</string>
<string name="welcome_final_button_text">Da!</string>
<string name="detail_panel_cats_label">Kategorije</string>
<string name="detail_panel_cats_loading">Učitavanje...</string>
<string name="detail_panel_cats_none">Ništa nije odabrano</string>
<string name="detail_description_empty">Nema opisa</string>
<string name="detail_license_empty">Nepoznata licencija</string>
<string name="menu_refresh">Osvježi</string>
<string name="read_storage_permission_rationale">Potrebno dopuštenje čitanja vanjske pohrane. Bez toga aplikacija ne može pristupiti Vašoj galeriji.</string>
<string name="write_storage_permission_rationale">Potrebno dopuštenje spremanja na vanjsku pohranu. Bez toga aplikacija ne može pristupiti Vašoj kameri.</string>
<string name="location_permission_rationale">Potrebno dopuštenje za određivanje trenutačne lokacije za prijedloge kategorija (nije obvezno)</string>
<string name="ok">U redu</string>
<string name="title_activity_nearby">Mjesta u blizini</string>
<string name="no_nearby">Nisu pronađena mjesta u blizini</string>
<string name="warning">Upozorenje</string>
<string name="file_exists">Ova datoteka već postoji na Zajedničkom poslužitelju. Jeste li sigurni da želite nastaviti?</string>
<string name="yes">Da</string>
<string name="no">Ne</string>
<string name="media_detail_title">Naslov</string>
<string name="media_detail_media_title">Naslov medija</string>
<string name="media_detail_description">Opis</string>
<string name="media_detail_description_explanation">Ovdje ide opis datoteke. Mogao bi biti poprilično dug i trebat će se prelomiti u nekoliko redova. Nadamo se da će lijepo izgledati.</string>
<string name="media_detail_author">Autor</string>
<string name="media_detail_author_explanation">Ovdje ide suradničko ime autora izabrane slike.</string>
<string name="media_detail_uploaded_date">Datum postavljanja</string>
<string name="media_detail_license">Licencija</string>
<string name="media_detail_coordinates">Koordinate</string>
<string name="media_detail_coordinates_empty">Ništa nije navedeno</string>
<string name="become_a_tester_title">Postani beta tester</string>
<string name="become_a_tester_description">Prijavite se na naš beta-kanal na Google Playu i dobijte raniji pristup novim mogućnostima i ispravkama pogrješaka</string>
<string name="_2fa_code">Kôd za provjeru u 2 koraka</string>
<string name="number_of_uploads">Moje ograničenje nedavnih postavljanja</string>
<string name="maximum_limit">Najviše moguće</string>
<string name="maximum_limit_alert">Nije moguće prikazati više od 500</string>
<string name="set_limit">Postavi ograničenje nedavnih postavljanja</string>
<string name="login_failed_2fa_not_supported">Kôd za provjeru u 2 koraka nije podržan.</string>
<string name="logout_verification">Zaista se želite odjaviti?</string>
<string name="commons_logo">Logotip Zajedničkog poslužitelja</string>
<string name="commons_website">Mrežno mjesto Zajedničkog poslužitelja</string>
<string name="commons_facebook">Stranica Zajedničkog poslužitelja na Facebooku</string>
<string name="commons_github">Izvorni kôd Zajedničkog poslužitelja na Githubu</string>
<string name="background_image">Pozadinska slika</string>
<string name="mediaimage_failed">Slika nije uspjela</string>
<string name="no_image_found">Slika nije pronađena</string>
<string name="upload_image">Postavi sliku</string>
<string name="welcome_image_mount_zao">Planina Zao</string>
<string name="welcome_image_llamas">Ljame</string>
<string name="welcome_image_rainbow_bridge">Dugin most</string>
<string name="welcome_image_tulip">Tulipan</string>
<string name="welcome_image_no_selfies">Bez selfija</string>
<string name="welcome_image_proprietary">Vlasnička slika</string>
<string name="welcome_image_welcome_wikipedia">Welcome (Wikipedija)</string>
<string name="welcome_image_welcome_copyright">Dobro došli (autorska prava)</string>
<string name="welcome_image_sydney_opera_house">Sidnejska opera</string>
<string name="cancel">Odustani</string>
<string name="navigation_drawer_open">Otvori</string>
<string name="navigation_drawer_close">Zatvori</string>
<string name="navigation_item_home">Početna stranica</string>
<string name="navigation_item_upload">Postavljanje</string>
<string name="navigation_item_nearby">U blizini</string>
<string name="navigation_item_about">O</string>
<string name="navigation_item_settings">Postavke</string>
<string name="navigation_item_feedback">Povratna informacija</string>
<string name="navigation_item_logout">Odjava</string>
<string name="navigation_item_info">Upute</string>
<string name="navigation_item_notification">Obavijesti</string>
<string name="navigation_item_featured_images">Izabrano</string>
<string name="nearby_needs_permissions">Mjesta u blizini ne mogu biti prikazana bez dopuštenja određivanja lokacije</string>
<string name="no_description_found">nema opisa</string>
<string name="nearby_info_menu_commons_article">Stranica datoteke na Zajedničkom poslužitelju</string>
<string name="nearby_info_menu_wikidata_article">Stavka na Wikidati</string>
<string name="nearby_info_menu_wikipedia_article">Članak na Wikipediji</string>
<string name="error_while_cache">Pogrješka predmemoriranja slika</string>
<string name="title_info">Jedinstveni naziv datoteke koji će služiti kao njeno ime. Možete koristiti uobičajeni jezik s razmacima. Ne uključuje datotečni nastavak.</string>
<string name="description_info">Opišite medij što je više moguće: gdje je napravljen, što prikazuje,... Opišite objekte ili osobe. Napišite informacije koje ne mogu biti lako okrivene, npr. doba dana ako je u pitanju pejzaž. Ako medij prikazuje nešto neobično, molimo objasnite što je neobično.</string>
<string name="upload_image_too_dark">Slika je pretamna, želite ili je ipak ostaviti? Zajednički poslužitelj je namijenjen slikama od enciklopedijske vrijednosti.</string>
<string name="upload_image_blurry">Slika je mutna, želite ili je ipak ostaviti? Zajednički poslužitelj je namijenjen slikama od enciklopedijske vrijednosti.</string>
<string name="give_permission">Daj dopuštenje</string>
<string name="use_external_storage">Rabi vanjsku pohranu</string>
<string name="use_external_storage_summary">Spremite slike načinjene kamerom Vašeg uređaja</string>
<string name="login_to_your_account">Prijavite se na Vaš račun</string>
<string name="send_log_file">Pošalji zapisnik</string>
<string name="send_log_file_description">Pošalji zapisnik elektroničkom poštom razvijateljima</string>
<string name="no_web_browser">Preglednik nije pronađen</string>
<string name="null_url">Pogrješka! URL nije pronađen</string>
<string name="nominate_deletion">Predloži za brisanje</string>
<string name="nominated_for_deletion">Slika je predložena za brisanje</string>
<string name="view_browser">Pogledaj u pregledniku</string>
<string name="nearby_location_has_not_changed">Lokacija nepromijenjena.</string>
<string name="nearby_location_not_available">Lokacija nedostupna.</string>
<string name="location_permission_rationale_nearby">Potrebno je dopuštenje za popis mjesta u blizini</string>
<string name="get_directions">PRIBAVI UPUTE</string>
<string name="read_article">PROČITAJ ČLANAK</string>
<string name="notifications_welcome">%1$s, dobro došli na Zajednički poslužitelj! Drago nam je da ste tu.</string>
<string name="notifications_talk_page_message">%1$s Vam je ostavio poruku na Vašoj razgovornoj stranici</string>
<string name="notifications_thank_you_edit">Hvala Vam na uređivanju</string>
<string name="notifications_mention">%1$s Vas je spomenuo na %2$s.</string>
<string name="toggle_view_button">Prebaci prikaz</string>
<string name="nearby_directions">UPUTE</string>
<string name="nearby_wikidata">WIKIDATA</string>
<string name="nearby_wikipedia">WIKIPEDIJA</string>
<string name="nearby_commons">ZAJEDNIČKI POSLUŽITELJ</string>
<string name="about_rate_us">&lt;u&gt;Ocijenite nas&lt;/u&gt;</string>
<string name="about_faq">&lt;u&gt;ČPP&lt;/u&gt;</string>
<string name="welcome_skip_button">Preskoči upute</string>
<string name="no_internet">Internet nije dostupan</string>
<string name="internet_established">Internet je dostupan</string>
<string name="error_notifications">Pogrješka dohvaćanja obavijesti</string>
<string name="no_notifications">Nema obavijesti</string>
<string name="about_translate">&lt;u&gt;Prevedi&lt;/u&gt;</string>
<string name="about_translate_title">Jezici</string>
<string name="about_translate_message">Odaberite jezik na koji bi željeli prevoditi</string>
<string name="about_translate_proceed">Nastavi</string>
<string name="about_translate_cancel">Odustani</string>
<string name="retry">Pokušaj ponovo</string>
<string name="showcase_view_got_it_button">U redu!</string>
<string name="showcase_view_whole_nearby_activity">Ovo su mjesta u blizini koja trebaju slike za ilustriranje članaka o njima na Wikipediji</string>
<string name="showcase_view_list_icon">Dodirnite da biste dobili popis ovih mjesta</string>
<string name="showcase_view_plus_fab">Možete postaviti sliku s bilo kojeg mjesta u Vašoj galeriji ili kameri</string>
<string name="no_images_found">Slike nisu pronađene!</string>
<string name="error_loading_images">Pogrješka prilikom učitavanja slika.</string>
<string name="image_uploaded_by">Postavio: %1$s</string>
<string name="share_app_title">Aplikacija za dijeljenje</string>
<string name="share_coordinates_not_present">Prilikom označavanja slike koordinate nisu navedene</string>
<string name="error_fetching_nearby_places">Pogrješka prilikom dohvaćanja mjesta u blizini.</string>
</resources>

View file

@ -35,8 +35,6 @@
<string name="share_title_hint">Titel</string>
<string name="share_description_hint">Beschreibung</string>
<string name="login_failed_network">Oonmeldung fehlgeschlooht Netzwerrekfehler</string>
<string name="login_failed_username">Oonmeldung fehlgeschlooht Bittschön Benutzernoome üwerprüfe</string>
<string name="login_failed_password">Oonmeldung fehlgeschlooht Bittschön Passwort üwerprüfe</string>
<string name="login_failed_throttled" fuzzy="true">Zu viele erfollichlose Versuche. Bittschön in en poor Minute wieder erneit versuche.</string>
<string name="login_failed_blocked">Der Benutzer woard leider uff Commons gesperrt</string>
<string name="login_failed_generic">Oonmeldung fehlgeschlooht</string>

View file

@ -34,8 +34,6 @@
<string name="share_title_hint">Titul</string>
<string name="share_description_hint">Wopisanje</string>
<string name="login_failed_network">Přizjewjenje je so njeporadźiło - syćowy zmylk</string>
<string name="login_failed_username">Přizjewjenje je njemóžno - prošu přepruwuj swoje wužiwarske mjeno</string>
<string name="login_failed_password">Přizjewjenje njeje móžno - prošu přepruwuj swoje hesło</string>
<string name="login_failed_throttled" fuzzy="true">Přewjele njewuspěšnych pospytow. Prošu spytaj za něšto mjeńšin hišće raz.</string>
<string name="login_failed_blocked">Tutoho wužiwarja su bohužel na Commons zablokowali</string>
<string name="login_failed_generic">Přizjewjenje je so njeporadźiło</string>

View file

@ -59,8 +59,7 @@
<string name="add_title_toast">Kérlek, adj címet a fájlnak</string>
<string name="share_description_hint">Leírás</string>
<string name="login_failed_network">Nem lehet bejelentkezni - hálózati hiba</string>
<string name="login_failed_username">Nem lehet bejelentkezni - ellenőrizd a felhasználóneved</string>
<string name="login_failed_password">Nem lehet bejelentkezni - ellenőrizd a jelszavad</string>
<string name="login_failed_wrong_credentials">Nem sikerült bejelentkezni kérlek, ellenőrizd a felhasználónevedet és a jelszavadat</string>
<string name="login_failed_throttled">Túl sok sikertelen próbálkozás. Próbálkozz újra pár perc múlva.</string>
<string name="login_failed_blocked">Sajnáljuk, ezt a felhasználót blokkolták a Commonson</string>
<string name="login_failed_2fa_needed">Meg kell adnia a kétlépcsős hitelesítő kódját.</string>
@ -255,15 +254,18 @@
<string name="no_internet">Internet nem elérhető</string>
<string name="internet_established">Internet elérhető</string>
<string name="no_notifications">Nincs értesítés</string>
<string name="about_translate">&lt;u&gt;Fordítás&lt;/u&gt;</string>
<string name="about_translate_title">Nyelvek</string>
<string name="about_translate_proceed">Folytatás</string>
<string name="about_translate_cancel">Mégse</string>
<string name="retry">Újra</string>
<string name="showcase_view_got_it_button">Értettem!</string>
<string name="showcase_view_whole_nearby_activity">Ezek a helyek vannak a közeledben, amikről van Wikipédia szócikk és nincs bennük kép.</string>
<string name="showcase_view_list_icon">A gombra koppintva bejön egy lista, ami ezeket a helyeket mutatja.</string>
<string name="showcase_view_plus_fab">Bármelyik helyhez feltölthetsz képet a galériádból vagy készíthetsz újat a kamerával.</string>
<string name="no_images_found">Nem található kép!</string>
<string name="error_loading_images">Képbetöltés közben hiba történt</string>
<string name="image_uploaded_by">Feltöltötte: %1$s</string>
<string name="share_app_title">Alkalmazás megosztása</string>
<string name="share_coordinates_not_present">A koordináták nem lettek megadva a kép kiválasztásakor.</string>
<string name="error_fetching_nearby_places">Hiba a közeli helyek elérésekor.</string>

View file

@ -46,8 +46,6 @@
<string name="share_title_hint">Judul</string>
<string name="share_description_hint">Deskripsi</string>
<string name="login_failed_network">Tidak dapat login - kesalahan pada jaringan</string>
<string name="login_failed_username">Tidak dapat masuk log - harap periksa nama pengguna Anda</string>
<string name="login_failed_password">Tidak dapat masuk log - harap periksa kata sandi Anda</string>
<string name="login_failed_throttled" fuzzy="true">Terlalu banyak usaha yang gagal. Harap coba lagi dalam beberapa menit</string>
<string name="login_failed_blocked">Maaf, pengguna ini telah diblokir di Commons</string>
<string name="login_failed_generic">Gagal masuk log</string>

View file

@ -49,8 +49,6 @@
<string name="add_title_toast">Gefðu þessari skrá einhvern titil</string>
<string name="share_description_hint">Lýsing</string>
<string name="login_failed_network">Innskráning mistókst - bilun í neti</string>
<string name="login_failed_username">Innskráning mistókst. Athugaðu notandanafnið þitt</string>
<string name="login_failed_password">Innskráning mistókst. Athugaðu lykilorðið þitt</string>
<string name="login_failed_throttled">Of margar misteknar tilraunir. Reyndu aftur eftir nokkrar mínútur.</string>
<string name="login_failed_blocked">Því miður, þessi notandi hefur verið bannaður á Commons</string>
<string name="login_failed_2fa_needed">Þú verður að setja inn tveggja-þrepa auðkenningarkóðann þinn.</string>

View file

@ -53,8 +53,6 @@
<string name="share_title_hint">Titolo</string>
<string name="share_description_hint">Descrizione</string>
<string name="login_failed_network">Impossibile effettuare l\'accesso - errore di rete</string>
<string name="login_failed_username">Impossibile effettuare l\'accesso - controlla il nome utente</string>
<string name="login_failed_password">Impossibile effettuare l\'accesso - controlla la password</string>
<string name="login_failed_throttled">Troppi tentativi falliti. Riprova tra alcuni minuti.</string>
<string name="login_failed_blocked">Spiacente, questo utente è stato bloccato su Commons</string>
<string name="login_failed_2fa_needed">Devi fornire il tuo codice di autenticazione a due fattori.</string>

View file

@ -58,8 +58,7 @@
<string name="add_title_toast">נא לתת כותרת לקובץ הזה</string>
<string name="share_description_hint">תיאור</string>
<string name="login_failed_network">לא ניתן להיכנס כשל בתקשורת</string>
<string name="login_failed_username">לא ניתן להיכנס נא לבדוק את שם המשתמש שלך</string>
<string name="login_failed_password">לא ניתן להיכנס נא לבדוק את הססמה שלך</string>
<string name="login_failed_wrong_credentials">לא ניתן להיכנס לחשבון נא לבדוק את שם המשתמש ואת הסיסמה</string>
<string name="login_failed_throttled">יותר מדי ניסיונות כושלים להיכנס. נא לנסות שוב בעוד מספר דקות.</string>
<string name="login_failed_blocked">סליחה, החשבון הזה חסום בוויקישיתוף</string>
<string name="login_failed_2fa_needed">יש לספק את קוד האימות הדו־שלבי שלך.</string>
@ -283,4 +282,6 @@
<string name="share_app_title">שיתוף היישום</string>
<string name="share_coordinates_not_present">לא צוינו קואורדינטות בעת בחירת התמונה</string>
<string name="error_fetching_nearby_places">שגיאה באחזור המקומות בסביבתך.</string>
<string name="menu_set_wallpaper">הגדרת רקע</string>
<string name="wallpaper_set_successfully">הרקע הוגדר בהצלחה!</string>
</resources>

View file

@ -1,10 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Authors:
* Shirayuki
* Takot
-->
<resources>
<string name="crash_dialog_title">コモンズがクラッシュしました</string>
<string name="crash_dialog_text">エラーが発生しました!</string>
<string name="crash_dialog_title">コモンズアプリがクラッシュしました</string>
<string name="crash_dialog_text">おおっと、何かおかしいようです!</string>
<string name="crash_dialog_comment_prompt">何をしていたかを記入してメールでお送りください。それをもとに問題点を解決します。</string>
<string name="crash_dialog_ok_toast">ありがとうございます!</string>
</resources>

View file

@ -58,8 +58,6 @@
<string name="add_title_toast">ファイル名をつけてください</string>
<string name="share_description_hint">説明</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_blocked">申し訳ありませんが、この利用者はコモンズでブロックされています。</string>
<string name="login_failed_2fa_needed">2要素認証コードを提供する必要があります。</string>
@ -138,7 +136,7 @@
<string name="tutorial_1_text">ウィキメディア・コモンズにはウィキペディアで使用する画像のほぼすべてが保管されています。</string>
<string name="tutorial_1_subtext">あなたの画像は世界中の人々が学習する助けになります!</string>
<string name="tutorial_2_text">アップロードする画像はあなたご本人が撮影したものかあなたが単独で制作したものに限定します。</string>
<string name="tutorial_2_subtext">自然 (動植物、山)\n• 道具 (自転車、駅)\n• 著名人 (市区村長・都道府県知事、自分が会ったオリンピック選手)</string>
<string name="tutorial_2_subtext">自然 (動植物、山)\n• 道具 (自転車、駅)\n• 著名人 (市区村長・都道府県知事、自分が会ったオリンピック選手)</string>
<string name="tutorial_2_subtext_1">自然物 (動植物、山)</string>
<string name="tutorial_2_subtext_2">道具 (自転車、駅)</string>
<string name="tutorial_2_subtext_3">著名人 (市区村長・都道府県知事、自分が会ったオリンピック選手)</string>

View file

@ -40,8 +40,6 @@
<string name="share_title_hint">Sesirah</string>
<string name="share_description_hint">Wedharan</string>
<string name="login_failed_network">Ora bisa mlebu log - jaringané gagal</string>
<string name="login_failed_username">Ora bisa mlebu log - tiliki jeneng panganggoné panjenengan</string>
<string name="login_failed_password">Ora bisa mlebu log - tiliki tembung wadiné panjenengan</string>
<string name="login_failed_throttled">Kakèhan upaya sing gagal. Jajalana manèh mengko.</string>
<string name="login_failed_blocked">Ngapunten, panganggo iki wis diblokir ing Commons</string>
<string name="login_failed_2fa_needed">Panjenengan kudu ngisi kodhe otèntifikasi rong faktoré panjenengan</string>

View file

@ -42,8 +42,6 @@
<string name="share_title_hint">სათაური</string>
<string name="share_description_hint">აღწერა</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_blocked">უკაცრავად, ეს მომხმარებელი დაბლოკილია ვიკისაწყობში</string>
<string name="login_failed_2fa_needed">თქვენ უნდა შეიყვანოთ ორფაქტორიანი ავტორიზაციის კოდი.</string>

View file

@ -42,8 +42,6 @@
<string name="share_title_hint">Azwel</string>
<string name="share_description_hint">Aglam</string>
<string name="login_failed_network">Ur izmir ara ad yeqqen - tuccḍa n uẓeṭṭa</string>
<string name="login_failed_username">Ur izmir ara ad yeqqen - wali isem-ik n useqdac</string>
<string name="login_failed_password">Ur izmir ara ad yeqqen - wali awal-ik uffir</string>
<string name="login_failed_throttled">Ddeq n uɛraḍ ur yeddin ara. Ɛreḍ akka di kra n tisdatin</string>
<string name="login_failed_blocked">Suref-aɣ, aseqdac-agi yewḥel di Commons</string>
<string name="login_failed_2fa_needed">Yessefk ad d-muddeḍ tangalt n n usesbteb s snat n tarrayin.</string>

View file

@ -35,8 +35,6 @@
<string name="share_title_hint">ចំណងជើង</string>
<string name="share_description_hint">បរិយាយ</string>
<string name="login_failed_network">មិនអាចកត់ឈ្មោះចូល - បណ្តាញ network បរាជ័យ</string>
<string name="login_failed_username">មិនអាចកត់ឈ្មោះចូល - សូមពិនិត្យឈ្មោះអ្នកប្រើប្រាស់របស់អ្នក</string>
<string name="login_failed_password">មិនអាចកត់ឈ្មោះចូល - សូមពិនិត្យលេខសម្ងាត់របស់អ្នក</string>
<string name="login_failed_throttled" fuzzy="true">ការព្យាយាមមិនបានសម្រេចមានចំនួនច្រើនដងពេក។ សូមព្យាយាមម្តងទៀតនៅប៉ុន្មាននាទីក្រោយ។</string>
<string name="login_failed_blocked">សូមអភ័យទោស អ្នកប្រើប្រាស់រូបនេះត្រូវបានហាមឃាត់នៅ Commons</string>
<string name="login_failed_generic">កត់ឈ្មោះចូលបរាជ័យ</string>

View file

@ -42,8 +42,6 @@
<string name="share_title_hint">제목</string>
<string name="share_description_hint">설명</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_blocked">죄송합니다, 이 사용자는 공용에서 차단되였습니다</string>
<string name="login_failed_2fa_needed">두인자검증부호를 제공해야 합니다.</string>

View file

@ -58,8 +58,7 @@
<string name="add_title_toast">이 파일의 제목을 지정해 주십시오</string>
<string name="share_description_hint">설명</string>
<string name="login_failed_network">로그인할 수 없습니다 - 네트워크 오류입니다</string>
<string name="login_failed_username">로그인할 수 없습니다 - 사용자 이름을 확인하세요</string>
<string name="login_failed_password">로그인할 수 없습니다 - 비밀번호를 확인하세요</string>
<string name="login_failed_wrong_credentials">로그인할 수 없습니다 - 사용자 이름과 비밀번호를 확인해 주십시오</string>
<string name="login_failed_throttled">실패한 시도가 너무 많습니다. 몇 분 후에 다시 시도하세요.</string>
<string name="login_failed_blocked">죄송합니다, 이 사용자는 공용에서 차단되었습니다</string>
<string name="login_failed_2fa_needed">2요소 인증 코드를 제공해야 합니다.</string>
@ -276,4 +275,6 @@
<string name="share_app_title">앱 공유</string>
<string name="share_coordinates_not_present">그림 선택 중에 좌표가 지정되지 않았습니다</string>
<string name="error_fetching_nearby_places">주변 장소를 가져오는데 오류가 있습니다.</string>
<string name="menu_set_wallpaper">배경화면 설정</string>
<string name="wallpaper_set_successfully">배경화면을 성공적으로 설정했습니다!</string>
</resources>

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