diff --git a/.travis.yml b/.travis.yml index 267625fdd..3e2380e5f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,9 +22,14 @@ before_script: - emulator -avd test -no-audio -no-window & - android-wait-for-emulator script: - - ./gradlew test connectedAndroidTest -stacktrace + - ./gradlew check connectedCheck jacocoTestReport -stacktrace +after_success: + - bash <(curl -s https://codecov.io/bash) after_failure: + - echo '*** Connected Test Rsults ***' - w3m -dump ${TRAVIS_BUILD_DIR}/app/build/reports/androidTests/connected/*Test.html + - echo '*** Lint Results ***' + - cat ${TRAVIS_BUILD_DIR}/app/build/reports/lint-results.xml jdk: # - openjdk8 # not yet available - oraclejdk8 diff --git a/CREDITS b/CREDITS index 20a64ad9f..a0e1fcec4 100644 --- a/CREDITS +++ b/CREDITS @@ -19,9 +19,17 @@ their contribution to the product. * Veyndan Stuart * Vivek Maskara * Neslihan Turan +* Wikimedia Czech Republic (host for the Prague pre-hackathon 2017) +* Dinu Kumarasiri +* Dmitry Brant +* Adam Shorland +* John Lubbock 3rd party open source libraries used: * Butterknife * GSON * Timber * MapBox + +3rd party open source apps from which significant code has been reused: +* Android Wikipedia app https://github.com/wikimedia/apps-android-wikipedia diff --git a/app/build.gradle b/app/build.gradle index 60db1b27a..b3f898183 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,4 +1,5 @@ apply plugin: 'com.android.application' +apply plugin: 'jacoco-android' apply from: 'quality.gradle' dependencies { @@ -53,6 +54,9 @@ android { 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' } + debug { + testCoverageEnabled true + } } lintOptions { diff --git a/app/src/androidTest/java/fr/free/nrw/commons/NearbyControllerTest.java b/app/src/androidTest/java/fr/free/nrw/commons/NearbyControllerTest.java index 1eb73b3d1..ab528c2ab 100644 --- a/app/src/androidTest/java/fr/free/nrw/commons/NearbyControllerTest.java +++ b/app/src/androidTest/java/fr/free/nrw/commons/NearbyControllerTest.java @@ -29,7 +29,7 @@ public class NearbyControllerTest { } @Test public void testNullAttractions() { - LatLng location = new LatLng(0, 0); + LatLng location = new LatLng(0, 0, 0); List options = NearbyController.loadAttractionsFromLocationToBaseMarkerOptions( @@ -42,7 +42,7 @@ public class NearbyControllerTest { } @Test public void testEmptyList() { - LatLng location = new LatLng(0, 0); + LatLng location = new LatLng(0, 0, 0); List emptyList = new ArrayList<>(); List options = diff --git a/app/src/main/java/fr/free/nrw/commons/MediaDataExtractor.java b/app/src/main/java/fr/free/nrw/commons/MediaDataExtractor.java index 280309d3d..8bbc03f4d 100644 --- a/app/src/main/java/fr/free/nrw/commons/MediaDataExtractor.java +++ b/app/src/main/java/fr/free/nrw/commons/MediaDataExtractor.java @@ -262,7 +262,7 @@ public class MediaDataExtractor { NodeList childNodes = parentNode.getChildNodes(); double latitudeText = Double.parseDouble(childNodes.item(1).getTextContent()); double longitudeText = Double.parseDouble(childNodes.item(2).getTextContent()); - LatLng coordinates = new LatLng(latitudeText, longitudeText); + LatLng coordinates = new LatLng(latitudeText, longitudeText, 0); return coordinates.getPrettyCoordinateString(); } diff --git a/app/src/main/java/fr/free/nrw/commons/Utils.java b/app/src/main/java/fr/free/nrw/commons/Utils.java index 98388f660..8279af808 100644 --- a/app/src/main/java/fr/free/nrw/commons/Utils.java +++ b/app/src/main/java/fr/free/nrw/commons/Utils.java @@ -2,7 +2,10 @@ package fr.free.nrw.commons; import android.content.Context; import android.net.Uri; +import android.os.Build; import android.preference.PreferenceManager; +import android.text.Html; +import android.text.Spanned; import fr.free.nrw.commons.settings.Prefs; import timber.log.Timber; @@ -78,6 +81,19 @@ public class Utils { } } + /** Fix Html.fromHtml is deprecated problem + * @param source provided Html string + * @return returned Spanned of appropriate method according to version check + * */ + public static Spanned fromHtml(String source) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + return Html.fromHtml(source, Html.FROM_HTML_MODE_LEGACY); + } else { + //noinspection deprecation + return Html.fromHtml(source); + } + } + /** * Strips localization symbols from a string. * Removes the suffix after "@" and quotes. diff --git a/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.java b/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.java index a42fef7cf..f6171e360 100644 --- a/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.java @@ -30,9 +30,10 @@ public class LoginActivity extends AccountAuthenticatorActivity { private Button loginButton; private EditText usernameEdit; - EditText passwordEdit; - EditText twoFactorEdit; + private EditText passwordEdit; + private EditText twoFactorEdit; ProgressDialog progressDialog; + private LoginTextWatcher textWatcher = new LoginTextWatcher(); private CommonsApplication app; @@ -52,10 +53,9 @@ public class LoginActivity extends AccountAuthenticatorActivity { prefs = getSharedPreferences("fr.free.nrw.commons", MODE_PRIVATE); - TextWatcher loginEnabler = newLoginTextWatcher(); - usernameEdit.addTextChangedListener(loginEnabler); - passwordEdit.addTextChangedListener(loginEnabler); - twoFactorEdit.addTextChangedListener(loginEnabler); + usernameEdit.addTextChangedListener(textWatcher); + passwordEdit.addTextChangedListener(textWatcher); + twoFactorEdit.addTextChangedListener(textWatcher); passwordEdit.setOnEditorActionListener( newLoginInputActionListener() ); loginButton.setOnClickListener(new View.OnClickListener() { @@ -72,27 +72,24 @@ public class LoginActivity extends AccountAuthenticatorActivity { }); } - private TextWatcher newLoginTextWatcher() { - return new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) { } + private class LoginTextWatcher implements TextWatcher { + @Override + public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) { + } - @Override - public void onTextChanged(CharSequence charSequence, int start, int count, int after) { } + @Override + public void onTextChanged(CharSequence charSequence, int start, int count, int after) { + } - @Override - public void afterTextChanged(Editable editable) { - if( - usernameEdit.getText().length() != 0 && - passwordEdit.getText().length() != 0 && - ( BuildConfig.DEBUG || twoFactorEdit.getText().length() != 0 || twoFactorEdit.getVisibility() != View.VISIBLE ) - ) { - loginButton.setEnabled(true); - } else { - loginButton.setEnabled(false); - } + @Override + public void afterTextChanged(Editable editable) { + if (usernameEdit.getText().length() != 0 && passwordEdit.getText().length() != 0 && + (BuildConfig.DEBUG || twoFactorEdit.getText().length() != 0 || twoFactorEdit.getVisibility() != View.VISIBLE)) { + loginButton.setEnabled(true); + } else { + loginButton.setEnabled(false); } - }; + } } private TextView.OnEditorActionListener newLoginInputActionListener() { @@ -139,6 +136,9 @@ public class LoginActivity extends AccountAuthenticatorActivity { } catch (Exception e) { e.printStackTrace(); } + usernameEdit.removeTextChangedListener(textWatcher); + passwordEdit.removeTextChangedListener(textWatcher); + twoFactorEdit.removeTextChangedListener(textWatcher); super.onDestroy(); } diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategorizationFragment.java b/app/src/main/java/fr/free/nrw/commons/category/CategorizationFragment.java index ab63ef632..3af2f6473 100644 --- a/app/src/main/java/fr/free/nrw/commons/category/CategorizationFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/category/CategorizationFragment.java @@ -1,6 +1,5 @@ package fr.free.nrw.commons.category; -import android.app.Activity; import android.content.ContentProviderClient; import android.content.DialogInterface; import android.content.SharedPreferences; @@ -58,6 +57,7 @@ public class CategorizationFragment extends Fragment { ProgressBar categoriesSearchInProgress; TextView categoriesNotFoundView; TextView categoriesSkip; + private CategoryTextWatcher textWatcher = new CategoryTextWatcher(); CategoriesAdapter categoriesAdapter; ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(2); @@ -442,21 +442,7 @@ public class CategorizationFragment extends Fragment { } }); - categoriesFilter.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) { - } - - @Override - public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) { - startUpdatingCategoryList(); - } - - @Override - public void afterTextChanged(Editable editable) { - - } - }); + categoriesFilter.addTextChangedListener(textWatcher); startUpdatingCategoryList(); @@ -469,14 +455,6 @@ public class CategorizationFragment extends Fragment { inflater.inflate(R.menu.fragment_categorization, menu); } - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setHasOptionsMenu(true); - getActivity().setTitle(R.string.categories_activity_title); - client = getActivity().getContentResolver().acquireContentProviderClient(CategoryContentProvider.AUTHORITY); - } - @Override public void onResume() { super.onResume(); @@ -498,6 +476,12 @@ public class CategorizationFragment extends Fragment { } } + @Override + public void onDestroyView() { + categoriesFilter.removeTextChangedListener(textWatcher); + super.onDestroyView(); + } + public void backButtonDialog() { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); @@ -581,8 +565,27 @@ public class CategorizationFragment extends Fragment { } @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - onCategoriesSaveHandler = (OnCategoriesSaveHandler) activity; + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + setHasOptionsMenu(true); + onCategoriesSaveHandler = (OnCategoriesSaveHandler) getActivity(); + getActivity().setTitle(R.string.categories_activity_title); + client = getActivity().getContentResolver().acquireContentProviderClient(CategoryContentProvider.AUTHORITY); + } + + private class CategoryTextWatcher implements TextWatcher { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) { + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) { + startUpdatingCategoryList(); + } + + @Override + public void afterTextChanged(Editable editable) { + + } } } diff --git a/app/src/main/java/fr/free/nrw/commons/concurrency/BackgroundPoolExceptionHandler.java b/app/src/main/java/fr/free/nrw/commons/concurrency/BackgroundPoolExceptionHandler.java new file mode 100644 index 000000000..f971eb8cc --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/concurrency/BackgroundPoolExceptionHandler.java @@ -0,0 +1,21 @@ +package fr.free.nrw.commons.concurrency; + +import android.support.annotation.NonNull; +import fr.free.nrw.commons.BuildConfig; + +public class BackgroundPoolExceptionHandler implements ExceptionHandler { + @Override + public void onException(@NonNull final Throwable t) { + //Crash for debug build + if (BuildConfig.DEBUG) { + Thread thread = new Thread(new Runnable() { + @Override + public void run() { + throw new RuntimeException(t); + } + }); + thread.start(); + } + } +} + diff --git a/app/src/main/java/fr/free/nrw/commons/concurrency/ExceptionAwareThreadPoolExecutor.java b/app/src/main/java/fr/free/nrw/commons/concurrency/ExceptionAwareThreadPoolExecutor.java new file mode 100644 index 000000000..8196b26e7 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/concurrency/ExceptionAwareThreadPoolExecutor.java @@ -0,0 +1,41 @@ +package fr.free.nrw.commons.concurrency; + +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadFactory; + + +class ExceptionAwareThreadPoolExecutor extends ScheduledThreadPoolExecutor { + + private final ExceptionHandler exceptionHandler; + + public ExceptionAwareThreadPoolExecutor(int corePoolSize, ThreadFactory threadFactory, + ExceptionHandler exceptionHandler) { + super(corePoolSize, threadFactory); + this.exceptionHandler = exceptionHandler; + } + + @Override + protected void afterExecute(Runnable r, Throwable t) { + super.afterExecute(r, t); + if (t == null && r instanceof Future) { + try { + Future future = (Future) r; + if (future.isDone()) future.get(); + } catch (CancellationException | InterruptedException e) { + //ignore + } catch (ExecutionException e) { + t = e.getCause() != null ? e.getCause() : e; + } catch (Exception e) { + t = e; + } + } + + if (t != null) { + exceptionHandler.onException(t); + } + } +} + diff --git a/app/src/main/java/fr/free/nrw/commons/concurrency/ExceptionHandler.java b/app/src/main/java/fr/free/nrw/commons/concurrency/ExceptionHandler.java new file mode 100644 index 000000000..cc5a4c008 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/concurrency/ExceptionHandler.java @@ -0,0 +1,7 @@ +package fr.free.nrw.commons.concurrency; + +import android.support.annotation.NonNull; + +public interface ExceptionHandler { + void onException(@NonNull Throwable t); +} diff --git a/app/src/main/java/fr/free/nrw/commons/concurrency/ThreadFactoryMaker.java b/app/src/main/java/fr/free/nrw/commons/concurrency/ThreadFactoryMaker.java new file mode 100644 index 000000000..221ebef41 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/concurrency/ThreadFactoryMaker.java @@ -0,0 +1,29 @@ +package fr.free.nrw.commons.concurrency; + +import android.os.Process; +import android.support.annotation.NonNull; + +import java.util.concurrent.ThreadFactory; + +class ThreadFactoryMaker { + public static ThreadFactory get(@NonNull final String name, final int priority) { + return new ThreadFactory() { + private int count = 0; + + @Override + public Thread newThread(final Runnable runnable) { + count++; + Runnable wrapperRunnable = new Runnable() { + @Override + public void run() { + Process.setThreadPriority(priority); + runnable.run(); + } + }; + Thread t = new Thread(wrapperRunnable, String.format("%s-%s", name, count)); + return t; + } + }; + } +} + diff --git a/app/src/main/java/fr/free/nrw/commons/concurrency/ThreadPoolExecutorService.java b/app/src/main/java/fr/free/nrw/commons/concurrency/ThreadPoolExecutorService.java new file mode 100644 index 000000000..1516d85d8 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/concurrency/ThreadPoolExecutorService.java @@ -0,0 +1,101 @@ +package fr.free.nrw.commons.concurrency; + +import android.os.Process; +import android.support.annotation.NonNull; + +import java.util.concurrent.Callable; +import java.util.concurrent.Executor; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +public class ThreadPoolExecutorService implements Executor { + private final ScheduledThreadPoolExecutor backgroundPool; + + private ThreadPoolExecutorService(Builder b) { + backgroundPool = new ExceptionAwareThreadPoolExecutor(b.poolSize, + ThreadFactoryMaker.get(b.name, b.priority), b.exceptionHandler); + } + + public ScheduledFuture schedule(Callable callable, long time, TimeUnit timeUnit) { + return backgroundPool.schedule(callable, time, timeUnit); + } + + public ScheduledFuture schedule(Runnable runnable) { + return schedule(runnable, 0, TimeUnit.SECONDS); + } + + public ScheduledFuture schedule(Runnable runnable, long time, TimeUnit timeUnit) { + return backgroundPool.schedule(runnable, time, timeUnit); + } + + public ScheduledFuture scheduleAtFixedRate(final Runnable task, long initialDelay, + long period, final TimeUnit timeUnit) { + return backgroundPool.scheduleAtFixedRate(task, initialDelay, period, timeUnit); + } + + public void shutdown() { + backgroundPool.shutdown(); + } + + @Override + public void execute(Runnable command) { + backgroundPool.execute(command); + } + + /** + * Builder class for {@link ThreadPoolExecutorService} + */ + public static class Builder { + //Required + private final String name; + + //Optional + private int poolSize = 1; + private int priority = Process.THREAD_PRIORITY_BACKGROUND + Process.THREAD_PRIORITY_MORE_FAVORABLE; + private ExceptionHandler exceptionHandler = null; + + /** + * @param name the name of the threads in the service. if there are N threads, + * the thread names will be like name-1, name-2, name-3,...,name-N + */ + public Builder(@NonNull String name) { + this.name = name; + } + + /** + * @param poolSize the number of threads to keep in the pool + * @throws IllegalArgumentException if size of pool <=0 + */ + public Builder setPoolSize(int poolSize) throws IllegalArgumentException { + if (poolSize <= 0) { + throw new IllegalArgumentException("Pool size must be grater than 0"); + } + this.poolSize = poolSize; + return this; + } + + /** + * @param priority Priority of the threads in the service. You can supply a constant from + * {@link android.os.Process} + * By default, the priority is set to a value slightly higher than the normal + * background priority + */ + public Builder setPriority(int priority) { + this.priority = priority; + return this; + } + + /** + * @param handler The handler to use to handle exceptions in the service + */ + public Builder setExceptionHandler(ExceptionHandler handler) { + this.exceptionHandler = handler; + return this; + } + + public ThreadPoolExecutorService build() { + return new ThreadPoolExecutorService(this); + } + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsActivity.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsActivity.java index 1586d8202..7c03c33c7 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsActivity.java @@ -11,6 +11,7 @@ import android.database.DataSetObserver; import android.os.Bundle; import android.os.IBinder; import android.preference.PreferenceManager; +import android.support.annotation.NonNull; import android.support.v4.app.FragmentManager; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; @@ -22,8 +23,11 @@ import android.view.View; import android.widget.Adapter; import android.widget.AdapterView; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; + import java.util.ArrayList; -import java.util.Locale; import butterknife.ButterKnife; import fr.free.nrw.commons.CommonsApplication; @@ -35,6 +39,7 @@ import fr.free.nrw.commons.hamburger.HamburgerMenuContainer; import fr.free.nrw.commons.media.MediaDetailPagerFragment; import fr.free.nrw.commons.settings.Prefs; import fr.free.nrw.commons.upload.UploadService; +import fr.free.nrw.commons.utils.ExecutorUtils; import timber.log.Timber; public class ContributionsActivity @@ -234,18 +239,7 @@ public class ContributionsActivity ((CursorAdapter)contributionsList.getAdapter()).swapCursor(cursor); } - if (cursor.getCount() == 0 - && Locale.getDefault().getISO3Language().equals(Locale.ENGLISH.getISO3Language())) { - //cursor count is zero and language is english - - // we need to set the message for 0 case explicitly. - getSupportActionBar().setSubtitle(getResources() - .getString(R.string.contributions_subtitle_zero)); - } else { - getSupportActionBar().setSubtitle(getResources() - .getQuantityString(R.plurals.contributions_subtitle, - cursor.getCount(), - cursor.getCount())); - } + setUploadCount(); contributionsList.clearSyncMessage(); notifyAndMigrateDataSetObservers(); @@ -275,6 +269,27 @@ public class ContributionsActivity return contributionsList.getAdapter().getCount(); } + private void setUploadCount() { + UploadCountClient uploadCountClient = new UploadCountClient(); + CommonsApplication application = CommonsApplication.getInstance(); + ListenableFuture future = uploadCountClient + .getUploadCount(application.getCurrentAccount().name); + Futures.addCallback(future, new FutureCallback() { + @Override + public void onSuccess(Integer uploadCount) { + getSupportActionBar().setSubtitle(getResources() + .getQuantityString(R.plurals.contributions_subtitle, + uploadCount, + uploadCount)); + } + + @Override + public void onFailure(@NonNull Throwable t) { + Timber.e(t, "Fetching upload count failed"); + } + }, ExecutorUtils.uiExecutor()); + } + @Override public void notifyDatasetChanged() { // Do nothing for now diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/UploadCountClient.java b/app/src/main/java/fr/free/nrw/commons/contributions/UploadCountClient.java new file mode 100644 index 000000000..5aaf0fdbe --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/contributions/UploadCountClient.java @@ -0,0 +1,56 @@ +package fr.free.nrw.commons.contributions; + +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.SettableFuture; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.Locale; + +import fr.free.nrw.commons.CommonsApplication; +import fr.free.nrw.commons.concurrency.BackgroundPoolExceptionHandler; +import fr.free.nrw.commons.concurrency.ThreadPoolExecutorService; +import timber.log.Timber; + +public class UploadCountClient { + private ThreadPoolExecutorService threadPoolExecutor; + + public UploadCountClient() { + threadPoolExecutor = new ThreadPoolExecutorService.Builder("bg-pool") + .setPoolSize(Runtime.getRuntime().availableProcessors()) + .setExceptionHandler(new BackgroundPoolExceptionHandler()) + .build(); + } + + private static final String UPLOAD_COUNT_URL_TEMPLATE = + "https://tools.wmflabs.org/urbanecmbot/uploadsbyuser/uploadsbyuser.py?user=%s"; + + public ListenableFuture getUploadCount(final String userName) { + final SettableFuture future = SettableFuture.create(); + threadPoolExecutor.schedule(new Runnable() { + @Override + public void run() { + URL url; + try { + url = new URL(String.format(Locale.ENGLISH, UPLOAD_COUNT_URL_TEMPLATE, userName)); + HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); + try { + BufferedReader bufferedReader = new BufferedReader(new + InputStreamReader(urlConnection.getInputStream())); + String uploadCount = bufferedReader.readLine(); + bufferedReader.close(); + future.set(Integer.parseInt(uploadCount)); + } finally { + urlConnection.disconnect(); + } + } catch (Exception e) { + Timber.e("Error getting upload count Error", e); + future.setException(e); + } + } + }); + return future; + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/location/LatLng.java b/app/src/main/java/fr/free/nrw/commons/location/LatLng.java index acd67ebf7..1e3202914 100644 --- a/app/src/main/java/fr/free/nrw/commons/location/LatLng.java +++ b/app/src/main/java/fr/free/nrw/commons/location/LatLng.java @@ -2,8 +2,9 @@ package fr.free.nrw.commons.location; public class LatLng { - public final double latitude; - public final double longitude; + private final double latitude; + private final double longitude; + private final float accuracy; /** Accepts latitude and longitude. * North and South values are cut off at 90° @@ -11,13 +12,14 @@ public class LatLng { * @param latitude double value * @param longitude double value */ - public LatLng(double latitude, double longitude) { + public LatLng(double latitude, double longitude, float accuracy) { if(-180.0D <= longitude && longitude < 180.0D) { this.longitude = longitude; } else { this.longitude = ((longitude - 180.0D) % 360.0D + 360.0D) % 360.0D - 180.0D; } this.latitude = Math.max(-90.0D, Math.min(90.0D, latitude)); + this.accuracy = accuracy; } public int hashCode() { @@ -93,4 +95,31 @@ public class LatLng { return formatCoordinate(this.latitude) + " " + this.getNorthSouth() + ", " + formatCoordinate(this.longitude) + " " + this.getEastWest(); } + + /** + * Return the location accuracy in meter. + * + * @return float + */ + public float getAccuracy() { + return accuracy; + } + + /** + * Return the longitude in degrees. + * + * @return double + */ + public double getLongitude() { + return longitude; + } + + /** + * Return the latitude in degrees. + * + * @return double + */ + public double getLatitude() { + return latitude; + } } diff --git a/app/src/main/java/fr/free/nrw/commons/location/LocationServiceManager.java b/app/src/main/java/fr/free/nrw/commons/location/LocationServiceManager.java index 3c0d2b23b..e87eaf92e 100644 --- a/app/src/main/java/fr/free/nrw/commons/location/LocationServiceManager.java +++ b/app/src/main/java/fr/free/nrw/commons/location/LocationServiceManager.java @@ -14,6 +14,7 @@ public class LocationServiceManager implements LocationListener { private String provider; private LocationManager locationManager; private LatLng latestLocation; + private Float latestLocationAccuracy; public LocationServiceManager(Context context) { this.locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); @@ -24,6 +25,16 @@ public class LocationServiceManager implements LocationListener { return latestLocation; } + /** + * Returns the accuracy of the location. The measurement is + * given as a radius in meter of 68 % confidence. + * + * @return Float + */ + public Float getLatestLocationAccuracy() { + return latestLocationAccuracy; + } + /** Registers a LocationManager to listen for current location. */ public void registerLocationManager() { @@ -57,9 +68,11 @@ public class LocationServiceManager implements LocationListener { public void onLocationChanged(Location location) { double currentLatitude = location.getLatitude(); double currentLongitude = location.getLongitude(); - Timber.d("Latitude: %f Longitude: %f", currentLatitude, currentLongitude); + latestLocationAccuracy = location.getAccuracy(); + Timber.d("Latitude: %f Longitude: %f Accuracy %f", + currentLatitude, currentLongitude, latestLocationAccuracy); - latestLocation = new LatLng(currentLatitude, currentLongitude); + latestLocation = new LatLng(currentLatitude, currentLongitude, latestLocationAccuracy); } @Override diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyController.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyController.java index dc6efe754..b38c01022 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyController.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyController.java @@ -109,8 +109,8 @@ public class NearbyController { nearbyBaseMarker.title(place.name); nearbyBaseMarker.position( new com.mapbox.mapboxsdk.geometry.LatLng( - place.location.latitude, - place.location.longitude)); + place.location.getLatitude(), + place.location.getLongitude())); nearbyBaseMarker.place(place); nearbyBaseMarker.icon(icon); diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyInfoDialog.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyInfoDialog.java index 9308413da..f2c7674b6 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyInfoDialog.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyInfoDialog.java @@ -57,7 +57,7 @@ public class NearbyInfoDialog extends OverlayDialog { Bundle bundle = getArguments(); placeTitle.setText(bundle.getString(ARG_TITLE)); placeDescription.setText(bundle.getString(ARG_DESC)); - location = new LatLng(bundle.getDouble(ARG_LATITUDE), bundle.getDouble(ARG_LONGITUDE)); + location = new LatLng(bundle.getDouble(ARG_LATITUDE), bundle.getDouble(ARG_LONGITUDE), 0); getArticleLink(bundle); } @@ -122,8 +122,8 @@ public class NearbyInfoDialog extends OverlayDialog { Bundle bundle = new Bundle(); bundle.putString(ARG_TITLE, place.name); bundle.putString(ARG_DESC, place.description); - bundle.putDouble(ARG_LATITUDE, place.location.latitude); - bundle.putDouble(ARG_LONGITUDE, place.location.longitude); + bundle.putDouble(ARG_LATITUDE, place.location.getLatitude()); + bundle.putDouble(ARG_LONGITUDE, place.location.getLongitude()); bundle.putParcelable(ARG_SITE_LINK, place.siteLinks); mDialog.setArguments(bundle); DialogUtil.showSafely(fragmentActivity, mDialog); @@ -138,7 +138,8 @@ public class NearbyInfoDialog extends OverlayDialog { @OnClick(R.id.link_preview_directions_button) void onDirectionsClick() { //Open map app at given position - Uri gmmIntentUri = Uri.parse("geo:0,0?q=" + location.latitude + "," + location.longitude); + Uri gmmIntentUri = Uri.parse( + "geo:0,0?q=" + location.getLatitude() + "," + location.getLongitude()); Intent mapIntent = new Intent(Intent.ACTION_VIEW, gmmIntentUri); if (mapIntent.resolveActivity(getActivity().getPackageManager()) != null) { diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyListFragment.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyListFragment.java index bc9e22a3f..b3759f228 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyListFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyListFragment.java @@ -85,8 +85,8 @@ public class NearbyListFragment extends ListFragment { Place place = (Place) listview.getItemAtPosition(position); LatLng placeLatLng = place.location; - double latitude = placeLatLng.latitude; - double longitude = placeLatLng.longitude; + double latitude = placeLatLng.getLatitude(); + double longitude = placeLatLng.getLongitude(); Timber.d("Item at position %d has coords: Lat: %f Long: %f", position, latitude, longitude); diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyMapFragment.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyMapFragment.java index b80d16313..0cae80df8 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyMapFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyMapFragment.java @@ -1,5 +1,6 @@ package fr.free.nrw.commons.nearby; +import android.graphics.Color; import android.net.Uri; import android.os.Bundle; import android.preference.PreferenceManager; @@ -14,6 +15,8 @@ import com.google.gson.GsonBuilder; import com.google.gson.reflect.TypeToken; import com.mapbox.mapboxsdk.Mapbox; import com.mapbox.mapboxsdk.annotations.Marker; +import com.mapbox.mapboxsdk.annotations.MarkerOptions; +import com.mapbox.mapboxsdk.annotations.PolygonOptions; import com.mapbox.mapboxsdk.camera.CameraPosition; import com.mapbox.mapboxsdk.constants.Style; import com.mapbox.mapboxsdk.geometry.LatLng; @@ -24,6 +27,7 @@ import com.mapbox.mapboxsdk.maps.OnMapReadyCallback; import com.mapbox.services.android.telemetry.MapboxTelemetry; import java.lang.reflect.Type; +import java.util.ArrayList; import java.util.List; import fr.free.nrw.commons.R; @@ -79,7 +83,7 @@ public class NearbyMapFragment extends android.support.v4.app.Fragment { MapboxMapOptions options = new MapboxMapOptions() .styleUrl(Style.OUTDOORS) .camera(new CameraPosition.Builder() - .target(new LatLng(curLatLng.latitude, curLatLng.longitude)) + .target(new LatLng(curLatLng.getLatitude(), curLatLng.getLongitude())) .zoom(11) .build()); @@ -102,6 +106,8 @@ public class NearbyMapFragment extends android.support.v4.app.Fragment { return false; } }); + + addCurrentLocationMarker(mapboxMap); } }); if (PreferenceManager.getDefaultSharedPreferences(getActivity()).getBoolean("theme",true)) { @@ -111,6 +117,53 @@ public class NearbyMapFragment extends android.support.v4.app.Fragment { } } + /** + * Adds a marker for the user's current position. Adds a + * circle which uses the accuracy * 2, to draw a circle + * which represents the user's position with an accuracy + * of 95%. + */ + public void addCurrentLocationMarker(MapboxMap mapboxMap) { + MarkerOptions currentLocationMarker = new MarkerOptions() + .position(new LatLng(curLatLng.getLatitude(), curLatLng.getLongitude())); + mapboxMap.addMarker(currentLocationMarker); + + List circle = createCircleArray(curLatLng.getLatitude(), curLatLng.getLongitude(), + curLatLng.getAccuracy() * 2, 100); + + mapboxMap.addPolygon( + new PolygonOptions() + .addAll(circle) + .strokeColor(Color.parseColor("#55000000")) + .fillColor(Color.parseColor("#11000000")) + ); + } + + /** + * Creates a series of points that create a circle on the map. + * Takes the center latitude, center longitude of the circle, + * the radius in meter and the number of nodes of the circle. + * + * @return List List of LatLng points of the circle. + */ + public List createCircleArray( + double centerLat, double centerLong, float radius, int nodes) { + List circle = new ArrayList<>(); + float radiusKilometer = radius / 1000; + double radiusLong = radiusKilometer + / (111.320 * Math.cos(centerLat * Math.PI / 180)); + double radiusLat = radiusKilometer / 110.574; + + for (int i = 0; i < nodes; i++) { + double theta = ((double) i / (double) nodes) * (2 * Math.PI); + double nodeLongitude = centerLong + radiusLong * Math.cos(theta); + double nodeLatitude = centerLat + radiusLat * Math.sin(theta); + circle.add(new LatLng(nodeLatitude, nodeLongitude)); + } + + return circle; + } + @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyPlaces.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyPlaces.java index 365d3e06f..94f0ac005 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyPlaces.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyPlaces.java @@ -74,8 +74,8 @@ public class NearbyPlaces { String query = wikidataQuery .replace("${RAD}", String.format(Locale.ROOT, "%.2f", radius)) - .replace("${LAT}", String.format(Locale.ROOT, "%.4f", cur.latitude)) - .replace("${LONG}", String.format(Locale.ROOT, "%.4f", cur.longitude)) + .replace("${LAT}", String.format(Locale.ROOT, "%.4f", cur.getLatitude())) + .replace("${LONG}", String.format(Locale.ROOT, "%.4f", cur.getLongitude())) .replace("${LANG}", lang); Timber.v("# Wikidata query: \n" + query); @@ -125,7 +125,7 @@ public class NearbyPlaces { type, // list type, // details Uri.parse(icon), - new LatLng(latitude, longitude), + new LatLng(latitude, longitude, 0), new Sitelinks.Builder() .setWikipediaLink(wikipediaSitelink) .setCommonsLink(commonsSitelink) @@ -187,7 +187,7 @@ public class NearbyPlaces { type, // list type, // details null, - new LatLng(latitude, longitude), + new LatLng(latitude, longitude, 0), new Sitelinks.Builder().build() )); } diff --git a/app/src/main/java/fr/free/nrw/commons/settings/SettingsActivity.java b/app/src/main/java/fr/free/nrw/commons/settings/SettingsActivity.java index 73c97528b..42b222cab 100644 --- a/app/src/main/java/fr/free/nrw/commons/settings/SettingsActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/settings/SettingsActivity.java @@ -12,8 +12,6 @@ import fr.free.nrw.commons.R; import fr.free.nrw.commons.theme.NavigationBaseActivity; public class SettingsActivity extends NavigationBaseActivity { - private SettingsFragment settingsFragment; - private AppCompatDelegate settingsDelegate; @Override @@ -25,8 +23,6 @@ public class SettingsActivity extends NavigationBaseActivity { setTheme(R.style.LightAppTheme); } - settingsFragment = (SettingsFragment) getFragmentManager().findFragmentById(R.id.settingsFragment); - super.onCreate(savedInstanceState); setContentView(R.layout.activity_settings); diff --git a/app/src/main/java/fr/free/nrw/commons/ui/widget/HtmlTextView.java b/app/src/main/java/fr/free/nrw/commons/ui/widget/HtmlTextView.java index bc5fd71cd..e0924505c 100644 --- a/app/src/main/java/fr/free/nrw/commons/ui/widget/HtmlTextView.java +++ b/app/src/main/java/fr/free/nrw/commons/ui/widget/HtmlTextView.java @@ -2,10 +2,11 @@ package fr.free.nrw.commons.ui.widget; import android.content.Context; import android.support.v7.widget.AppCompatTextView; -import android.text.Html; import android.text.method.LinkMovementMethod; import android.util.AttributeSet; +import fr.free.nrw.commons.Utils; + /** * An {@link AppCompatTextView} which formats the text to HTML displayable text and makes any * links clickable. @@ -16,6 +17,6 @@ public class HtmlTextView extends AppCompatTextView { super(context, attrs); setMovementMethod(LinkMovementMethod.getInstance()); - setText(Html.fromHtml(getText().toString())); + setText(Utils.fromHtml(getText().toString())); } } diff --git a/app/src/main/java/fr/free/nrw/commons/upload/MultipleUploadListFragment.java b/app/src/main/java/fr/free/nrw/commons/upload/MultipleUploadListFragment.java index b6ca9e598..9476d55e1 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/MultipleUploadListFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/MultipleUploadListFragment.java @@ -42,6 +42,7 @@ public class MultipleUploadListFragment extends Fragment { private GridView photosGrid; private PhotoDisplayAdapter photosAdapter; private EditText baseTitle; + private TitleTextWatcher textWatcher = new TitleTextWatcher(); private Point photoSize; private MediaDetailPagerFragment.MediaDetailProvider detailProvider; @@ -97,7 +98,6 @@ public class MultipleUploadListFragment extends Fragment { holder = (UploadHolderView)view.getTag(); } - Contribution up = (Contribution)this.getItem(i); if(holder.imageUri == null || !holder.imageUri.equals(up.getLocalUri())) { @@ -170,37 +170,16 @@ public class MultipleUploadListFragment extends Fragment { photoSize = calculatePicDimension(detailProvider.getTotalMediaCount()); photosGrid.setColumnWidth(photoSize.x); - baseTitle.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence charSequence, int i1, int i2, int i3) { - - } - - @Override - public void onTextChanged(CharSequence charSequence, int i1, int i2, int i3) { - for(int i = 0; i < detailProvider.getTotalMediaCount(); i++) { - Contribution up = (Contribution) detailProvider.getMediaAtPosition(i); - Boolean isDirty = (Boolean)up.getTag("isDirty"); - if(isDirty == null || !isDirty) { - if(!TextUtils.isEmpty(charSequence)) { - up.setFilename(charSequence.toString() + " - " + ((Integer)up.getTag("sequence") + 1)); - } else { - up.setFilename(""); - } - } - } - detailProvider.notifyDatasetChanged(); - } - - @Override - public void afterTextChanged(Editable editable) { - - } - }); - + baseTitle.addTextChangedListener(textWatcher); return view; } + @Override + public void onDestroyView() { + baseTitle.removeTextChangedListener(textWatcher); + super.onDestroyView(); + } + @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); @@ -227,5 +206,30 @@ public class MultipleUploadListFragment extends Fragment { setHasOptionsMenu(true); } - + + private class TitleTextWatcher implements TextWatcher { + @Override + public void beforeTextChanged(CharSequence charSequence, int i1, int i2, int i3) { + } + + @Override + public void onTextChanged(CharSequence charSequence, int i1, int i2, int i3) { + for(int i = 0; i < detailProvider.getTotalMediaCount(); i++) { + Contribution up = (Contribution) detailProvider.getMediaAtPosition(i); + Boolean isDirty = (Boolean)up.getTag("isDirty"); + if(isDirty == null || !isDirty) { + if(!TextUtils.isEmpty(charSequence)) { + up.setFilename(charSequence.toString() + " - " + ((Integer)up.getTag("sequence") + 1)); + } else { + up.setFilename(""); + } + } + } + detailProvider.notifyDatasetChanged(); + } + + @Override + public void afterTextChanged(Editable editable) { + } + } } diff --git a/app/src/main/java/fr/free/nrw/commons/upload/SingleUploadFragment.java b/app/src/main/java/fr/free/nrw/commons/upload/SingleUploadFragment.java index 2cf23318c..08e2f6050 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/SingleUploadFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/SingleUploadFragment.java @@ -1,6 +1,5 @@ package fr.free.nrw.commons.upload; -import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; @@ -53,6 +52,7 @@ public class SingleUploadFragment extends Fragment { @BindView(R.id.licenseSpinner) Spinner licenseSpinner; private OnUploadActionInitiated uploadActionInitiatedHandler; + private TitleTextWatcher textWatcher = new TitleTextWatcher(); @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { @@ -125,28 +125,19 @@ public class SingleUploadFragment extends Fragment { Timber.d("Position: %d %s", position, getString(Utils.licenseNameFor(license))); licenseSpinner.setSelection(position); - TextWatcher uploadEnabler = new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) { } - - @Override - public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {} - - @Override - public void afterTextChanged(Editable editable) { - if(getActivity() != null) { - getActivity().invalidateOptionsMenu(); - } - } - }; - - titleEdit.addTextChangedListener(uploadEnabler); + titleEdit.addTextChangedListener(textWatcher); setLicenseSummary(license); return rootView; } + @Override + public void onDestroyView() { + titleEdit.removeTextChangedListener(textWatcher); + super.onDestroyView(); + } + @OnItemSelected(R.id.licenseSpinner) void onLicenseSelected(AdapterView parent, View view, int position, long id) { String licenseName = parent.getItemAtPosition(position).toString(); @@ -206,9 +197,10 @@ public class SingleUploadFragment extends Fragment { } @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - uploadActionInitiatedHandler = (OnUploadActionInitiated) activity; + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + setHasOptionsMenu(true); + uploadActionInitiatedHandler = (OnUploadActionInitiated) getActivity(); } @Override @@ -223,9 +215,18 @@ public class SingleUploadFragment extends Fragment { } } - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setHasOptionsMenu(true); + private class TitleTextWatcher implements TextWatcher { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) { } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) { } + + @Override + public void afterTextChanged(Editable editable) { + if(getActivity() != null) { + getActivity().invalidateOptionsMenu(); + } + } } } diff --git a/app/src/main/java/fr/free/nrw/commons/utils/ExecutorUtils.java b/app/src/main/java/fr/free/nrw/commons/utils/ExecutorUtils.java new file mode 100644 index 000000000..2cb93a058 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/utils/ExecutorUtils.java @@ -0,0 +1,23 @@ +package fr.free.nrw.commons.utils; + +import android.os.Handler; +import android.os.Looper; + +import java.util.concurrent.Executor; + +public class ExecutorUtils { + + private static final Executor uiExecutor = new Executor() { + @Override + public void execute(Runnable command) { + if (Looper.myLooper() == Looper.getMainLooper()) { + command.run(); + } else { + new Handler(Looper.getMainLooper()).post(command); + } + } + }; + + public static Executor uiExecutor () { return uiExecutor;} + +} diff --git a/app/src/main/java/fr/free/nrw/commons/utils/LengthUtils.java b/app/src/main/java/fr/free/nrw/commons/utils/LengthUtils.java index 3a8cc6aa5..c9c1fefc2 100644 --- a/app/src/main/java/fr/free/nrw/commons/utils/LengthUtils.java +++ b/app/src/main/java/fr/free/nrw/commons/utils/LengthUtils.java @@ -38,10 +38,10 @@ public class LengthUtils { } private static double computeAngleBetween(LatLng from, LatLng to) { - return distanceRadians(Math.toRadians(from.latitude), - Math.toRadians(from.longitude), - Math.toRadians(to.latitude), - Math.toRadians(to.longitude)); + return distanceRadians(Math.toRadians(from.getLatitude()), + Math.toRadians(from.getLongitude()), + Math.toRadians(to.getLatitude()), + Math.toRadians(to.getLongitude())); } private static double distanceRadians(double lat1, double lng1, double lat2, double lng2) { diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index e60cf84e4..0e8871391 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -10,6 +10,7 @@ الرجاء الانتظار… تم الدخول بشكل صحيح فشل تسجيل الدخول + الملف غير موجود. فضلا اختر ملفا آخر. فشل الاستيقان! بدأ الرفع! رُفع %1$s! @@ -20,7 +21,7 @@ فشل رفع %1$s انقر لتشاهد متبقى %d - مرفوعاتي + مرفوعاتي الأخيرة في قائمة الانتظار فشل انتهاء %1$d%% @@ -76,7 +77,7 @@ هل تظن أنك فهمت؟ نعم! تصنيفات - جارٍ التحميل… + جارٍ التحميل… لا شيء محدد لا وصف ترخيص غير معلوم diff --git a/app/src/main/res/values-ast/strings.xml b/app/src/main/res/values-ast/strings.xml index 16ff5f04a..b49144c15 100644 --- a/app/src/main/res/values-ast/strings.xml +++ b/app/src/main/res/values-ast/strings.xml @@ -7,7 +7,7 @@ Identificase Date d\'alta Aniciando sesión - Aguarde, por favor… + Espera… ¡Identificación correuta! ¡Falló l\'aniciu de sesión! Nun s\'alcontró\'l ficheru. Tenta con otru. @@ -20,11 +20,11 @@ Acabando de xubir %1$s Falló la xuba de %1$s Toque pa velo - - Xubiendo 1 ficheru + + Xubiendo un ficheru Xubiendo %d ficheros - Les mios xubes + Les mios xubes recién En cola Falló %1$d%% completao @@ -42,6 +42,7 @@ Nun se pudo aniciar sesión – por favor compruebe la so contraseña Demasiaos intentos incorreutos. Téntalo otra vuelta n\'unos minutos. Sentímoslo, esti usuariu ta bloquiáu en Commons + Tienes de dar el códigu d\'identificación de dos factores. Falló l\'aniciu de sesión Xubir Da-y un nome al conxuntu @@ -50,16 +51,19 @@ Guetar categoríes Guardar Refrescar - - Inda nun hai cargues + El GPS ta desactiváu nel preséu. ¿Quiés activalu? + Activar GPS + Inda nun hai xubes + + \@string/contributions_subtitle_zero 1 carga %d cargues - + Principiando 1 carga Principiando %d cargues - + 1 carga %d cargues @@ -74,7 +78,7 @@ Software de códigu abiertu lliberáu baxo la <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">Llicencia Apache v2</a>. Wikimedia Commons ya\'l so logotipu son marques rexistraes de la Fundación Wikimedia y utilícense col so permisu. Nun tamos acreditaos pola Fundación Wikimedia nin tamos afiliaos con ella. El <a href=\"https://github.com/commons-app/apps-android-commons\">códigu fonte</a> ya\'l <a href=\"https://commons-app.github.io/\">sitiu web</a> tán en GitHub. Crea una nueva <a href=\"https://github.com/commons-app/apps-android-commons/issues\">incidencia en GitHub</a> pa informar de problemes y suxerencies. Wikimedia:Commons-android-strings-about privacy policy/ast - <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">CRÉDITOS</a> + <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">Créditos</a> Tocante a Unviar comentarios (per corréu) Nun s\'instaló nengún veceru de corréu @@ -126,7 +130,7 @@ ¿Cree que yá sabe cómo va? ¡Si! Categoríes - Cargando… + Cargando… Nenguna seleicionada Ensin descripción Llicencia desconocida @@ -135,6 +139,7 @@ Permisu opcional: llograr l\'allugamientu actual pa suxerir categoríes Aceutar Llugares cercanos + Nun s\'alcontraron llugares cercanos Avisu Esti ficheru yá esiste\'n Commons. ¿Confirmes que quies siguir? @@ -143,8 +148,42 @@ Títulu del mediu Descripción Equí va la descripción del mediu. Posiblemente pué ser llargo enforma, y necesitará espardese per delles llinies. Sicasí, esperamos que se vea guapo. + Data d\'unviu Faete probador beta Escueye\'l nuesu canal beta en Google Play y ten accesu tempranu a carauterístiques nueves ya igües de fallos Usar Wikidata (Avisu: desactivar esto pue causar un gran consumu de datos nel móvil) + Códigu I2F + Mio llende de xubes recién + Llende máxima + Nun pueden vese más de 500 + Configurar la llende de xubíes recién + Anguaño nun hai encontu pa autenticación de dos factores. + ¿Confirmes que quies salir? + Logo de Commons + Imaxe de fondu + Falló la imaxe multimedia + Nun s\'atopó nenguna imaxe + Xubir imaxe + Monte Zao + Llames + Ponte del Arcu la Vieya + Tulipa + Non selfies + Imaxe privativa + Bienvenida a Wikipedia + Bienvenida a Derechos d\'autor + Ópera de Sidney + Encaboxar + Abrir + Zarrar + Entamu + Xubir + Cercanu + Tocante a + Configuración + La to opinión + Salir + Artículu en Commons + Artículu en WikiData diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index 68d39875b..5d3a16b8b 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -10,7 +10,7 @@ Изчакайте… Успешно вписване. Неуспешно вписване! - Файлът не е намерен. Моля, опитайте друг файл. + Файлът не е намерен. Моля, опитайте с друг файл. Неуспешен опит за удостоверяване! Качването започна! Файл %1$s е качен! @@ -24,9 +24,9 @@ Преглед в браузъра Заглавие Описание - Неуспешно влизане - проблем в мрежата - Неуспешно влизане - моля проверете потребителското си име - Неуспешно влизане - моля проверете паролата си + Неуспешно влизане – проблем в мрежата + Неуспешно влизане – моля проверете потребителското си име + Неуспешно влизане – моля проверете паролата си Качване Изменения Качване @@ -65,7 +65,7 @@ Моля, НЕ качвайте: Да! Категории - Зареждане… + Зареждане… Няма описание Непознат лиценз Обновяване @@ -76,4 +76,17 @@ Не Заглавие Описание + Дата на качване + Лами + Лале + Отказ + Отваряне + Затваряне + Начало + Качване + Наблизо + За приложението + Настройки + Обратна връзка + Излизане diff --git a/app/src/main/res/values-bn/strings.xml b/app/src/main/res/values-bn/strings.xml index 069fc299b..15599fd11 100644 --- a/app/src/main/res/values-bn/strings.xml +++ b/app/src/main/res/values-bn/strings.xml @@ -128,7 +128,7 @@ আপনি মনে করেন আপনি বুঝতে পেরেছেন? হ্যাঁ! বিষয়শ্রেণীসমূহ - লোড হচ্ছে… + লোড হচ্ছে… কিছুই নির্বাচন করা হয়নি বিবরণ নেই অজানা লাইসেন্স diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 33d98722f..8a99630ee 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -130,7 +130,7 @@ Myslíte, že rozumíte? Ano! Kategorie - Načítá se… + Načítá se… Nevybrány Bez popisu Neznámá licence @@ -160,6 +160,19 @@ Počet nahraných souborů, které se zobrazí Tato aplikace zatím nepodporuje tzv. dvoufázové ověření. Opravdu se chcete odhlásit? + Logo Wikimedia Commons + Obrázek na pozadí + Nebyl nalezen žádný obrázek + Nahrát obrázek + Hora Zao + Lamy + Duhový most + Tulipán + Žádná selfíčka + Autorskoprávně chráněný obrázek + Vítejte na Wikipedii + Úvod k autorským právům + Opera v Sydney Zrušit Otevřít Zavřít @@ -170,4 +183,6 @@ Nastavení Zpětná vazba Odhlášení + Článek na Commons + Článek na Wikidatech diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index c7dad4dfb..e3c0dd77c 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -130,7 +130,7 @@ Verstanden? Ja! Kategorien - Lade … + Lade … Keine ausgewählt Keine Beschreibung Unbekannte Lizenz diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index f03af818e..d70e9e66a 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -7,7 +7,7 @@ Acceder Regístrate Accediendo - Espera un momento… + Espera un momento… ¡Inicio de sesión exitoso! ¡Inicio de sesión fallido! No se encontró el archivo. Prueba con otro. @@ -130,7 +130,7 @@ ¿Crees que ya lo tienes? ¡Sí! Categorías - Cargando… + Cargando… No hay nada seleccionado Sin descripción Licencia desconocida @@ -160,6 +160,20 @@ Establecer el límite de subidas recientes Actualmente no se admite la auntenticación de dos factores. ¿Confirmas que quieres salir? + Logo de Commons + Imagen de fondo + Falló la imagen de multimedia + No se encontró imagen + Subir imagen + Monte Zao + Llamas + Puente del Arco Iris + Tulipa + No selfies + Imagen propietaria + Wikipedia de bienvenida + Derechos de autor de bienvenida + Ópera de Sídney Cancelar Abrir Cerrar @@ -170,4 +184,6 @@ Configuración Comentarios Salir + Artículo en Commons + Artículo en WikiData diff --git a/app/src/main/res/values-fr/error.xml b/app/src/main/res/values-fr/error.xml index 79ac065fe..2cd1e09f3 100644 --- a/app/src/main/res/values-fr/error.xml +++ b/app/src/main/res/values-fr/error.xml @@ -1,7 +1,7 @@ - Commons s’est planté - Oups! Quelque chose s’est mal passé! - Dites-nous ce que vous faisiez, puis partagez-le par courriel avec nous. Cela nous aidera à le corriger! - Merci! + Commons a planté + Oups ! Quelque chose s’est mal passé ! + Dites-nous ce que vous faisiez, puis partagez-le par courriel avec nous. Cela nous aidera à le corriger ! + Merci ! diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 2aaf6f3ee..f86b5e6cb 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -7,12 +7,12 @@ Se connecter S’inscrire Connexion - Veuillez patienter… + Veuillez patienter… Connexion réussie ! Échec de la connexion ! Fichier non trouvé. Veuillez en essayer un autre. Échec de l’authentification ! - Téléchargement démarré ! + Téléversement démarré ! %1$s téléversés ! Appuyer pour voir votre téléversement Début de téléversement %1$s @@ -83,7 +83,7 @@ Envoyer vos commentaires (par courriel) Aucun client de courriel installé Catégories utilisées récemment - Attente de première synchronisation… + Attente de première synchronisation… Vous n’avez pas encore téléchargé de photo. Réessayer Annuler @@ -117,11 +117,11 @@ CC Zéro Wikimédia Communs héberge la plupart des images qui sont utilisées dans Wikipédia Vos images aident à éduquer les gens dans le monde entier ! - Veuillez télécharger les images qui sont prises ou créées entièrement par vous : + Veuillez téléverser des images qui sont prises ou créées entièrement par vous : - Objets naturels (fleurs, animaux, montagnes) \n- Objets utiles (bicyclettes, gares ferroviaires) \n- Personnes célèbres (votre maire, les athlètes olympiques que vous avez rencontrés) - Veuillez ne PAS télécharger : + Veuillez ne PAS téléverser : - des selfies ou des images de vos amis \n- des images téléchargées sur Internet\n- des copies d’écran d’applications propriétaires - Exemple de téléchargement : + Exemple de téléversement : - Titre : L’opéra de Sydney\n- Description : L’opéra de Sydney vu à travers la baie\n- Catégories : Opéra de Sydney, Opéra de Sydney depuis l’ouest, vues à distance de l’Opéra de Sydney Contribuez avec vos images. Aidez les articles de Wikipédia à prendre vie ! Les images sur Wikipédia viennent de Wikimedia Commons. @@ -130,7 +130,7 @@ Vous pensez que c’est bon ? Oui ! Catégories - Chargement… + Chargement… Aucune catégorie sélectionnée Aucune description Licence inconnue @@ -148,7 +148,7 @@ Titre du média Description La description du média vient ici. Cela peut être potentiellement assez long, et devra être réparti sur plusieurs lignes. Nous espérons que cela restera joli néanmoins. - Date de téléchargement + Date de téléversement Devenir un bêta-testeur S’inscrire à notre canal bêta sur Google Play et obtenir un accès anticipé aux nouvelles fonctionnalités et corrections de bogue Utiliser Wikidata @@ -158,8 +158,22 @@ Limite maximale Impossible d’afficher plus de 500 Fixer la limite de téléversement récent - L’authentification à deux facteurs n’est pas supportée pour le moment. + L’authentification à deux facteurs n’est pas prise en charge pour le moment. Voulez-vous vraiment vous déconnecter ? + Logo de Communs + Image de fond + Échec de l’image du média + Aucune image trouvée + Téléverser une image + Mont Zao + Lamas + Pont Arc-en-ciel + Tulipe + Pas d’autoportrait + Image privée + Bienvenue de Wikipédia + Droit d’auteur de bienvenue + Maison de l’opéra de Sydney Annuler Ouvrir Fermer @@ -170,4 +184,6 @@ Paramètres Commentaire Déconnexion + Article de Communs + Article de WikiData diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 792274625..ee25ce0db 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -97,7 +97,7 @@ Attribuzione-Condividi allo stesso modo 4.0 Attribuzione 4.0 Attribuzione-Condividi allo stesso modo 3.0 - CC Attribuzione 3.0 + Attribuzione 3.0 CC0 CC BY-SA 3.0 CC BY-SA 3.0 (Austria) @@ -124,7 +124,7 @@ Pensi di aver capito? Sì! Categorie - Caricamento… + Caricamento… Nessuna selezione Nessuna descrizione Licenza sconosciuta @@ -146,6 +146,12 @@ Non è possibile mostrarne più di 500 L\'autenticazione a due fattori non è attualmente supportata. Vuoi veramente uscire? + Nessuna immagine trovata + Carica immagine + Arcobaleno + Tulipano + No autoscatti (selfie) + Teatro dell\'opera di Sydney Annulla Apri Chiudi diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml index 961b45ef3..65b3c107c 100644 --- a/app/src/main/res/values-iw/strings.xml +++ b/app/src/main/res/values-iw/strings.xml @@ -54,7 +54,7 @@ ה־GPS במכשיר שלך אינו מופעל. האם להפעיל אותו? הפעלת GPS לא הועלה עדיין שום דבר - + העלאה אחת %d העלאות @@ -129,7 +129,7 @@ נראה לך שקלטת? כן! קטגוריות - טעינה… + טעינה... לא נבחר דבר אין תיאור רישיון לא ידוע @@ -159,6 +159,17 @@ הגדרת מגבלת העלאות אחרונות אימות דו־שלבי אינו נתמך כעת. האם באמת לצאת מהחשבון? + סמל ויקישיתוף + תמונת רקע + לא נמצאה תמונה + העלאת תמונה + הר זאו + לאמות + גשר הקשת בענן + צבעוני + בלי תמונות סלפי + תמונה קניינית + בית האופרה של סידני ביטול פתיחה סגירה diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 383402e49..38b0a39ca 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -7,7 +7,7 @@ ログイン 利用者登録 ログイン中 - お待ちください… + お待ちください… ログインしました! ログインに失敗しました! ファイルが見つかりません。別のファイルでお試しください。 diff --git a/app/src/main/res/values-jv/strings.xml b/app/src/main/res/values-jv/strings.xml index a4967572f..6c23773f8 100644 --- a/app/src/main/res/values-jv/strings.xml +++ b/app/src/main/res/values-jv/strings.xml @@ -33,7 +33,7 @@ Cedhak kéné Unggahanku Dum - Delok ing Pangluru + Deleng ing Pangluru Sesirah Wedharan Ora bisa mlebu log - jaringané gagal @@ -41,6 +41,7 @@ Ora bisa mlebu log - tiliki tembung wadiné panjenengan Kakèhan upaya sing gagal. Jajalana manèh mengko. Ngapunten, panganggo iki wis diblokir ing Commons + Panjenengan kudu ngisi kodhe otèntifikasi rong faktoré panjenengan Wurung mlebu log Unggah Jenengi sèt iki @@ -49,6 +50,9 @@ Golèk kategori Simpen Anyarana + GPS dipatèni ing pirantiné panjenengan. Panjenengan arep ngurubaké? + Urubaké GPS + Durung ana unggahan \@string/contributions_subtitle_zero %d unggahan @@ -104,6 +108,8 @@ CC BY-SA 3.0 CC BY 4.0 CC Nul + Tulung AJA ngunggah: + Contoné unggahan: Panjenengan sumerep, ta? Iya! Kategori @@ -134,14 +140,28 @@ Setèl Watesan Unggahan Anyar Otèntifikasi rong faktor saiki durung bisa. Panjenengan yakin arep metu log? + Logo Commons + Gambar Latar Wuri + Gambar Médhia Wurung + Gambar Ora Katemu + Unggah Gambar + Gunung Zao + Llama + Kreteg Kluwung + Tulip + Ora Ana Swafoto + Sugeng Rawuh ing Wikipédia + Wisma Opera Sydney Wurung Bukak Tutup Tepas Unggah Cedhak kéné - Bab + Ngenani Setèlan Pamrayoga Metu log + Artikel Commons + Artikel WikiData diff --git a/app/src/main/res/values-kn/strings.xml b/app/src/main/res/values-kn/strings.xml index f25f1d830..643ca04e2 100644 --- a/app/src/main/res/values-kn/strings.xml +++ b/app/src/main/res/values-kn/strings.xml @@ -1,39 +1,59 @@ - ವಿಕಿಮೀಡಿಯ ಕಾಮನ್ಸ್ + ಕಾಮನ್ಸ್ ವ್ಯವಸ್ಥೆಗಳು ಬಳಕೆದಾರ ಹೆಸರು ಪ್ರವೇಶಪದ ಲಾಗ್ ಇನ್ + ಸೈನ್ ಅಪ್ ಲಾಗಿನ್ ಆಗುತ್ತಿದ್ದೀರಿ ದಯವಿಟ್ಟು ನಿರೀಕ್ಷಿಸಿ… ಲಾಗಿನ್ ಯಶಸ್ವಿ! ಲಾಗಿನ್ ಸಾಧ್ಯವಾಗಿಲ್ಲ! + ಈ ಕಡತ ಕಂಡುಬಂದಿಲ್ಲ. ದಯವಿಟ್ಟು ಇನ್ನೊಂದು ಕಡತ ಪ್ರಯತ್ನಿಸಿ. ದೃಢೀಕರಣ ವಿಫಲ! ಅಪ್ಲೋಡ್ ಪ್ರಾರಂಭವಾಗಿದೆ! %1$s ಅಪ್ಲೋಡ್ ಆಗಿದೆ! ನಿಮ್ಮ ನಕಲೇರಿಕೆಯನ್ನು ನೋಡಲು ಮೆಲ್ಲಗೆ ತಟ್ಟಿ ನಕಲೇರಿಕೆಯ%1$s ಪ್ರಾರಂಭ %1$s ನಕಲೇರಿಕೆ ಆಗಿದೆ! + %1$s ಅಪ್ಲೋಡ್ ಮಾಡುವಿಕೆಯನ್ನು ಪೂರ್ಣಗೊಳಿಸಲಾಗುತ್ತಿದೆ + %1$s ಅಪ್ಲೋಡ್ ಮಾಡುವಿಕೆ ವಿಫಲವಾಗಿದೆ ನೋಡಲು ಮೆಲ್ಲಗೆ ತಟ್ಟಿ - ನನ್ನ ನಕಲೇರಿಕೆಗಳು + + %d file uploading\n%d ಕಡತ ಅಪ್ಲೋಡ್ ಅಗುತ್ತಿದೆ + %d files uploading\n%d ಕಡತಗಳು ಅಪ್ಲೋಡ್ ಅಗುತ್ತಿದೆ + + ನನ್ನ ಇತ್ತಿಚಿನ ಅಪ್ಲೋಡ್ಗಳು ಸರತಿಸಾಲಿನಲ್ಲಿದೆ ವಿಫಲವಾಗಿದೆ %1$d%% ಸಂಪೂರ್ಣ ನಕಲೇರಿಸಲಾಗುತ್ತಿದೆ ಛಾಯಾಂಕಣದಿಂದ ಚಿತ್ರ ತೆಗೆಯಿರಿ + ಹತ್ತಿರದ ನನ್ನ ನಕಲೇರಿಕೆಗಳು ಹಂಚು ಬ್ರೌಸರ್‌ನಲ್ಲಿ ತೆರೆ ಶೀರ್ಷಿಕೆ ವಿವರ + ಲಾಗಿನ್ ಮಾಡಲಾಗಲಿಲ್ಲ - ನೆಟ್ವರ್ಕ್ ವೈಫಲ್ಯ + ಲಾಗಿನ್ ಮಾಡಲಾಗಲಿಲ್ಲ - ದಯವಿಟ್ಟು ನಿಮ್ಮ ಬಳಕೆದಾರ ಹೆಸರು ಪರಿಶೀಲಿಸಿ + ಲಾಗಿನ್ ಮಾಡಲಾಗಲಿಲ್ಲ - ದಯವಿಟ್ಟು ನಿಮ್ಮ ಸ೦ಕೇತದ ಪದ ಪರಿಶೀಲಿಸಿ + ಹಲವಾರು ವಿಫಲ ಪ್ರಯತ್ನಗಳು. ದಯವಿಟ್ಟು ಕೆಲವು ನಿಮಿಷಗಳಲ್ಲಿ ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ. + ಕ್ಷಮಿಸಿ, ಈ ಬಳಕೆದಾರರನ್ನು ಕಾಮನ್ಸ್ನಲ್ಲಿ ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ + ನಿಮ್ಮ ಎರಡು ಅಂಶ ದೃಢೀಕರಣ ಕೋಡ್ ಅನ್ನು ನೀವು ಒದಗಿಸಬೇಕು. ಲಾಗಿನ್ ಸಾಧ್ಯವಾಗಿಲ್ಲ ಅಪ್ಲೋಡ್ + ಈ ಸೆಟ್ ಹೆಸರಿಸಿ ಬದಲಾವಣೆಗಳು ಅಪ್ಲೋಡ್ ವರ್ಗಗಳಲ್ಲಿ ಹುಡುಕು ಉಳಿಸಿ + ಪುನಶ್ಚೇತನಗೊಳಿಸು + ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿ ಜಿಪಿಎಸ್ ಅನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ. ನೀವು ಅದನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಲು ಬಯಸುವಿರಾ? + ಜಿಪಿಎಸ್ ಸಕ್ರಿಯಗೊಳಿಸು + ಇನ್ನೂ ಯಾವುದೆ ಅಪ್ಲೋಡ್ಗಳಿಲ್ಲ ವರ್ಗಗಳು ಬಳಕೆಯ ವರದಿಗಳು ವ್ಯವಸ್ಥೆಗಳು diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index fecdcf0f3..e41fe088b 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -20,9 +20,9 @@ %1$s 파일 올리기를 끝내는 중 %1$s 파일 올리기 실패 보려면 탭하세요 - - 1개 파일 올리는 중 - 파일 %d개 올리는 중 + + %d개의 파일을 올리는 중 + %d개의 파일을 올리는 중 내 최근 업로드 대기 중 @@ -59,9 +59,9 @@ 1개 업로드 %d개 업로드 - - 한 장 업로드 시작 - %d 장 업로드 시작 + + %d장의 업로드를 시작합니다 + %d장의 업로드를 시작합니다 %d개 업로드 @@ -124,7 +124,7 @@ 이해하셨습니까? 예! 분류 - 불러오는 중… + 불러오는 중… 선택하지 않음 설명 없음 알 수 없는 라이선스 @@ -152,6 +152,19 @@ 최근 업로드 제한 설정 2요소 인증은 현재 지원하지 않습니다. 정말 로그아웃하시겠습니까? + 공용 로고 + 배경 그림 + 미디어 그림 실패 + 그림이 없습니다 + 그림 올리기 + 자오 연봉 + 라마 + 레인보우 브리지 + 튤립 + 사유 그림 + 환영 위키백과 + 환영 저작권 + 시드니 오페라 하우스 취소 열기 닫기 @@ -162,4 +175,6 @@ 설정 피드백 로그아웃 + 공용 문서 + 위키데이터 문서 diff --git a/app/src/main/res/values-lb/strings.xml b/app/src/main/res/values-lb/strings.xml index 25d740718..05c686619 100644 --- a/app/src/main/res/values-lb/strings.xml +++ b/app/src/main/res/values-lb/strings.xml @@ -7,7 +7,7 @@ Aloggen Schreift Iech an Aloggen - Waart w.e.g. … + Waart w.e.g. … Umeldung huet geklappt! D\'Aloggen huet net funktionéiert Fichier net fonnt. probéiert w.e.g. en anere Fichier. @@ -50,6 +50,7 @@ Kategorie sichen Späicheren Aktualiséieren + GPS ass op Ärem Apparat ausgeschalt. Wëllt Dir en aktivéieren? GPS aktivéieren Nach keng eropgeluede Fichieren @@ -154,6 +155,20 @@ 2FA-Code Maximal Limite Et kënnen der net méi wéi 500 gewise ginn + Wëllt dir Iech wierklech ausloggen? + Commons-Logo + Hannergrondbild + Kee Bild fonnt + Bild eroplueden + Bierg Zao + Lamaen + Rainbow Bridge + Tulp + Keng Selfien + Geschützt Bild + Wëllkomm Wikipedia + Wëllkomm Copyright + Oper vu Sydney Ofbriechen Opmaachen Zoumaachen @@ -164,4 +179,6 @@ Astellungen Feedback Ausloggen + Commons-Artikel + Wikidata-Artikel diff --git a/app/src/main/res/values-mk/strings.xml b/app/src/main/res/values-mk/strings.xml index 427ce3e46..1fbca9cbb 100644 --- a/app/src/main/res/values-mk/strings.xml +++ b/app/src/main/res/values-mk/strings.xml @@ -91,7 +91,7 @@ Преземи Лиценца Користи претходен наслов/опис - Автоматски давај ја тековната местоположба + Автоматски давај тековна местоположба Добивање на тековната местоположба за да се дадат предлози за категории, доколку сликата нема геоознаки Ноќен режим Користи темен изглед diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 7c137688b..ee04c11fa 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -50,6 +50,7 @@ Categorieën zoeken Opslaan Vernieuwen + Nog geen uploads Nog geen uploads 1 upload @@ -144,6 +145,7 @@ Uploaddatum Wikidata gebruiken 2FA-code + Geen selfies Annuleren Openen Sluiten diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index fa74253cc..12645e4b7 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -50,6 +50,8 @@ Szukaj kategorii Zapisz Odśwież + GPS w twoim urządzeniu jest wyłączony. Czy chcesz go włączyć? + Włącz GPS Przesłano @string/contributions_subtitle_zero Przesłano %d plik @@ -74,7 +76,7 @@ Oprogramowanie Open Source, wydane na licencji <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">Apache License v2</a>. Wikimedia Commons i jego logo są znakami towarowymi Wikimedia Foundation i są wykorzystywane za zgodą Wikimedia Foundation. Nie jesteśmy powiązani z Wikimedia Foundation. <a href=\"https://github.com/commons-app/apps-android-commons\">Kod źródłowy</a> oraz <a href=\"https://commons-app.github.io/\">strona internetowa</a> na GitHub. Aby zgłosić błąd lub sugestię, utwórz nowe <a href=\"https://github.com/commons-app/apps-android-commons/issues\">zgłoszenie na GitHub</a>. <a href=\"https://wikimediafoundation.org/wiki/Privacy_policy\">Polityka prywatności</a> - <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">Twórcy</a> + <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">Twórcy</a> O aplikacji Wyślij opinię (przez e-mail) Klient e-mail nie jest zainstalowany @@ -126,7 +128,7 @@ Czy wszystko jest jasne? Tak! Kategorie - Ładowanie… + Ładowanie… Nic nie zaznaczono Brak opisu Nieznana licencja @@ -146,4 +148,12 @@ Zostań beta-testerem Dołącz do kanału bety w Google Play i dostań wczesny dostęp do nowych funkcji i łatek Użyj Wikidanych + Kod 2FA + Uwierzytelnianie dwuskładnikowe obecnie nie jest obsługiwane. + Anuluj + Otwórz + Zamknij + Prześlij + Ustawienia + Wyloguj diff --git a/app/src/main/res/values-pms/strings.xml b/app/src/main/res/values-pms/strings.xml index 2d177059e..6b98156e5 100644 --- a/app/src/main/res/values-pms/strings.xml +++ b/app/src/main/res/values-pms/strings.xml @@ -53,17 +53,18 @@ Agiorné Ël GPS a l\'é disabilità su sò angign. Veul-lo ativelo? Ativé ël GPS - - Ancor gnun cariament - 1 cariament + Ancor gnun cariament + + \@string/contributions_subtitle_zero + %d cariament %d cariament - - 1 cariament ancaminà + + %d cariament ancaminà %d cariament ancaminà - - 1 cariament + + %d cariament %d cariament Gnun-e categorìe rëspondente a %1$s trovà @@ -77,7 +78,7 @@ Ij programa Open Source a son publicà sota la <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">licensa Apache v2</a>. Wikimedia Commons e sò sìmbol a son dle marche argistrà dla Fondassion Wikimedia e a son dovrà con ël përmess ëd la Fondassion Wikimedia. Nojàutri i soma nen aprovà da o afilià a la Fondassion Wikimedia. <a href=\"https://github.com/commons-app/apps-android-commons\">Sorgiss</a> e <a href=\"https://commons-app.github.io/\">sit an sl\'aragnà</a> su GitHub. Creé na neuva <a href=\"https://github.com/commons-app/apps-android-commons/issues\">signalassion GitHub</a> për signalé dij givo e dij sugeriment. <a href=\"https://wikimediafoundation.org/wiki/Privacy_policy\">Régole ëd confidensialità</a> - <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">RINGRASSIAMENT</a> + <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">Ringrassiament</a> A propòsit Mandé dij coment (për pòsta eletrònica) Gnun clien ëd pòsta eletrònica anstalà @@ -138,6 +139,7 @@ Autorisassion facoltativa: Oten-e la posission atual për dij sugeriment ëd categorìa Va bin Pòst davzin + Trovà gnente ant ij parage Avertensa S\'archivi a esist già su Comun. É-lo sigur ëd vorèj andé anans? @@ -146,8 +148,33 @@ Tìtol dël mojen Descrission La descrission dël mojen a va ambelessì. Sòn a podrìa esse potensialman longh, e a dovrà esse spantià su vàire linie. I speroma comsëssìa ch\'a ven-a grassios. + Dàita ëd cariament Dventé në sperimentador Beta Anscriv-se a nòstr canal beta su Google Play a oten-e n\'acess antissipà a le neuve fonsionalità e coression ëd givo Dovré Wikidàit (Atension: disabilité sòn a podrìa provoché un gròss consum ëd dàit con ël sacociàbil) + Còdes 2FA + Mè lìmit ëd cariament recent + Lìmit màssim + Impossìbil ësmon-e pi che 500 + Fissé la limitassion ëd cariament recent + L\'autentificassion a doi fator për ël moment a l\'é nen sostnùa. + Veul-lo për da bon seurte dal sistema? + Marca ëd Comun + Plancia dë sfond + Faliment ëd la plancia dël mojen + Gnun-a plancia trovà + Carié na plancia + Mont Zao + Lama + Pont Arcansiel + Tulipan + Gnun autoscat + Plancia privà + Bin-ëvnù ëd Wikipedia + Drit d\'autor ëd bin-ëvnù + Ca dl\'òpera ëd Sidney + Anulé + Duverté + Saré diff --git a/app/src/main/res/values-qq/strings.xml b/app/src/main/res/values-qq/strings.xml index 9cf5e752e..021205ee1 100644 --- a/app/src/main/res/values-qq/strings.xml +++ b/app/src/main/res/values-qq/strings.xml @@ -98,6 +98,10 @@ {{Identical|Title}} {{Identical|Description}} This is a mountain between Yamagata and Miyagi Prefectures in Japan, see [[d:Q167951]] for details. + An animal. See [[:d:Q42569|Wikidata item Q42569]] for a list of possible translations. + A bridge in Japan. See [[:d:Q1046736|Wikidata item Q1046736]] for a list of possible translations. + A flower. See [[:d:Q93201|Wikidata item Q93201]] for a list of possible translations. + A building in the city of Sydney. See [[:d:Q45178|Wikidata item Q45178]] for a list of possible translations. {{Identical|Cancel}} {{Identical|Open}} {{Identical|Close}} diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index f2d735075..dfc1a68c8 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -7,7 +7,7 @@ Войти Зарегистрироваться Вход в систему - Пожалуйста, подождите… + Пожалуйста, подождите… Опознание прошло успешно Ошибка входа в систему! Файл не найден. Попробуйте другой файл. @@ -87,7 +87,7 @@ Отправить отзыв (по эл. почте) Почтовый клиент не установлен Недавно использованные категории - Ожидание первой синхронизации… + Ожидание первой синхронизации… Вы ещё не загрузили ни одной фотографии. Повторить Отмена @@ -134,7 +134,7 @@ Вам это понятно? Да! Категории - Загрузка… + Загрузка… Ничего не выбрано Нет описания Неизвестная лицензия @@ -165,6 +165,9 @@ Двухфакторная аутентификация в настоящее время не поддерживается. Вы действительно хотите выйти? Логотип Викисклада + Фоновое изображение + Изображение не найден + Загрузить изображение Гора Зао Ламы Радужный мост @@ -180,4 +183,5 @@ Настройки Обратная связь Выйти + Статья на Викискладе diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 154f95c3d..cfbe2ab5e 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -20,11 +20,11 @@ Avslutar uppladdning av %1$s Uppladdning av %1$s misslyckades Tryck för att visa - - 1 filuppladdning - %d filuppladdningar + + %d fil laddas upp + %d filer laddas upp - Mina uppladdningar + Mina senaste uppladdningar Köade Misslyckades %1$d%% färdigt @@ -42,6 +42,7 @@ Det gick inte att logga in - var god kontrollera ditt lösenord För många misslyckade försök. Var god försök igen om några minuter. Tyvärr, denna användare har blockerats på Commons + Du måste ange din tvåstegsverifieringskod. Inloggningen misslyckades Ladda upp Namnge denna uppsättning @@ -50,17 +51,20 @@ Sök kategorier Spara Uppdatera - - Inga uppladdningar ännu - 1 uppladdning + GPS:en är inaktiverad på denna enhet. Vill du aktivera den? + Aktivera GPS + Inga uppladdningar ännu + + \@string/contributions_subtitle_zero + %d uppladdning %d uppladdningar - - Startar 1 uppladdning + + Startar %d uppladdning Startar %d uppladdningar - - 1 uppladdning + + %d uppladdning %d uppladdningar Inga kategorier som stämmer överens med %1$s hittades @@ -74,7 +78,7 @@ Programvara med öppen källkod släppt under <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">licensen Apache v2</a>. Wikimedia Commons och dess logotyp är varumärken av Wikimedia Foundation och används med tillstånd från Wikimedia Foundation. Vi varken stöds eller är kopplade med Wikimedia Foundation. <a href=\"https://github.com/commons-app/apps-android-commons\">Källkoden</a> och <a href=\"https://commons-app.github.io/\">webbplatsen</a> på GitHub. Skapa ett nytt <a href=\"https://github.com/commons-app/apps-android-commons/issues\">GitHub-ärende</a> för att rapportera buggar och förslag. <a href=\"https://wikimediafoundation.org/wiki/Privacy_policy\">Integritetspolicy</a> - <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">ERKÄNNANDE</a> + <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">Erkännande</a> Om Skicka feedback (via e-post) Ingen e-postklient är installerad @@ -135,6 +139,7 @@ Valfri behörighet: Hämta aktuell plats för kategoriförslag OK Platser i närheten + Inga platser i närheten hittades Varning Denna fil finns redan på Commons. Är du säker på att du vill fortsätta? Ja @@ -143,8 +148,42 @@ Mediatitel Beskrivning Beskrivningen för mediafilen ska vara här. Den kan vara riktig lång och kommer att behöva sträcka sig över flera rader. Vi hoppas i alla fall att det kommer se bra ut. + Uppladdningsdatum Bli en betatestare Registrera dig på våra betakanal på Google Play och få tidig åtkomst till nya funktioner och buggfixar Använd Wikidata (Varning: Stora mängder mobildata kan förekomma om detta inaktiveras) + 2-stegsverif.kod + Gräns för mina senaste uppladdningar + Maxgräns + Kan inte visa fler än 500 + Ändra gräns för senaste uppladdningar + Tvåstegsverifiering stöds för tillfället inte. + Vill du verkligen logga ut? + Commons-logotyp + Bakgrundsbild + Mediabild misslyckades + Ingen bild hittades + Ladda upp bild + Zao + Lamor + Regnbågsbro + Tulpan + Inga selfies + Proprietär bild + Välkommen Wikipedia + Välkommen upphovsrätt + Sydneys operahus + Avbryt + Öppna + Stäng + Hem + Ladda upp + I närheten + Om + Inställningar + Återkoppling + Logga ut + Commons-artikel + WikiData-artikel diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index cc1b4f06e..2f9593ad5 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -83,7 +83,7 @@ 傳送回饋 (通過電子郵件) 未安裝電子郵件客戶端 最近使用過的分類 - 等待首次同步… + 等待首次同步… 您尚未上傳任何照片。 重試 取消 @@ -130,7 +130,7 @@ 您明白了嗎? 是! 分類 - 載入中… + 載入中… 未選擇 無說明 未知許可 diff --git a/app/src/test/java/fr/free/nrw/commons/LatLngTests.java b/app/src/test/java/fr/free/nrw/commons/LatLngTests.java index 5267a48bd..aed97c58c 100644 --- a/app/src/test/java/fr/free/nrw/commons/LatLngTests.java +++ b/app/src/test/java/fr/free/nrw/commons/LatLngTests.java @@ -9,55 +9,55 @@ import org.junit.Test; public class LatLngTests { @Test public void testZeroZero() { - LatLng place = new LatLng(0, 0); + LatLng place = new LatLng(0, 0, 0); String prettyString = place.getPrettyCoordinateString(); Assert.assertThat(prettyString, is("0.0 N, 0.0 E")); } @Test public void testAntipode() { - LatLng place = new LatLng(0, 180); + LatLng place = new LatLng(0, 180, 0); String prettyString = place.getPrettyCoordinateString(); Assert.assertThat(prettyString, is("0.0 N, 180.0 W")); } @Test public void testNorthPole() { - LatLng place = new LatLng(90, 0); + LatLng place = new LatLng(90, 0, 0); String prettyString = place.getPrettyCoordinateString(); Assert.assertThat(prettyString, is("90.0 N, 0.0 E")); } @Test public void testSouthPole() { - LatLng place = new LatLng(-90, 0); + LatLng place = new LatLng(-90, 0, 0); String prettyString = place.getPrettyCoordinateString(); Assert.assertThat(prettyString, is("90.0 S, 0.0 E")); } @Test public void testLargerNumbers() { - LatLng place = new LatLng(120, 380); + LatLng place = new LatLng(120, 380, 0); String prettyString = place.getPrettyCoordinateString(); Assert.assertThat(prettyString, is("90.0 N, 20.0 E")); } @Test public void testNegativeNumbers() { - LatLng place = new LatLng(-120, -30); + LatLng place = new LatLng(-120, -30, 0); String prettyString = place.getPrettyCoordinateString(); Assert.assertThat(prettyString, is("90.0 S, 30.0 W")); } @Test public void testTooBigWestValue() { - LatLng place = new LatLng(20, -190); + LatLng place = new LatLng(20, -190, 0); String prettyString = place.getPrettyCoordinateString(); Assert.assertThat(prettyString, is("20.0 N, 170.0 E")); } @Test public void testRounding() { - LatLng place = new LatLng(0.1234567, -0.33333333); + LatLng place = new LatLng(0.1234567, -0.33333333, 0); String prettyString = place.getPrettyCoordinateString(); Assert.assertThat(prettyString, is("0.1235 N, 0.3333 W")); } @Test public void testRoundingAgain() { - LatLng place = new LatLng(-0.000001, -0.999999); + LatLng place = new LatLng(-0.000001, -0.999999, 0); String prettyString = place.getPrettyCoordinateString(); Assert.assertThat(prettyString, is("0.0 S, 1.0 W")); } diff --git a/app/src/test/java/fr/free/nrw/commons/LengthUtilsTest.java b/app/src/test/java/fr/free/nrw/commons/LengthUtilsTest.java index b2556c197..3fdeafc40 100644 --- a/app/src/test/java/fr/free/nrw/commons/LengthUtilsTest.java +++ b/app/src/test/java/fr/free/nrw/commons/LengthUtilsTest.java @@ -10,36 +10,36 @@ import org.junit.Test; public class LengthUtilsTest { @Test public void testZeroDistance() { - LatLng pointA = new LatLng(0, 0); - LatLng pointB = new LatLng(0, 0); + LatLng pointA = new LatLng(0, 0, 0); + LatLng pointB = new LatLng(0, 0, 0); String distance = LengthUtils.formatDistanceBetween(pointA, pointB); Assert.assertThat(distance, is("0m")); } @Test public void testOneDegreeOnEquator() { - LatLng pointA = new LatLng(0, 0); - LatLng pointB = new LatLng(0, 1); + LatLng pointA = new LatLng(0, 0, 0); + LatLng pointB = new LatLng(0, 1, 0); String distance = LengthUtils.formatDistanceBetween(pointA, pointB); Assert.assertThat(distance, is("111.2km")); } @Test public void testOneDegreeFortyFiveDegrees() { - LatLng pointA = new LatLng(45, 0); - LatLng pointB = new LatLng(45, 1); + LatLng pointA = new LatLng(45, 0, 0); + LatLng pointB = new LatLng(45, 1, 0); String distance = LengthUtils.formatDistanceBetween(pointA, pointB); Assert.assertThat(distance, is("78.6km")); } @Test public void testOneDegreeSouthPole() { - LatLng pointA = new LatLng(-90, 0); - LatLng pointB = new LatLng(-90, 1); + LatLng pointA = new LatLng(-90, 0, 0); + LatLng pointB = new LatLng(-90, 1, 0); String distance = LengthUtils.formatDistanceBetween(pointA, pointB); Assert.assertThat(distance, is("0m")); } @Test public void testPoleToPole() { - LatLng pointA = new LatLng(90, 0); - LatLng pointB = new LatLng(-90, 0); + LatLng pointA = new LatLng(90, 0, 0); + LatLng pointB = new LatLng(-90, 0, 0); String distance = LengthUtils.formatDistanceBetween(pointA, pointB); Assert.assertThat(distance, is("20,015.1km")); } diff --git a/build.gradle b/build.gradle index 35001d3c5..aadbcc875 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,8 @@ buildscript { mavenCentral() } dependencies { - classpath "com.android.tools.build:gradle:${project.gradleVersion}" + classpath "com.android.tools.build:gradle:${project.gradleVersion}" + classpath 'com.dicedmelon.gradle:jacoco-android:0.1.1' } }