diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 7b735524a5..cea462cc54 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -12,17 +12,17 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v2.4.0 + uses: actions/checkout@v3 - name: Set up JDK - uses: actions/setup-java@v2.5.0 + uses: actions/setup-java@v3 with: distribution: "temurin" java-version: 17 - name: Cache packages id: cache-packages - uses: actions/cache@v2.1.7 + uses: actions/cache@v3 with: path: | ~/.gradle/caches @@ -37,7 +37,7 @@ jobs: - name: AVD cache if: github.event_name != 'pull_request' - uses: actions/cache@v2 + uses: actions/cache@v3 id: avd-cache with: path: | @@ -89,7 +89,7 @@ jobs: run: bash ./gradlew assembleBetaDebug --stacktrace - name: Upload betaDebug APK - uses: actions/upload-artifact@v2.3.1 + uses: actions/upload-artifact@v3 with: name: betaDebugAPK path: app/build/outputs/apk/beta/debug/app-*.apk @@ -98,7 +98,7 @@ jobs: run: bash ./gradlew assembleProdDebug --stacktrace - name: Upload prodDebug APK - uses: actions/upload-artifact@v2.3.1 + uses: actions/upload-artifact@v3 with: name: prodDebugAPK path: app/build/outputs/apk/prod/debug/app-*.apk diff --git a/app/build.gradle b/app/build.gradle index 5b95f473d5..46d72d0810 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -145,7 +145,7 @@ dependencies { implementation "androidx.multidex:multidex:$MULTIDEX_VERSION" - def work_version = "2.8.0" + def work_version = "2.8.1" // Kotlin + coroutines implementation "androidx.work:work-runtime-ktx:$work_version" implementation("androidx.work:work-runtime:$work_version") @@ -181,7 +181,7 @@ android { setProperty("archivesBaseName", "app-commons-v$versionName-" + getBranchName()) minSdkVersion 21 - targetSdkVersion 31 + targetSdkVersion 33 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunnerArguments clearPackageData: 'true' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e36b08f21d..5ba49201aa 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -13,6 +13,8 @@ + + diff --git a/app/src/main/java/fr/free/nrw/commons/LocationPicker/LocationPickerActivity.java b/app/src/main/java/fr/free/nrw/commons/LocationPicker/LocationPickerActivity.java index c99c94f752..abe2eefd5d 100644 --- a/app/src/main/java/fr/free/nrw/commons/LocationPicker/LocationPickerActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/LocationPicker/LocationPickerActivity.java @@ -57,6 +57,10 @@ import fr.free.nrw.commons.R; import fr.free.nrw.commons.Utils; import fr.free.nrw.commons.kvstore.JsonKvStore; +import fr.free.nrw.commons.location.LocationPermissionsHelper; +import fr.free.nrw.commons.location.LocationPermissionsHelper.Dialog; +import fr.free.nrw.commons.location.LocationPermissionsHelper.LocationPermissionCallback; +import fr.free.nrw.commons.location.LocationServiceManager; import fr.free.nrw.commons.theme.BaseActivity; import fr.free.nrw.commons.utils.SystemThemeUtils; import javax.inject.Inject; @@ -148,6 +152,9 @@ public class LocationPickerActivity extends BaseActivity implements OnMapReadyCa SystemThemeUtils systemThemeUtils; private boolean isDarkTheme; + @Inject + LocationServiceManager locationManager; + @Override protected void onCreate(@Nullable final Bundle savedInstanceState) { getWindow().requestFeature(Window.FEATURE_ACTION_BAR); @@ -452,11 +459,43 @@ private void addCenterOnGPSButton(){ fabCenterOnLocation = findViewById(R.id.center_on_gps); fabCenterOnLocation.setOnClickListener(view -> getCenter()); } + /** - * Animate map to move to desired Latitude and Longitude + * Center the map at user's current location */ - void getCenter() { - mapboxMap.animateCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(location.getLatitude(),location.getLongitude()),15.0)); + private void getCenter() { + LocationPermissionsHelper.Dialog locationAccessDialog = new Dialog( + R.string.location_permission_title, + R.string.upload_map_location_access + ); + + LocationPermissionsHelper.Dialog locationOffDialog = new Dialog( + R.string.ask_to_turn_location_on, + R.string.upload_map_location_access + ); + LocationPermissionsHelper locationPermissionsHelper = new LocationPermissionsHelper( + this, locationManager, new LocationPermissionCallback() { + @Override + public void onLocationPermissionDenied(String toastMessage) { + // Do nothing + } + + @Override + public void onLocationPermissionGranted() { + fr.free.nrw.commons.location.LatLng currLocation = locationManager.getLastLocation(); + if (currLocation != null) { + final CameraPosition position; + position = new CameraPosition.Builder() + .target(new com.mapbox.mapboxsdk.geometry.LatLng(currLocation.getLatitude(), + currLocation.getLongitude(), 0)) // Sets the new camera position + .zoom(mapboxMap.getCameraPosition().zoom) // Same zoom level + .build(); + + mapboxMap.animateCamera(CameraUpdateFactory.newCameraPosition(position), 1000); + } + } + }); + locationPermissionsHelper.handleLocationPermissions(locationAccessDialog, locationOffDialog); } @Override diff --git a/app/src/main/java/fr/free/nrw/commons/auth/SignupActivity.java b/app/src/main/java/fr/free/nrw/commons/auth/SignupActivity.java index d6e4568fd9..be90bb4bb6 100644 --- a/app/src/main/java/fr/free/nrw/commons/auth/SignupActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/auth/SignupActivity.java @@ -1,5 +1,7 @@ package fr.free.nrw.commons.auth; +import android.content.res.Configuration; +import android.os.Build; import android.os.Bundle; import android.webkit.WebSettings; import android.webkit.WebView; @@ -61,4 +63,20 @@ public void onBackPressed() { super.onBackPressed(); } } + + /** + * Known bug in androidx.appcompat library version 1.1.0 being tracked here + * https://issuetracker.google.com/issues/141132133 + * App tries to put light/dark theme to webview and crashes in the process + * This code tries to prevent applying the theme when sdk is between api 21 to 25 + * @param overrideConfiguration + */ + @Override + public void applyOverrideConfiguration(final Configuration overrideConfiguration) { + if (Build.VERSION.SDK_INT <= 25 && + (getResources().getConfiguration().uiMode == getApplicationContext().getResources().getConfiguration().uiMode)) { + return; + } + super.applyOverrideConfiguration(overrideConfiguration); + } } diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarkListRootFragment.java b/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarkListRootFragment.java index 790ec263fb..27f6e824f0 100644 --- a/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarkListRootFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarkListRootFragment.java @@ -184,7 +184,7 @@ public Integer getContributionStateAt(int position) { public void refreshNominatedMedia(int index) { if (mediaDetails != null && !listFragment.isVisible()) { removeFragment(mediaDetails); - mediaDetails = new MediaDetailPagerFragment(false, true); + mediaDetails = MediaDetailPagerFragment.newInstance(false, true); ((BookmarkFragment) getParentFragment()).setScroll(false); setFragment(mediaDetails, listFragment); mediaDetails.showImage(index); @@ -243,7 +243,7 @@ public void onItemClick(AdapterView parent, View view, int position, long id) Log.d("deneme8", "on media clicked"); container.setVisibility(View.VISIBLE); ((BookmarkFragment) getParentFragment()).tabLayout.setVisibility(View.GONE); - mediaDetails = new MediaDetailPagerFragment(false, true); + mediaDetails = MediaDetailPagerFragment.newInstance(false, true); ((BookmarkFragment) getParentFragment()).setScroll(false); setFragment(mediaDetails, listFragment); mediaDetails.showImage(position); diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategoryDetailsActivity.java b/app/src/main/java/fr/free/nrw/commons/category/CategoryDetailsActivity.java index 78b627cefd..341d13f9f5 100644 --- a/app/src/main/java/fr/free/nrw/commons/category/CategoryDetailsActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/category/CategoryDetailsActivity.java @@ -115,7 +115,7 @@ public void onMediaClicked(int position) { mediaContainer.setVisibility(View.VISIBLE); if (mediaDetails == null || !mediaDetails.isVisible()) { // set isFeaturedImage true for featured images, to include author field on media detail - mediaDetails = new MediaDetailPagerFragment(false, true); + mediaDetails = MediaDetailPagerFragment.newInstance(false, true); FragmentManager supportFragmentManager = getSupportFragmentManager(); supportFragmentManager .beginTransaction() diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/Contribution.kt b/app/src/main/java/fr/free/nrw/commons/contributions/Contribution.kt index 400bddfba1..b75271f054 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/Contribution.kt +++ b/app/src/main/java/fr/free/nrw/commons/contributions/Contribution.kt @@ -43,7 +43,11 @@ data class Contribution constructor( var hasInvalidLocation : Int = 0, var contentUri: Uri? = null, var countryCode : String? = null, - var imageSHA1 : String? = null + var imageSHA1 : String? = null, + /** + * Number of times a contribution has been retried after a failure + */ + var retries: Int = 0 ) : Parcelable { fun completeWith(media: Media): Contribution { @@ -111,6 +115,6 @@ data class Contribution constructor( */ fun formatDescriptions(descriptions: List) = descriptions.filter { it.descriptionText.isNotEmpty() } - .joinToString { "{{${it.languageCode}|1=${it.descriptionText}}}" } + .joinToString(separator = "") { "{{${it.languageCode}|1=${it.descriptionText}}}" } } } diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.java index 0a01ef70c3..567293b817 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.java @@ -6,6 +6,7 @@ import android.app.Activity; import android.content.Context; import android.content.Intent; +import android.widget.Toast; import androidx.annotation.NonNull; import fr.free.nrw.commons.R; import fr.free.nrw.commons.filepicker.DefaultCallback; @@ -13,8 +14,14 @@ import fr.free.nrw.commons.filepicker.FilePicker.ImageSource; import fr.free.nrw.commons.filepicker.UploadableFile; import fr.free.nrw.commons.kvstore.JsonKvStore; +import fr.free.nrw.commons.location.LatLng; +import fr.free.nrw.commons.location.LocationPermissionsHelper; +import fr.free.nrw.commons.location.LocationPermissionsHelper.Dialog; +import fr.free.nrw.commons.location.LocationPermissionsHelper.LocationPermissionCallback; +import fr.free.nrw.commons.location.LocationServiceManager; import fr.free.nrw.commons.nearby.Place; import fr.free.nrw.commons.upload.UploadActivity; +import fr.free.nrw.commons.utils.DialogUtil; import fr.free.nrw.commons.utils.PermissionUtils; import fr.free.nrw.commons.utils.ViewUtil; import java.util.ArrayList; @@ -28,7 +35,11 @@ public class ContributionController { public static final String ACTION_INTERNAL_UPLOADS = "internalImageUploads"; private final JsonKvStore defaultKvStore; + private LatLng locationBeforeImageCapture; + private boolean isInAppCameraUpload; + @Inject + LocationServiceManager locationManager; @Inject public ContributionController(@Named("default_preferences") JsonKvStore defaultKvStore) { this.defaultKvStore = defaultKvStore; @@ -45,12 +56,100 @@ public void initiateCameraPick(Activity activity) { } PermissionUtils.checkPermissionsAndPerformAction(activity, - Manifest.permission.WRITE_EXTERNAL_STORAGE, - () -> initiateCameraUpload(activity), + PermissionUtils.PERMISSIONS_STORAGE, + () -> { + if (defaultKvStore.getBoolean("inAppCameraFirstRun")) { + defaultKvStore.putBoolean("inAppCameraFirstRun", false); + askUserToAllowLocationAccess(activity); + } else if(defaultKvStore.getBoolean("inAppCameraLocationPref")) { + createDialogsAndHandleLocationPermissions(activity); + } else { + initiateCameraUpload(activity); + } + }, R.string.storage_permission_title, R.string.write_storage_permission_rationale); } + /** + * Asks users to provide location access + * + * @param activity + */ + private void createDialogsAndHandleLocationPermissions(Activity activity) { + LocationPermissionsHelper.Dialog locationAccessDialog = new Dialog( + R.string.location_permission_title, + R.string.in_app_camera_location_permission_rationale + ); + + LocationPermissionsHelper.Dialog locationOffDialog = new Dialog( + R.string.ask_to_turn_location_on, + R.string.in_app_camera_needs_location + ); + LocationPermissionsHelper locationPermissionsHelper = new LocationPermissionsHelper( + activity, locationManager, + new LocationPermissionCallback() { + @Override + public void onLocationPermissionDenied(String toastMessage) { + Toast.makeText( + activity, + toastMessage, + Toast.LENGTH_LONG + ).show(); + initiateCameraUpload(activity); + } + + @Override + public void onLocationPermissionGranted() { + initiateCameraUpload(activity); + } + } + ); + locationPermissionsHelper.handleLocationPermissions( + locationAccessDialog, + locationOffDialog + ); + } + + /** + * Suggest user to attach location information with pictures. + * If the user selects "Yes", then: + * + * Location is taken from the EXIF if the default camera application + * does not redact location tags. + * + * Otherwise, if the EXIF metadata does not have location information, + * then location captured by the app is used + * + * @param activity + */ + private void askUserToAllowLocationAccess(Activity activity) { + DialogUtil.showAlertDialog(activity, + activity.getString(R.string.in_app_camera_location_permission_title), + activity.getString(R.string.in_app_camera_location_access_explanation), + activity.getString(R.string.option_allow), + activity.getString(R.string.option_dismiss), + ()-> { + defaultKvStore.putBoolean("inAppCameraLocationPref", true); + createDialogsAndHandleLocationPermissions(activity); + }, + () -> { + defaultKvStore.putBoolean("inAppCameraLocationPref", false); + initiateCameraUpload(activity); + }, + null, + true); + } + + /** + * Check if apps have access to location even after having individual access + * + * @return + */ + private boolean isLocationAccessToAppsTurnedOn() { + return (locationManager.isNetworkProviderEnabled() || locationManager.isGPSProviderEnabled()); + } + /** * Initiate gallery picker */ @@ -65,10 +164,8 @@ public void initiateCustomGalleryPickWithPermission(final Activity activity) { setPickerConfiguration(activity,true); PermissionUtils.checkPermissionsAndPerformAction(activity, - Manifest.permission.WRITE_EXTERNAL_STORAGE, - () -> { - FilePicker.openCustomSelector(activity, 0); - }, + PermissionUtils.PERMISSIONS_STORAGE, + () -> FilePicker.openCustomSelector(activity, 0), R.string.storage_permission_title, R.string.write_storage_permission_rationale); } @@ -79,8 +176,8 @@ public void initiateCustomGalleryPickWithPermission(final Activity activity) { */ private void initiateGalleryUpload(final Activity activity, final boolean allowMultipleUploads) { setPickerConfiguration(activity, allowMultipleUploads); - boolean isGetContentPickerPreferred = defaultKvStore.getBoolean("getContentPhotoPickerPref"); - FilePicker.openGallery(activity, 0, isGetContentPickerPreferred); + boolean openDocumentIntentPreferred = defaultKvStore.getBoolean("openDocumentPhotoPickerPref", true); + FilePicker.openGallery(activity, 0, openDocumentIntentPreferred); } /** @@ -99,6 +196,10 @@ private void setPickerConfiguration(Activity activity, */ private void initiateCameraUpload(Activity activity) { setPickerConfiguration(activity, false); + if (defaultKvStore.getBoolean("inAppCameraLocationPref", false)) { + locationBeforeImageCapture = locationManager.getLastLocation(); + } + isInAppCameraUpload = true; FilePicker.openCameraForImage(activity, 0); } @@ -134,7 +235,8 @@ public List handleExternalImagesPicked(Activity activity, /** * Returns intent to be passed to upload activity - * Attaches place object for nearby uploads + * Attaches place object for nearby uploads and + * location before image capture if in-app camera is used */ private Intent handleImagesPicked(Context context, List imagesFiles) { @@ -148,6 +250,17 @@ private Intent handleImagesPicked(Context context, shareIntent.putExtra(PLACE_OBJECT, place); } + if (locationBeforeImageCapture != null) { + shareIntent.putExtra( + UploadActivity.LOCATION_BEFORE_IMAGE_CAPTURE, + locationBeforeImageCapture); + } + + shareIntent.putExtra( + UploadActivity.IN_APP_CAMERA_UPLOAD, + isInAppCameraUpload + ); + isInAppCameraUpload = false; // reset the flag for next use return shareIntent; } diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.java index 03e3e56116..7d8058568e 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.java @@ -92,6 +92,7 @@ public class ContributionsFragment private static final String CONTRIBUTION_LIST_FRAGMENT_TAG = "ContributionListFragmentTag"; private MediaDetailPagerFragment mediaDetailPagerFragment; static final String MEDIA_DETAIL_PAGER_FRAGMENT_TAG = "MediaDetailFragmentTag"; + private static final int MAX_RETRIES = 10; @BindView(R.id.card_view_nearby) public NearbyNotificationCardView nearbyNotificationCardView; @BindView(R.id.campaigns_view) CampaignView campaignView; @@ -438,7 +439,7 @@ public void onResume() { } private void checkPermissionsAndShowNearbyCardView() { - if (PermissionUtils.hasPermission(getActivity(), Manifest.permission.ACCESS_FINE_LOCATION)) { + if (PermissionUtils.hasPermission(getActivity(), new String[]{Manifest.permission.ACCESS_FINE_LOCATION})) { onLocationPermissionGranted(); } else if (shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION) && store.getBoolean("displayLocationPermissionForCardView", true) @@ -451,7 +452,7 @@ private void checkPermissionsAndShowNearbyCardView() { private void requestLocationPermission() { PermissionUtils.checkPermissionsAndPerformAction(getActivity(), - Manifest.permission.ACCESS_FINE_LOCATION, + new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, this::onLocationPermissionGranted, this::displayYouWontSeeNearbyMessage, -1, @@ -593,6 +594,15 @@ public void notifyDataSetChanged() { } } + /** + * Restarts the upload process for a contribution + * @param contribution + */ + public void restartUpload(Contribution contribution) { + contribution.setState(Contribution.STATE_QUEUED); + contributionsPresenter.saveContribution(contribution); + Timber.d("Restarting for %s", contribution.toString()); + } /** * Retry upload when it is failed * @@ -601,10 +611,23 @@ public void notifyDataSetChanged() { @Override public void retryUpload(Contribution contribution) { if (NetworkUtils.isInternetConnectionEstablished(getContext())) { - if (contribution.getState() == STATE_FAILED || contribution.getState() == STATE_PAUSED || contribution.getState()==Contribution.STATE_QUEUED_LIMITED_CONNECTION_MODE) { - contribution.setState(Contribution.STATE_QUEUED); - contributionsPresenter.saveContribution(contribution); - Timber.d("Restarting for %s", contribution.toString()); + if (contribution.getState() == STATE_PAUSED || contribution.getState()==Contribution.STATE_QUEUED_LIMITED_CONNECTION_MODE) { + restartUpload(contribution); + } else if (contribution.getState() == STATE_FAILED) { + int retries = contribution.getRetries(); + // TODO: Improve UX. Additional details: https://github.com/commons-app/apps-android-commons/pull/5257#discussion_r1304662562 + /* Limit the number of retries for a failed upload + to handle cases like invalid filename as such uploads + will never be successful */ + if(retries < MAX_RETRIES) { + contribution.setRetries(retries + 1); + Timber.d("Retried uploading %s %d times", contribution.getMedia().getFilename(), retries + 1); + restartUpload(contribution); + } else { + // TODO: Show the exact reason for failure + Toast.makeText(getContext(), + R.string.retry_limit_reached, Toast.LENGTH_SHORT).show(); + } } else { Timber.d("Skipping re-upload for non-failed %s", contribution.toString()); } @@ -645,7 +668,7 @@ public void viewPagerNotifyDataSetChanged() { @Override public void showDetail(int position, boolean isWikipediaButtonDisplayed) { if (mediaDetailPagerFragment == null || !mediaDetailPagerFragment.isVisible()) { - mediaDetailPagerFragment = new MediaDetailPagerFragment(false, true); + mediaDetailPagerFragment = MediaDetailPagerFragment.newInstance(false, true); if(isUserProfile) { ((ProfileActivity)getActivity()).setScroll(false); } @@ -726,7 +749,7 @@ void upDateUploadCount() { public void refreshNominatedMedia(int index) { if(mediaDetailPagerFragment != null && !contributionsListFragment.isVisible()) { removeFragment(mediaDetailPagerFragment); - mediaDetailPagerFragment = new MediaDetailPagerFragment(false, true); + mediaDetailPagerFragment = MediaDetailPagerFragment.newInstance(false, true); mediaDetailPagerFragment.showImage(index); showMediaDetailPagerFragment(); } diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsPresenter.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsPresenter.java index 002e8bc95b..f676f193ab 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsPresenter.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsPresenter.java @@ -1,18 +1,12 @@ package fr.free.nrw.commons.contributions; import androidx.work.ExistingWorkPolicy; -import androidx.work.OneTimeWorkRequest; -import androidx.work.WorkManager; import fr.free.nrw.commons.MediaDataExtractor; import fr.free.nrw.commons.contributions.ContributionsContract.UserActionListener; import fr.free.nrw.commons.di.CommonsApplicationModule; -import fr.free.nrw.commons.upload.worker.UploadWorker; +import fr.free.nrw.commons.upload.worker.WorkRequestHelper; import io.reactivex.Scheduler; import io.reactivex.disposables.CompositeDisposable; -import io.reactivex.functions.Action; -import io.reactivex.functions.Consumer; -import java.util.Collections; -import java.util.List; import javax.inject.Inject; import javax.inject.Named; @@ -76,11 +70,7 @@ public void saveContribution(Contribution contribution) { compositeDisposable.add(repository .save(contribution) .subscribeOn(ioThreadScheduler) - .subscribe(() -> { - WorkManager.getInstance(view.getContext().getApplicationContext()) - .enqueueUniqueWork( - UploadWorker.class.getSimpleName(), - ExistingWorkPolicy.KEEP, OneTimeWorkRequest.from(UploadWorker.class)); - })); + .subscribe(() -> WorkRequestHelper.Companion.makeOneTimeWorkRequest( + view.getContext().getApplicationContext(), ExistingWorkPolicy.KEEP))); } } diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.java b/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.java index a96f1f37bd..6a14f8ec61 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.java @@ -1,6 +1,7 @@ package fr.free.nrw.commons.contributions; import android.Manifest.permission; +import android.annotation.SuppressLint; import android.app.Activity; import android.content.Context; import android.content.Intent; @@ -18,8 +19,6 @@ import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.work.ExistingWorkPolicy; -import androidx.work.OneTimeWorkRequest; -import androidx.work.WorkManager; import butterknife.BindView; import butterknife.ButterKnife; import fr.free.nrw.commons.CommonsApplication; @@ -44,9 +43,11 @@ import fr.free.nrw.commons.quiz.QuizChecker; import fr.free.nrw.commons.settings.SettingsFragment; import fr.free.nrw.commons.theme.BaseActivity; -import fr.free.nrw.commons.upload.worker.UploadWorker; +import fr.free.nrw.commons.upload.worker.WorkRequestHelper; import fr.free.nrw.commons.utils.PermissionUtils; import fr.free.nrw.commons.utils.ViewUtilWrapper; +import io.reactivex.schedulers.Schedulers; +import java.util.Collections; import javax.inject.Inject; import javax.inject.Named; import timber.log.Timber; @@ -58,6 +59,8 @@ public class MainActivity extends BaseActivity SessionManager sessionManager; @Inject ContributionController controller; + @Inject + ContributionDao contributionDao; @BindView(R.id.toolbar) Toolbar toolbar; @BindView(R.id.pager) @@ -138,6 +141,9 @@ public void onCreate(Bundle savedInstanceState) { setTitle(getString(R.string.navigation_item_explore)); setUpLoggedOutPager(); } else { + if (applicationKvStore.getBoolean("firstrun", true)) { + applicationKvStore.putBoolean("hasAlreadyLaunchedBigMultiupload", false); + } if(savedInstanceState == null){ //starting a fresh fragment. // Open Last opened screen if it is Contributions or Nearby, otherwise Contributions @@ -159,7 +165,7 @@ public void onCreate(Bundle savedInstanceState) { if (VERSION.SDK_INT >= VERSION_CODES.Q) { PermissionUtils.checkPermissionsAndPerformAction( this, - permission.ACCESS_MEDIA_LOCATION, + new String[]{permission.ACCESS_MEDIA_LOCATION}, () -> {}, R.string.media_location_permission_denied, R.string.add_location_manually @@ -360,6 +366,21 @@ public boolean onOptionsItemSelected(MenuItem item) { } } + /** + * Retry all failed uploads as soon as the user returns to the app + */ + @SuppressLint("CheckResult") + private void retryAllFailedUploads() { + contributionDao. + getContribution(Collections.singletonList(Contribution.STATE_FAILED)) + .subscribeOn(Schedulers.io()) + .subscribe(failedUploads -> { + for (Contribution contribution: failedUploads) { + contributionsFragment.retryUpload(contribution); + } + }); + } + public void toggleLimitedConnectionMode() { defaultKvStore.putBoolean(CommonsApplication.IS_LIMITED_CONNECTION_MODE_ENABLED, !defaultKvStore @@ -369,10 +390,8 @@ public void toggleLimitedConnectionMode() { viewUtilWrapper .showShortToast(getBaseContext(), getString(R.string.limited_connection_enabled)); } else { - WorkManager.getInstance(getApplicationContext()).enqueueUniqueWork( - UploadWorker.class.getSimpleName(), - ExistingWorkPolicy.APPEND_OR_REPLACE, OneTimeWorkRequest.from(UploadWorker.class)); - + WorkRequestHelper.Companion.makeOneTimeWorkRequest(getApplicationContext(), + ExistingWorkPolicy.APPEND_OR_REPLACE); viewUtilWrapper .showShortToast(getBaseContext(), getString(R.string.limited_connection_disabled)); } @@ -403,8 +422,11 @@ protected void onResume() { if ((applicationKvStore.getBoolean("firstrun", true)) && (!applicationKvStore.getBoolean("login_skipped"))) { + defaultKvStore.putBoolean("inAppCameraFirstRun", true); WelcomeActivity.startYourself(this); } + + retryAllFailedUploads(); } @Override diff --git a/app/src/main/java/fr/free/nrw/commons/db/AppDatabase.kt b/app/src/main/java/fr/free/nrw/commons/db/AppDatabase.kt index 49c95343a5..6d63e58a1c 100644 --- a/app/src/main/java/fr/free/nrw/commons/db/AppDatabase.kt +++ b/app/src/main/java/fr/free/nrw/commons/db/AppDatabase.kt @@ -15,7 +15,7 @@ import fr.free.nrw.commons.upload.depicts.DepictsDao * The database for accessing the respective DAOs * */ -@Database(entities = [Contribution::class, Depicts::class, UploadedStatus::class, NotForUploadStatus::class, ReviewEntity::class], version = 15, exportSchema = false) +@Database(entities = [Contribution::class, Depicts::class, UploadedStatus::class, NotForUploadStatus::class, ReviewEntity::class], version = 16, exportSchema = false) @TypeConverters(Converters::class) abstract class AppDatabase : RoomDatabase() { abstract fun contributionDao(): ContributionDao diff --git a/app/src/main/java/fr/free/nrw/commons/description/DescriptionEditActivity.kt b/app/src/main/java/fr/free/nrw/commons/description/DescriptionEditActivity.kt index 8d7795f5e4..c7750457e4 100644 --- a/app/src/main/java/fr/free/nrw/commons/description/DescriptionEditActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/description/DescriptionEditActivity.kt @@ -151,7 +151,7 @@ class DescriptionEditActivity : BaseActivity(), UploadMediaDetailAdapter.EventLi buffer.append(uploadDetails.languageCode) buffer.append("|1=") buffer.append(uploadDetails.descriptionText) - buffer.append("}}, ") + buffer.append("}}") } } buffer.replace(", $".toRegex(), "") diff --git a/app/src/main/java/fr/free/nrw/commons/explore/ExploreListRootFragment.java b/app/src/main/java/fr/free/nrw/commons/explore/ExploreListRootFragment.java index 1b32d14239..cc2ad5fa62 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/ExploreListRootFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/explore/ExploreListRootFragment.java @@ -111,7 +111,7 @@ public void onAttach(final Context context) { public void onMediaClicked(int position) { container.setVisibility(View.VISIBLE); ((ExploreFragment) getParentFragment()).tabLayout.setVisibility(View.GONE); - mediaDetails = new MediaDetailPagerFragment(false, true); + mediaDetails = MediaDetailPagerFragment.newInstance(false, true); ((ExploreFragment) getParentFragment()).setScroll(false); setFragment(mediaDetails, listFragment); mediaDetails.showImage(position); diff --git a/app/src/main/java/fr/free/nrw/commons/explore/ExploreMapRootFragment.java b/app/src/main/java/fr/free/nrw/commons/explore/ExploreMapRootFragment.java index 385cd886a4..e01fb5948d 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/ExploreMapRootFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/explore/ExploreMapRootFragment.java @@ -118,7 +118,7 @@ public void onAttach(final Context context) { public void onMediaClicked(int position) { container.setVisibility(View.VISIBLE); ((ExploreFragment) getParentFragment()).tabLayout.setVisibility(View.GONE); - mediaDetails = new MediaDetailPagerFragment(false, true); + mediaDetails = MediaDetailPagerFragment.newInstance(false, true); ((ExploreFragment) getParentFragment()).setScroll(false); setFragment(mediaDetails, mapFragment); mediaDetails.showImage(position); diff --git a/app/src/main/java/fr/free/nrw/commons/explore/SearchActivity.java b/app/src/main/java/fr/free/nrw/commons/explore/SearchActivity.java index 6701d2c279..7f80c367e4 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/SearchActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/explore/SearchActivity.java @@ -221,7 +221,7 @@ public void onMediaClicked(int index) { searchView.setVisibility(View.GONE);// to remove searchview when mediaDetails fragment open if (mediaDetails == null || !mediaDetails.isVisible()) { // set isFeaturedImage true for featured images, to include author field on media detail - mediaDetails = new MediaDetailPagerFragment(false, true); + mediaDetails = MediaDetailPagerFragment.newInstance(false, true); supportFragmentManager .beginTransaction() .hide(supportFragmentManager.getFragments().get(supportFragmentManager.getBackStackEntryCount())) diff --git a/app/src/main/java/fr/free/nrw/commons/explore/depictions/WikidataItemDetailsActivity.java b/app/src/main/java/fr/free/nrw/commons/explore/depictions/WikidataItemDetailsActivity.java index 6f250ef60e..3b6580365f 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/depictions/WikidataItemDetailsActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/explore/depictions/WikidataItemDetailsActivity.java @@ -153,7 +153,7 @@ public void onMediaClicked(int position) { mediaContainer.setVisibility(View.VISIBLE); if (mediaDetailPagerFragment == null || !mediaDetailPagerFragment.isVisible()) { // set isFeaturedImage true for featured images, to include author field on media detail - mediaDetailPagerFragment = new MediaDetailPagerFragment(false, true); + mediaDetailPagerFragment = MediaDetailPagerFragment.newInstance(false, true); FragmentManager supportFragmentManager = getSupportFragmentManager(); supportFragmentManager .beginTransaction() diff --git a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.java b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.java index d2e6a432b5..a916c20cba 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.java @@ -263,7 +263,7 @@ private void registerNetworkReceiver() { private void performMapReadyActions() { if (isMapBoxReady) { if(!applicationKvStore.getBoolean("doNotAskForLocationPermission", false) || - PermissionUtils.hasPermission(getActivity(), Manifest.permission.ACCESS_FINE_LOCATION)){ + PermissionUtils.hasPermission(getActivity(), new String[]{Manifest.permission.ACCESS_FINE_LOCATION})){ checkPermissionsAndPerformAction(); }else{ isPermissionDenied = true; @@ -404,8 +404,8 @@ private void showErrorMessage(final String message) { public void checkPermissionsAndPerformAction() { Timber.d("Checking permission and perfoming action"); PermissionUtils.checkPermissionsAndPerformAction(getActivity(), - Manifest.permission.ACCESS_FINE_LOCATION, - () -> locationPermissionGranted(), + new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, + this::locationPermissionGranted, () -> isPermissionDenied = true, R.string.location_permission_title, R.string.location_permission_rationale_nearby); diff --git a/app/src/main/java/fr/free/nrw/commons/filepicker/FilePicker.java b/app/src/main/java/fr/free/nrw/commons/filepicker/FilePicker.java index 9591e96a5b..201aacb862 100644 --- a/app/src/main/java/fr/free/nrw/commons/filepicker/FilePicker.java +++ b/app/src/main/java/fr/free/nrw/commons/filepicker/FilePicker.java @@ -45,10 +45,10 @@ private static Uri createCameraPictureFile(@NonNull Context context) throws IOEx } private static Intent createGalleryIntent(@NonNull Context context, int type, - boolean isGetContentPickerPreferred) { + boolean openDocumentIntentPreferred) { // storing picked image type to shared preferences storeType(context, type); - return plainGalleryPickerIntent(isGetContentPickerPreferred) + return plainGalleryPickerIntent(openDocumentIntentPreferred) .putExtra(Intent.EXTRA_ALLOW_MULTIPLE, configuration(context).allowsMultiplePickingInGallery()); } @@ -104,8 +104,8 @@ private static int restoreType(@NonNull Context context) { * * @param type Custom type of your choice, which will be returned with the images */ - public static void openGallery(Activity activity, int type, boolean isGetContentPickerPreferred) { - Intent intent = createGalleryIntent(activity, type, isGetContentPickerPreferred); + public static void openGallery(Activity activity, int type, boolean openDocumentIntentPreferred) { + Intent intent = createGalleryIntent(activity, type, openDocumentIntentPreferred); activity.startActivityForResult(intent, RequestCodes.PICK_PICTURE_FROM_GALLERY); } @@ -199,8 +199,8 @@ private static boolean isPhoto(Intent data) { return data == null || (data.getData() == null && data.getClipData() == null); } - private static Intent plainGalleryPickerIntent(boolean isGetContentPickerPreferred) { - /** + private static Intent plainGalleryPickerIntent(boolean openDocumentIntentPreferred) { + /* * Asking for ACCESS_MEDIA_LOCATION at runtime solved the location-loss issue * in the custom selector in Contributions fragment. * Detailed discussion: https://github.com/commons-app/apps-android-commons/issues/5015 @@ -215,8 +215,8 @@ private static Intent plainGalleryPickerIntent(boolean isGetContentPickerPreferr * Reported on the Google Issue Tracker: https://issuetracker.google.com/issues/243294058 * Status: Won't fix (Intended behaviour) * - * Switched intent from ACTION_GET_CONTENT to ACTION_OPEN_DOCUMENT - * (based on user's preference) as: + * Switched intent from ACTION_GET_CONTENT to ACTION_OPEN_DOCUMENT (by default; can + * be changed through the Setting page) as: * * ACTION_GET_CONTENT opens the 'best application' for choosing that kind of data * The best application is the new Photo Picker that redacts the location tags @@ -224,14 +224,15 @@ private static Intent plainGalleryPickerIntent(boolean isGetContentPickerPreferr * ACTION_OPEN_DOCUMENT, however, displays the various DocumentsProvider instances * installed on the device, letting the user interactively navigate through them. * - * So, this allows us to use the traditional file picker that does not redact location tags from EXIF. + * So, this allows us to use the traditional file picker that does not redact location tags + * from EXIF. * */ Intent intent; - if (isGetContentPickerPreferred) { - intent = new Intent(Intent.ACTION_GET_CONTENT); - } else { + if (openDocumentIntentPreferred) { intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + } else { + intent = new Intent(Intent.ACTION_GET_CONTENT); } intent.setType("image/*"); return intent; diff --git a/app/src/main/java/fr/free/nrw/commons/filepicker/PickedFiles.java b/app/src/main/java/fr/free/nrw/commons/filepicker/PickedFiles.java index ea59831731..ca1abba623 100644 --- a/app/src/main/java/fr/free/nrw/commons/filepicker/PickedFiles.java +++ b/app/src/main/java/fr/free/nrw/commons/filepicker/PickedFiles.java @@ -56,18 +56,13 @@ private static File tempImageDirectory(@NonNull Context context) { * @param in input stream of source file. * @param file destination file */ - private static void writeToFile(InputStream in, File file) { - try { - OutputStream out = new FileOutputStream(file); + private static void writeToFile(InputStream in, File file) throws IOException { + try (OutputStream out = new FileOutputStream(file)) { byte[] buf = new byte[1024]; int len; while ((len = in.read(buf)) > 0) { out.write(buf, 0, len); } - out.close(); - in.close(); - } catch (Exception e) { - e.printStackTrace(); } } @@ -79,8 +74,9 @@ private static void writeToFile(InputStream in, File file) { * @throws IOException (File input stream exception) */ private static void copyFile(File src, File dst) throws IOException { - InputStream in = new FileInputStream(src); - writeToFile(in, dst); + try (InputStream in = new FileInputStream(src)) { + writeToFile(in, dst); + } } /** @@ -157,11 +153,12 @@ static void scanCopiedImages(Context context, List copiedImages) { * @return Uploadable file ready for tag redaction. */ public static UploadableFile pickedExistingPicture(@NonNull Context context, Uri photoUri) throws IOException, SecurityException {// SecurityException for those file providers who share URI but forget to grant necessary permissions - InputStream pictureInputStream = context.getContentResolver().openInputStream(photoUri); File directory = tempImageDirectory(context); File photoFile = new File(directory, UUID.randomUUID().toString() + "." + getMimeType(context, photoUri)); if (photoFile.createNewFile()) { - writeToFile(pictureInputStream, photoFile); + try (InputStream pictureInputStream = context.getContentResolver().openInputStream(photoUri)) { + writeToFile(pictureInputStream, photoFile); + } } else { throw new IOException("could not create photoFile to write upon"); } diff --git a/app/src/main/java/fr/free/nrw/commons/filepicker/UploadableFile.java b/app/src/main/java/fr/free/nrw/commons/filepicker/UploadableFile.java index 7df1cc9648..5e3a43bbe4 100644 --- a/app/src/main/java/fr/free/nrw/commons/filepicker/UploadableFile.java +++ b/app/src/main/java/fr/free/nrw/commons/filepicker/UploadableFile.java @@ -41,7 +41,7 @@ public UploadableFile(Uri contentUri, File file) { public UploadableFile(File file) { this.file = file; - this.contentUri = Uri.parse(file.getAbsolutePath()); + this.contentUri = Uri.fromFile(new File(file.getPath())); } public UploadableFile(Parcel in) { 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 2d091245e0..4970fc54fc 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 @@ -168,7 +168,7 @@ public double getLatitude() { } public Uri getGmmIntentUri() { - return Uri.parse("geo:0,0?q=" + latitude + "," + longitude); + return Uri.parse("geo:" + latitude + "," + longitude + "?z=16"); } @Override diff --git a/app/src/main/java/fr/free/nrw/commons/location/LocationPermissionsHelper.java b/app/src/main/java/fr/free/nrw/commons/location/LocationPermissionsHelper.java new file mode 100644 index 0000000000..9760f5d419 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/location/LocationPermissionsHelper.java @@ -0,0 +1,127 @@ +package fr.free.nrw.commons.location; + +import android.Manifest.permission; +import android.app.Activity; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.provider.Settings; +import android.widget.Toast; +import fr.free.nrw.commons.R; +import fr.free.nrw.commons.utils.DialogUtil; +import fr.free.nrw.commons.utils.PermissionUtils; + +/** + * Helper class to handle location permissions + */ +public class LocationPermissionsHelper { + Activity activity; + LocationServiceManager locationManager; + LocationPermissionCallback callback; + public LocationPermissionsHelper(Activity activity, LocationServiceManager locationManager, + LocationPermissionCallback callback) { + this.activity = activity; + this.locationManager = locationManager; + this.callback = callback; + } + public static class Dialog { + int dialogTitleResource; + int dialogTextResource; + + public Dialog(int dialogTitle, int dialogText) { + dialogTitleResource = dialogTitle; + dialogTextResource = dialogText; + } + } + + /** + * Handles the entire location permissions flow + * + * @param locationAccessDialog + * @param locationOffDialog + */ + public void handleLocationPermissions(Dialog locationAccessDialog, + Dialog locationOffDialog) { + requestForLocationAccess(locationAccessDialog, locationOffDialog); + } + + /** + * Ask for location permission if the user agrees on attaching location with pictures + * and the app does not have the access to location + * + * @param locationAccessDialog + * @param locationOffDialog + */ + private void requestForLocationAccess( + Dialog locationAccessDialog, + Dialog locationOffDialog + ) { + PermissionUtils.checkPermissionsAndPerformAction(activity, + new String[]{permission.ACCESS_FINE_LOCATION}, + () -> { + if(!isLocationAccessToAppsTurnedOn()) { + showLocationOffDialog(locationOffDialog); + } else { + if (callback != null) { + callback.onLocationPermissionGranted(); + } + } + }, + () -> { + if (callback != null) { + callback.onLocationPermissionDenied(activity.getString( + R.string.in_app_camera_location_permission_denied)); + } + }, + locationAccessDialog.dialogTitleResource, + locationAccessDialog.dialogTextResource); + } + + /** + * Check if apps have access to location even after having individual access + * + * @return + */ + public boolean isLocationAccessToAppsTurnedOn() { + return (locationManager.isNetworkProviderEnabled() || locationManager.isGPSProviderEnabled()); + } + + /** + * Ask user to grant location access to apps + * + */ + + private void showLocationOffDialog(Dialog locationOffDialog) { + DialogUtil + .showAlertDialog(activity, + activity.getString(locationOffDialog.dialogTitleResource), + activity.getString(locationOffDialog.dialogTextResource), + activity.getString(R.string.title_app_shortcut_setting), + activity.getString(R.string.cancel), + () -> openLocationSettings(), + () -> callback.onLocationPermissionDenied(activity.getString( + R.string.in_app_camera_location_unavailable))); + } + + /** + * Open location source settings so that apps with location access can access it + * + * TODO: modify it to fix https://github.com/commons-app/apps-android-commons/issues/5255 + */ + + private void openLocationSettings() { + final Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS); + final PackageManager packageManager = activity.getPackageManager(); + + if (intent.resolveActivity(packageManager)!= null) { + activity.startActivity(intent); + } + } + + /** + * Handle onPermissionDenied within individual classes based on the requirements + */ + public interface LocationPermissionCallback { + void onLocationPermissionDenied(String toastMessage); + void onLocationPermissionGranted(); + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java index b84c4b447c..8b7cf26c3b 100644 --- a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java @@ -362,15 +362,12 @@ public void run() { @OnClick(R.id.mediaDetailImageViewSpacer) public void launchZoomActivity(final View view) { - final boolean permission = PermissionUtils. - hasPermission(getActivity(), Manifest.permission.READ_EXTERNAL_STORAGE); - - if (permission) { + final boolean hasPermission = PermissionUtils.hasPermission(getActivity(), PermissionUtils.PERMISSIONS_STORAGE); + if (hasPermission) { launchZoomActivityAfterPermissionCheck(view); - } - else { + } else { PermissionUtils.checkPermissionsAndPerformAction(getActivity(), - Manifest.permission.READ_EXTERNAL_STORAGE, + PermissionUtils.PERMISSIONS_STORAGE, () -> { launchZoomActivityAfterPermissionCheck(view); }, @@ -1076,7 +1073,7 @@ public void onActivityResult(final int requestCode, final int resultCode, } else if (requestCode == REQUEST_CODE && resultCode == RESULT_CANCELED) { viewUtil.showShortToast(getContext(), - Objects.requireNonNull(getContext()) + requireContext() .getString(R.string.coordinates_picking_unsuccessful)); } else if (requestCode == REQUEST_CODE_EDIT_DESCRIPTION && resultCode == RESULT_CANCELED) { diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java index 3195796c5b..c2fdab7020 100644 --- a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java @@ -60,7 +60,7 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple private static CompositeDisposable compositeDisposable = new CompositeDisposable(); @BindView(R.id.mediaDetailsPager) ViewPager pager; - private Boolean editable; + private boolean editable; private boolean isFeaturedImage; private boolean isWikipediaButtonDisplayed; MediaDetailAdapter adapter; @@ -78,22 +78,31 @@ public ArrayList getRemovedItems() { return removedItems; } - private MediaDetailPagerFragment() {}; // Constructor calls made to be explicit - @SuppressLint("ValidFragment") - public MediaDetailPagerFragment(Boolean editable, boolean isFeaturedImage) { - this.editable = editable; - this.isFeaturedImage = isFeaturedImage; - isFromFeaturedRootFragment = false; - } - @SuppressLint("ValidFragment") - public MediaDetailPagerFragment(Boolean editable, boolean isFeaturedImage, int position) { - this.editable = editable; - this.isFeaturedImage = isFeaturedImage; - isFromFeaturedRootFragment = true; - this.position = position; + /** + * Use this factory method to create a new instance of this fragment using the provided + * parameters. + * + * This method will create a new instance of MediaDetailPagerFragment and the arguments will be + * saved to a bundle which will be later available in the {@link #onCreate(Bundle)} + * @param editable + * @param isFeaturedImage + * @return + */ + public static MediaDetailPagerFragment newInstance(boolean editable, boolean isFeaturedImage) { + MediaDetailPagerFragment mediaDetailPagerFragment = new MediaDetailPagerFragment(); + Bundle args = new Bundle(); + args.putBoolean("is_editable", editable); + args.putBoolean("is_featured_image", isFeaturedImage); + mediaDetailPagerFragment.setArguments(args); + return mediaDetailPagerFragment; } + public MediaDetailPagerFragment() { + // Required empty public constructor + }; + + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, @@ -147,8 +156,11 @@ public void onSaveInstanceState(Bundle outState) { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (savedInstanceState != null) { - editable = savedInstanceState.getBoolean("editable"); - isFeaturedImage = savedInstanceState.getBoolean("isFeaturedImage"); + editable = savedInstanceState.getBoolean("editable", false); + isFeaturedImage = savedInstanceState.getBoolean("isFeaturedImage", false); + if(null != pager) { + pager.setCurrentItem(savedInstanceState.getInt("current-page", 0), false); + } } setHasOptionsMenu(true); initProvider(); diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.java b/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.java index 418880b0af..ba78d041c5 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.java @@ -441,7 +441,7 @@ private void addCheckBoxCallback() { private void performMapReadyActions() { if (((MainActivity)getActivity()).activeFragment == ActiveFragment.NEARBY && isMapBoxReady) { if(!applicationKvStore.getBoolean("doNotAskForLocationPermission", false) || - PermissionUtils.hasPermission(getActivity(), Manifest.permission.ACCESS_FINE_LOCATION)){ + PermissionUtils.hasPermission(getActivity(), new String[]{Manifest.permission.ACCESS_FINE_LOCATION})){ checkPermissionsAndPerformAction(); }else{ isPermissionDenied = true; @@ -1215,8 +1215,8 @@ public void setTabItemContributions() { public void checkPermissionsAndPerformAction() { Timber.d("Checking permission and perfoming action"); PermissionUtils.checkPermissionsAndPerformAction(getActivity(), - Manifest.permission.ACCESS_FINE_LOCATION, - () -> locationPermissionGranted(), + new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, + this::locationPermissionGranted, () -> isPermissionDenied = true, R.string.location_permission_title, R.string.location_permission_rationale_nearby); diff --git a/app/src/main/java/fr/free/nrw/commons/repository/UploadRepository.java b/app/src/main/java/fr/free/nrw/commons/repository/UploadRepository.java index 007e89cb20..919c8d2cfc 100644 --- a/app/src/main/java/fr/free/nrw/commons/repository/UploadRepository.java +++ b/app/src/main/java/fr/free/nrw/commons/repository/UploadRepository.java @@ -188,9 +188,9 @@ public int getCount() { * @return */ public Observable preProcessImage(UploadableFile uploadableFile, Place place, - SimilarImageInterface similarImageInterface) { + SimilarImageInterface similarImageInterface, LatLng inAppPictureLocation) { return uploadModel.preProcessImage(uploadableFile, place, - similarImageInterface); + similarImageInterface, inAppPictureLocation); } /** @@ -199,8 +199,8 @@ public Observable preProcessImage(UploadableFile uploadableFile, Pla * @param uploadItem * @return */ - public Single getImageQuality(UploadItem uploadItem) { - return uploadModel.getImageQuality(uploadItem); + public Single getImageQuality(UploadItem uploadItem, LatLng location) { + return uploadModel.getImageQuality(uploadItem, location); } /** diff --git a/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.java b/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.java index 0846fa9dc4..a670545417 100644 --- a/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.java @@ -18,6 +18,7 @@ import android.widget.EditText; import android.widget.ListView; import android.widget.TextView; +import android.widget.Toast; import androidx.preference.ListPreference; import androidx.preference.MultiSelectListPreference; import androidx.preference.Preference; @@ -28,15 +29,21 @@ import androidx.preference.PreferenceViewHolder; import androidx.recyclerview.widget.RecyclerView.Adapter; import com.karumi.dexter.Dexter; +import com.karumi.dexter.MultiplePermissionsReport; +import com.karumi.dexter.PermissionToken; import com.karumi.dexter.listener.PermissionGrantedResponse; +import com.karumi.dexter.listener.PermissionRequest; +import com.karumi.dexter.listener.multi.MultiplePermissionsListener; import com.karumi.dexter.listener.single.BasePermissionListener; -import com.mapbox.mapboxsdk.Mapbox; import fr.free.nrw.commons.R; import fr.free.nrw.commons.Utils; import fr.free.nrw.commons.campaigns.CampaignView; import fr.free.nrw.commons.contributions.MainActivity; import fr.free.nrw.commons.di.ApplicationlessInjection; import fr.free.nrw.commons.kvstore.JsonKvStore; +import fr.free.nrw.commons.location.LocationPermissionsHelper; +import fr.free.nrw.commons.location.LocationPermissionsHelper.LocationPermissionCallback; +import fr.free.nrw.commons.location.LocationServiceManager; import fr.free.nrw.commons.logging.CommonsLogSender; import fr.free.nrw.commons.recentlanguages.Language; import fr.free.nrw.commons.recentlanguages.RecentLanguagesAdapter; @@ -65,6 +72,9 @@ public class SettingsFragment extends PreferenceFragmentCompat { @Inject RecentLanguagesDao recentLanguagesDao; + @Inject + LocationServiceManager locationManager; + private ListPreference themeListPreference; private Preference descriptionLanguageListPreference; private Preference appUiLanguageListPreference; @@ -97,6 +107,18 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { }); } + Preference inAppCameraLocationPref = findPreference("inAppCameraLocationPref"); + + inAppCameraLocationPref.setOnPreferenceChangeListener( + (preference, newValue) -> { + boolean isInAppCameraLocationTurnedOn = (boolean) newValue; + if (isInAppCameraLocationTurnedOn) { + createDialogsAndHandleLocationPermissions(getActivity()); + } + return true; + } + ); + // Gets current language code from shared preferences String languageCode; @@ -153,10 +175,10 @@ public boolean onPreferenceClick(Preference preference) { return true; }); - Preference getContentPickerPreference = findPreference("getContentPhotoPickerPref"); - getContentPickerPreference.setOnPreferenceChangeListener( + Preference documentBasedPickerPreference = findPreference("openDocumentPhotoPickerPref"); + documentBasedPickerPreference.setOnPreferenceChangeListener( (preference, newValue) -> { - boolean isGetContentPickerTurnedOn = (boolean) newValue; + boolean isGetContentPickerTurnedOn = !(boolean) newValue; if (isGetContentPickerTurnedOn) { showLocationLossWarning(); } @@ -172,9 +194,45 @@ public boolean onPreferenceClick(Preference preference) { findPreference("displayLocationPermissionForCardView").setEnabled(false); findPreference(CampaignView.CAMPAIGNS_DEFAULT_PREFERENCE).setEnabled(false); findPreference("managed_exif_tags").setEnabled(false); + findPreference("openDocumentPhotoPickerPref").setEnabled(false); + findPreference("inAppCameraLocationPref").setEnabled(false); } } + /** + * Asks users to provide location access + * + * @param activity + */ + private void createDialogsAndHandleLocationPermissions(Activity activity) { + LocationPermissionsHelper.Dialog locationAccessDialog = new LocationPermissionsHelper.Dialog( + R.string.location_permission_title, + R.string.in_app_camera_location_permission_rationale + ); + + LocationPermissionsHelper.Dialog locationOffDialog = new LocationPermissionsHelper.Dialog( + R.string.ask_to_turn_location_on, + R.string.in_app_camera_needs_location + ); + + LocationPermissionsHelper locationPermissionsHelper = new LocationPermissionsHelper( + activity, locationManager, new LocationPermissionCallback() { + @Override + public void onLocationPermissionDenied(String toastMessage) { + // dismiss the dialog + } + + @Override + public void onLocationPermissionGranted() { + // dismiss the dialog + } + }); + locationPermissionsHelper.handleLocationPermissions( + locationAccessDialog, + locationOffDialog + ); + } + /** * On some devices, the new Photo Picker with GET_CONTENT takeover * redacts location tags from EXIF metadata @@ -317,7 +375,7 @@ public void onItemClick(AdapterView adapterView, View view, int i, Locale defLocale = new Locale(languageCode); if(keyListPreference.equals("appUiDefaultLanguagePref")) { appUiLanguageListPreference.setSummary(defLocale.getDisplayLanguage(defLocale)); - setLocale(Objects.requireNonNull(getActivity()), languageCode); + setLocale(requireActivity(), languageCode); getActivity().recreate(); final Intent intent = new Intent(getActivity(), MainActivity.class); startActivity(intent); @@ -356,8 +414,8 @@ private void setUpRecentLanguagesSection(List recentLanguages, separator.setVisibility(View.VISIBLE); final RecentLanguagesAdapter recentLanguagesAdapter = new RecentLanguagesAdapter( - getActivity(), - recentLanguagesDao.getRecentLanguages(), + getActivity(), + recentLanguagesDao.getRecentLanguages(), selectedLanguages); languageHistoryListView.setAdapter(recentLanguagesAdapter); } @@ -382,7 +440,7 @@ private void onRecentLanguageClicked(String keyListPreference, Dialog dialog, Ad final Locale defLocale = new Locale(recentLanguageCode); if (keyListPreference.equals("appUiDefaultLanguagePref")) { appUiLanguageListPreference.setSummary(defLocale.getDisplayLanguage(defLocale)); - setLocale(Objects.requireNonNull(getActivity()), recentLanguageCode); + setLocale(requireActivity(), recentLanguageCode); getActivity().recreate(); final Intent intent = new Intent(getActivity(), MainActivity.class); startActivity(intent); @@ -452,7 +510,7 @@ private String getCurrentLanguageCode(final String preferenceKey) { * First checks for external storage permissions and then sends logs via email */ private void checkPermissionsAndSendLogs() { - if (PermissionUtils.hasPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE)) { + if (PermissionUtils.hasPermission(getActivity(), PermissionUtils.PERMISSIONS_STORAGE)) { commonsLogSender.send(getActivity(), null); } else { requestExternalStoragePermissions(); @@ -460,16 +518,26 @@ private void checkPermissionsAndSendLogs() { } /** - * Requests external storage permissions and shows a toast stating that log collection has started + * Requests external storage permissions and shows a toast stating that log collection has + * started */ private void requestExternalStoragePermissions() { Dexter.withActivity(getActivity()) - .withPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) - .withListener(new BasePermissionListener() { - @Override - public void onPermissionGranted(PermissionGrantedResponse response) { - ViewUtil.showLongToast(getActivity(), getResources().getString(R.string.log_collection_started)); - } - }).check(); + .withPermissions(PermissionUtils.PERMISSIONS_STORAGE) + .withListener(new MultiplePermissionsListener() { + @Override + public void onPermissionsChecked(MultiplePermissionsReport report) { + ViewUtil.showLongToast(getActivity(), + getResources().getString(R.string.log_collection_started)); + } + + @Override + public void onPermissionRationaleShouldBeShown( + List permissions, PermissionToken token) { + + } + }) + .onSameThread() + .check(); } } diff --git a/app/src/main/java/fr/free/nrw/commons/upload/FileProcessor.kt b/app/src/main/java/fr/free/nrw/commons/upload/FileProcessor.kt index 3d0c62236c..4bd4797c42 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/FileProcessor.kt +++ b/app/src/main/java/fr/free/nrw/commons/upload/FileProcessor.kt @@ -6,6 +6,7 @@ import android.net.Uri import androidx.exifinterface.media.ExifInterface import fr.free.nrw.commons.R import fr.free.nrw.commons.kvstore.JsonKvStore +import fr.free.nrw.commons.location.LatLng import fr.free.nrw.commons.mwapi.CategoryApi import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient import fr.free.nrw.commons.settings.Prefs @@ -48,7 +49,8 @@ class FileProcessor @Inject constructor( /** * Processes filePath coordinates, either from EXIF data or user location */ - fun processFileCoordinates(similarImageInterface: SimilarImageInterface, filePath: String?) + fun processFileCoordinates(similarImageInterface: SimilarImageInterface, + filePath: String?, inAppPictureLocation: LatLng?) : ImageCoordinates { val exifInterface: ExifInterface? = try { ExifInterface(filePath!!) @@ -59,7 +61,7 @@ class FileProcessor @Inject constructor( // Redact EXIF data as indicated in preferences. redactExifTags(exifInterface, getExifTagsToRedact()) Timber.d("Calling GPSExtractor") - val originalImageCoordinates = ImageCoordinates(exifInterface) + val originalImageCoordinates = ImageCoordinates(exifInterface, inAppPictureLocation) if (originalImageCoordinates.decimalCoords == null) { //Find other photos taken around the same time which has gps coordinates findOtherImages( @@ -156,11 +158,13 @@ class FileProcessor @Inject constructor( private fun readImageCoordinates(file: File) = try { - ImageCoordinates(contentResolver.openInputStream(Uri.fromFile(file))!!) + /* Used null location as location for similar images captured before is not available + in case it is not present in the EXIF. */ + ImageCoordinates(contentResolver.openInputStream(Uri.fromFile(file))!!, null) } catch (e: IOException) { Timber.e(e) try { - ImageCoordinates(file.absolutePath) + ImageCoordinates(file.absolutePath, null) } catch (ex: IOException) { Timber.e(ex) null diff --git a/app/src/main/java/fr/free/nrw/commons/upload/FileUtils.java b/app/src/main/java/fr/free/nrw/commons/upload/FileUtils.java index 10e1577055..f2c22fa9b8 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/FileUtils.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/FileUtils.java @@ -7,6 +7,7 @@ import androidx.exifinterface.media.ExifInterface; +import fr.free.nrw.commons.location.LatLng; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; @@ -64,11 +65,11 @@ static String getSHA1(InputStream is) { /** * Get Geolocation of filePath from input filePath path */ - static String getGeolocationOfFile(String filePath) { + static String getGeolocationOfFile(String filePath, LatLng inAppPictureLocation) { try { ExifInterface exifInterface = new ExifInterface(filePath); - ImageCoordinates imageObj = new ImageCoordinates(exifInterface); + ImageCoordinates imageObj = new ImageCoordinates(exifInterface, inAppPictureLocation); if (imageObj.getDecimalCoords() != null) { // If image has geolocation information in its EXIF return imageObj.getDecimalCoords(); } else { diff --git a/app/src/main/java/fr/free/nrw/commons/upload/FileUtilsWrapper.java b/app/src/main/java/fr/free/nrw/commons/upload/FileUtilsWrapper.java index 51bbd02bff..14154a66db 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/FileUtilsWrapper.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/FileUtilsWrapper.java @@ -1,6 +1,7 @@ package fr.free.nrw.commons.upload; import android.content.Context; +import fr.free.nrw.commons.location.LatLng; import io.reactivex.Observable; import java.io.BufferedInputStream; import java.io.File; @@ -37,8 +38,8 @@ public FileInputStream getFileInputStream(String filePath) throws FileNotFoundEx return FileUtils.getFileInputStream(filePath); } - public String getGeolocationOfFile(String filePath) { - return FileUtils.getGeolocationOfFile(filePath); + public String getGeolocationOfFile(String filePath, LatLng inAppPictureLocation) { + return FileUtils.getGeolocationOfFile(filePath, inAppPictureLocation); } diff --git a/app/src/main/java/fr/free/nrw/commons/upload/ImageCoordinates.kt b/app/src/main/java/fr/free/nrw/commons/upload/ImageCoordinates.kt index 7486bb2358..3f2ab36a60 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/ImageCoordinates.kt +++ b/app/src/main/java/fr/free/nrw/commons/upload/ImageCoordinates.kt @@ -9,8 +9,10 @@ import java.io.InputStream /** * Extracts geolocation to be passed to API for category suggestions. If a picture with geolocation * is uploaded, extract latitude and longitude from EXIF data of image. + * Otherwise, if current user location is available while using the in-app camera, + * use it to set image coordinates */ -class ImageCoordinates internal constructor(exif: ExifInterface?) { +class ImageCoordinates internal constructor(exif: ExifInterface?, inAppPictureLocation: LatLng?) { var decLatitude = 0.0 var decLongitude = 0.0 var imageCoordsExists = false @@ -26,13 +28,13 @@ class ImageCoordinates internal constructor(exif: ExifInterface?) { /** * Construct from a stream. */ - internal constructor(stream: InputStream) : this(ExifInterface(stream)) + internal constructor(stream: InputStream, inAppPictureLocation: LatLng?) : this(ExifInterface(stream), inAppPictureLocation) /** * Construct from the file path of the image. * @param path file path of the image */ @Throws(IOException::class) - internal constructor(path: String) : this(ExifInterface(path)) + internal constructor(path: String, inAppPictureLocation: LatLng?) : this(ExifInterface(path), inAppPictureLocation) init { //If image has no EXIF data and user has enabled GPS setting, get user's location @@ -61,6 +63,14 @@ class ImageCoordinates internal constructor(exif: ExifInterface?) { //If image has EXIF data, extract image coords imageCoordsExists = true Timber.d("EXIF data has location info") + } else if (inAppPictureLocation != null) { + decLatitude = inAppPictureLocation.latitude + decLongitude = inAppPictureLocation.longitude + if (!(decLatitude == 0.0 && decLongitude == 0.0)) { + decimalCoords = "$decLatitude|$decLongitude" + imageCoordsExists = true + Timber.d("Image coordinates recorded while using in-app camera") + } } } } diff --git a/app/src/main/java/fr/free/nrw/commons/upload/ImageProcessingService.java b/app/src/main/java/fr/free/nrw/commons/upload/ImageProcessingService.java index 1f31c1690d..6aa637ee54 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/ImageProcessingService.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/ImageProcessingService.java @@ -5,6 +5,7 @@ import static fr.free.nrw.commons.utils.ImageUtils.IMAGE_OK; import android.content.Context; +import fr.free.nrw.commons.location.LatLng; import fr.free.nrw.commons.media.MediaClient; import fr.free.nrw.commons.nearby.Place; import fr.free.nrw.commons.utils.ImageUtils; @@ -46,7 +47,7 @@ public ImageProcessingService(FileUtilsWrapper fileUtilsWrapper, * Check image quality before upload - checks duplicate image - checks dark image - checks * geolocation for image - check for valid title */ - Single validateImage(UploadItem uploadItem) { + Single validateImage(UploadItem uploadItem, LatLng inAppPictureLocation) { int currentImageQuality = uploadItem.getImageQuality(); Timber.d("Current image quality is %d", currentImageQuality); if (currentImageQuality == ImageUtils.IMAGE_KEEP) { @@ -57,7 +58,7 @@ Single validateImage(UploadItem uploadItem) { return Single.zip( checkDuplicateImage(filePath), - checkImageGeoLocation(uploadItem.getPlace(), filePath), + checkImageGeoLocation(uploadItem.getPlace(), filePath, inAppPictureLocation), checkDarkImage(filePath), validateItemTitle(uploadItem), checkFBMD(filePath), @@ -148,13 +149,13 @@ private Single checkDarkImage(String filePath) { * @param filePath file to be checked * @return IMAGE_GEOLOCATION_DIFFERENT or IMAGE_OK */ - private Single checkImageGeoLocation(Place place, String filePath) { + private Single checkImageGeoLocation(Place place, String filePath, LatLng inAppPictureLocation) { Timber.d("Checking for image geolocation %s", filePath); if (place == null || StringUtils.isBlank(place.getWikiDataEntityId())) { return Single.just(ImageUtils.IMAGE_OK); } return Single.fromCallable(() -> filePath) - .map(fileUtilsWrapper::getGeolocationOfFile) + .flatMap(path -> Single.just(fileUtilsWrapper.getGeolocationOfFile(path, inAppPictureLocation))) .flatMap(geoLocation -> { if (StringUtils.isBlank(geoLocation)) { return Single.just(ImageUtils.IMAGE_OK); diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.java b/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.java index 7fd4ecce7f..ab6fa70358 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.java @@ -1,21 +1,24 @@ package fr.free.nrw.commons.upload; import static fr.free.nrw.commons.contributions.ContributionController.ACTION_INTERNAL_UPLOADS; -import static fr.free.nrw.commons.upload.UploadPresenter.COUNTER_OF_CONSECUTIVE_UPLOADS_WITHOUT_COORDINATES; +import static fr.free.nrw.commons.utils.PermissionUtils.PERMISSIONS_STORAGE; import static fr.free.nrw.commons.wikidata.WikidataConstants.PLACE_OBJECT; import android.Manifest; import android.annotation.SuppressLint; import android.app.ProgressDialog; import android.content.Intent; +import android.location.Location; +import android.location.LocationManager; +import android.os.Build; import android.os.Bundle; +import android.provider.Settings; import android.util.DisplayMetrics; import android.view.View; import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.TextView; -import androidx.appcompat.app.AlertDialog; import androidx.cardview.widget.CardView; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; @@ -25,8 +28,6 @@ import androidx.viewpager.widget.PagerAdapter; import androidx.viewpager.widget.ViewPager; import androidx.work.ExistingWorkPolicy; -import androidx.work.OneTimeWorkRequest; -import androidx.work.WorkManager; import butterknife.BindView; import butterknife.ButterKnife; import butterknife.OnClick; @@ -35,28 +36,33 @@ import fr.free.nrw.commons.auth.LoginActivity; import fr.free.nrw.commons.auth.SessionManager; import fr.free.nrw.commons.contributions.ContributionController; -import fr.free.nrw.commons.contributions.MainActivity; import fr.free.nrw.commons.filepicker.UploadableFile; import fr.free.nrw.commons.kvstore.JsonKvStore; +import fr.free.nrw.commons.location.LatLng; +import fr.free.nrw.commons.location.LocationPermissionsHelper; +import fr.free.nrw.commons.location.LocationServiceManager; import fr.free.nrw.commons.mwapi.UserClient; import fr.free.nrw.commons.nearby.Place; +import fr.free.nrw.commons.settings.Prefs; import fr.free.nrw.commons.theme.BaseActivity; import fr.free.nrw.commons.upload.categories.UploadCategoriesFragment; import fr.free.nrw.commons.upload.depicts.DepictsFragment; import fr.free.nrw.commons.upload.license.MediaLicenseFragment; import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment; import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.UploadMediaDetailFragmentCallback; -import fr.free.nrw.commons.upload.worker.UploadWorker; +import fr.free.nrw.commons.upload.worker.WorkRequestHelper; import fr.free.nrw.commons.utils.DialogUtil; import fr.free.nrw.commons.utils.PermissionUtils; import fr.free.nrw.commons.utils.ViewUtil; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.CompositeDisposable; import io.reactivex.schedulers.Schedulers; +import java.security.Permission; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; +import java.util.Set; import javax.inject.Inject; import javax.inject.Named; import timber.log.Timber; @@ -74,6 +80,8 @@ public class UploadActivity extends BaseActivity implements UploadContract.View, SessionManager sessionManager; @Inject UserClient userClient; + @Inject + LocationServiceManager locationManager; @BindView(R.id.cv_container_top_card) @@ -109,6 +117,9 @@ public class UploadActivity extends BaseActivity implements UploadContract.View, private ThumbnailsAdapter thumbnailsAdapter; private Place place; + private LatLng prevLocation; + private LatLng currLocation; + private boolean isInAppCameraUpload; private List uploadableFiles = Collections.emptyList(); private int currentSelectedPosition = 0; /* @@ -117,6 +128,8 @@ public class UploadActivity extends BaseActivity implements UploadContract.View, private boolean isMultipleFilesSelected = false; public static final String EXTRA_FILES = "commons_image_exta"; + public static final String LOCATION_BEFORE_IMAGE_CAPTURE = "user_location_before_image_capture"; + public static final String IN_APP_CAMERA_UPLOAD = "in_app_camera_upload"; /** * Stores all nearby places found and related users response for @@ -135,12 +148,11 @@ protected void onCreate(Bundle savedInstanceState) { compositeDisposable = new CompositeDisposable(); init(); nearbyPopupAnswers = new HashMap<>(); - PermissionUtils.checkPermissionsAndPerformAction(this, - Manifest.permission.WRITE_EXTERNAL_STORAGE, - this::receiveSharedItems, - R.string.storage_permission_title, - R.string.write_storage_permission_rationale_for_image_share); + PERMISSIONS_STORAGE, + this::receiveSharedItems, + R.string.storage_permission_title, + R.string.write_storage_permission_rationale_for_image_share); //getting the current dpi of the device and if it is less than 320dp i.e. overlapping //threshold, thumbnails automatically minimizes DisplayMetrics metrics = getResources().getDisplayMetrics(); @@ -148,6 +160,11 @@ protected void onCreate(Bundle savedInstanceState) { if (dpi<=321) { onRlContainerTitleClicked(); } + if (PermissionUtils.hasPermission(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION})) { + locationManager.registerLocationManager(); + } + locationManager.requestLocationUpdatesFromProvider(LocationManager.GPS_PROVIDER); + locationManager.requestLocationUpdatesFromProvider(LocationManager.NETWORK_PROVIDER); } private void init() { @@ -165,7 +182,7 @@ private void initProgressDialog() { private void initThumbnailsRecyclerView() { rvThumbnails.setLayoutManager(new LinearLayoutManager(this, - LinearLayoutManager.HORIZONTAL, false)); + LinearLayoutManager.HORIZONTAL, false)); thumbnailsAdapter = new ThumbnailsAdapter(() -> currentSelectedPosition); rvThumbnails.setAdapter(thumbnailsAdapter); @@ -177,7 +194,7 @@ private void initViewPager() { vpUpload.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { @Override public void onPageScrolled(int position, float positionOffset, - int positionOffsetPixels) { + int positionOffsetPixels) { } @@ -222,29 +239,31 @@ protected void onResume() { */ protected void checkBlockStatus() { compositeDisposable.add(userClient.isUserBlockedFromCommons() - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .filter(result -> result) - .subscribe(result -> DialogUtil.showAlertDialog( - this, - getString(R.string.block_notification_title), - getString(R.string.block_notification), - getString(R.string.ok), - this::finish, - true))); + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .filter(result -> result) + .subscribe(result -> DialogUtil.showAlertDialog( + this, + getString(R.string.block_notification_title), + getString(R.string.block_notification), + getString(R.string.ok), + this::finish, + true))); } private void checkStoragePermissions() { - PermissionUtils.checkPermissionsAndPerformAction(this, - Manifest.permission.WRITE_EXTERNAL_STORAGE, + final boolean hasAllPermissions = PermissionUtils.hasPermission(this, PERMISSIONS_STORAGE); + if (!hasAllPermissions) { + PermissionUtils.checkPermissionsAndPerformAction(this, + PERMISSIONS_STORAGE, () -> { //TODO handle this }, R.string.storage_permission_title, R.string.write_storage_permission_rationale_for_image_share); + } } - @Override protected void onStop() { super.onStop(); @@ -312,14 +331,13 @@ public void onUploadMediaDeleted(int index) { @Override public void updateTopCardTitle() { tvTopCardTitle.setText(getResources() - .getQuantityString(R.plurals.upload_count_title, uploadableFiles.size(), uploadableFiles.size())); + .getQuantityString(R.plurals.upload_count_title, uploadableFiles.size(), uploadableFiles.size())); } @Override public void makeUploadRequest() { - WorkManager.getInstance(getApplicationContext()).enqueueUniqueWork( - UploadWorker.class.getSimpleName(), - ExistingWorkPolicy.APPEND_OR_REPLACE, OneTimeWorkRequest.from(UploadWorker.class)); + WorkRequestHelper.Companion.makeOneTimeWorkRequest(getApplicationContext(), + ExistingWorkPolicy.APPEND_OR_REPLACE); } @Override @@ -355,18 +373,76 @@ private void receiveSharedItems() { } else { //Show thumbnails if (uploadableFiles.size() - > 1) {//If there is only file, no need to show the image thumbnails + > 1) {//If there is only file, no need to show the image thumbnails thumbnailsAdapter.setUploadableFiles(uploadableFiles); } else { llContainerTopCard.setVisibility(View.GONE); } tvTopCardTitle.setText(getResources() - .getQuantityString(R.plurals.upload_count_title, uploadableFiles.size(), uploadableFiles.size())); + .getQuantityString(R.plurals.upload_count_title, uploadableFiles.size(), uploadableFiles.size())); fragments = new ArrayList<>(); + /* Suggest users to turn battery optimisation off when uploading more than a few files. + That's because we have noticed that many-files uploads have + a much higher probability of failing than uploads with less files. + + Show the dialog for Android 6 and above as + the ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS intent was added in API level 23 + */ + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (uploadableFiles.size() > 3 + && !defaultKvStore.getBoolean("hasAlreadyLaunchedBigMultiupload")) { + DialogUtil.showAlertDialog( + this, + getString(R.string.unrestricted_battery_mode), + getString(R.string.suggest_unrestricted_mode), + getString(R.string.title_activity_settings), + getString(R.string.cancel), + () -> { + /* Since opening the right settings page might be device dependent, using + https://github.com/WaseemSabir/BatteryPermissionHelper + directly appeared like a promising idea. + However, this simply closed the popup and did not make + the settings page appear on a Pixel as well as a Xiaomi device. + + Used the standard intent instead of using this library as + it shows a list of all the apps on the device and allows users to + turn battery optimisation off. + */ + Intent batteryOptimisationSettingsIntent = new Intent( + Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS); + startActivity(batteryOptimisationSettingsIntent); + }, + () -> {} + ); + defaultKvStore.putBoolean("hasAlreadyLaunchedBigMultiupload", true); + } + } for (UploadableFile uploadableFile : uploadableFiles) { UploadMediaDetailFragment uploadMediaDetailFragment = new UploadMediaDetailFragment(); - uploadMediaDetailFragment.setImageTobeUploaded(uploadableFile, place); + + LocationPermissionsHelper locationPermissionsHelper = new LocationPermissionsHelper( + this, locationManager, null); + if (locationPermissionsHelper.isLocationAccessToAppsTurnedOn()) { + currLocation = locationManager.getLastLocation(); + } + + if (currLocation != null) { + float locationDifference = getLocationDifference(currLocation, prevLocation); + boolean isLocationTagUnchecked = isLocationTagUncheckedInTheSettings(); + /* Remove location if the user has unchecked the Location EXIF tag in the + Manage EXIF Tags setting or turned "Record location for in-app shots" off. + Also, location information is discarded if the difference between + current location and location recorded just before capturing the image + is greater than 100 meters */ + if (isLocationTagUnchecked || locationDifference > 100 + || !defaultKvStore.getBoolean("inAppCameraLocationPref") + || !isInAppCameraUpload) { + currLocation = null; + } + } + uploadMediaDetailFragment.setImageTobeUploaded(uploadableFile, place, currLocation); + locationManager.unregisterLocationManager(); uploadMediaDetailFragment.setCallback(new UploadMediaDetailFragmentCallback() { @Override public void deletePictureAtIndex(int index) { @@ -424,6 +500,39 @@ public boolean isWLMUpload() { } } + /** + * Users may uncheck Location tag from the Manage EXIF tags setting any time. + * So, their location must not be shared in this case. + * + * @return + */ + private boolean isLocationTagUncheckedInTheSettings() { + Set prefExifTags = defaultKvStore.getStringSet(Prefs.MANAGED_EXIF_TAGS); + if (prefExifTags.contains(getString(R.string.exif_tag_location))) { + return false; + } + return true; + } + + /** + * Calculate the difference between current location and + * location recorded before capturing the image + * + * @param currLocation + * @param prevLocation + * @return + */ + private float getLocationDifference(LatLng currLocation, LatLng prevLocation) { + if (prevLocation == null) { + return 0.0f; + } + float[] distance = new float[2]; + Location.distanceBetween( + currLocation.getLatitude(), currLocation.getLongitude(), + prevLocation.getLatitude(), prevLocation.getLongitude(), distance); + return distance[0]; + } + private void receiveExternalSharedItems() { uploadableFiles = contributionController.handleExternalImagesPicked(this, getIntent()); } @@ -438,6 +547,8 @@ private void receiveInternalSharedItems() { Timber.i("Received multiple upload %s", uploadableFiles.size()); place = intent.getParcelableExtra(PLACE_OBJECT); + prevLocation = intent.getParcelableExtra(LOCATION_BEFORE_IMAGE_CAPTURE); + isInAppCameraUpload = intent.getBooleanExtra(IN_APP_CAMERA_UPLOAD, false); resetDirectPrefs(); } @@ -562,5 +673,4 @@ public void onBackPressed() { this::finish ); } - } diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadModel.java b/app/src/main/java/fr/free/nrw/commons/upload/UploadModel.java index 369ce02449..9617e95c73 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/UploadModel.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadModel.java @@ -7,6 +7,7 @@ import fr.free.nrw.commons.contributions.Contribution; import fr.free.nrw.commons.filepicker.UploadableFile; import fr.free.nrw.commons.kvstore.JsonKvStore; +import fr.free.nrw.commons.location.LatLng; import fr.free.nrw.commons.nearby.Place; import fr.free.nrw.commons.settings.Prefs; import fr.free.nrw.commons.upload.depicts.DepictsFragment; @@ -86,18 +87,20 @@ public void setSelectedCategories(List selectedCategories) { */ public Observable preProcessImage(final UploadableFile uploadableFile, final Place place, - final SimilarImageInterface similarImageInterface) { + final SimilarImageInterface similarImageInterface, + LatLng inAppPictureLocation) { return Observable.just( - createAndAddUploadItem(uploadableFile, place, similarImageInterface)); + createAndAddUploadItem(uploadableFile, place, similarImageInterface, inAppPictureLocation)); } - public Single getImageQuality(final UploadItem uploadItem) { - return imageProcessingService.validateImage(uploadItem); + public Single getImageQuality(final UploadItem uploadItem, LatLng inAppPictureLocation) { + return imageProcessingService.validateImage(uploadItem, inAppPictureLocation); } private UploadItem createAndAddUploadItem(final UploadableFile uploadableFile, final Place place, - final SimilarImageInterface similarImageInterface) { + final SimilarImageInterface similarImageInterface, + LatLng inAppPictureLocation) { final UploadableFile.DateTimeWithSource dateTimeWithSource = uploadableFile .getFileCreatedDate(context); long fileCreatedDate = -1; @@ -110,7 +113,8 @@ private UploadItem createAndAddUploadItem(final UploadableFile uploadableFile, } Timber.d("File created date is %d", fileCreatedDate); final ImageCoordinates imageCoordinates = fileProcessor - .processFileCoordinates(similarImageInterface, uploadableFile.getFilePath()); + .processFileCoordinates(similarImageInterface, uploadableFile.getFilePath(), + inAppPictureLocation); final UploadItem uploadItem = new UploadItem( Uri.parse(uploadableFile.getFilePath()), uploadableFile.getMimeType(context), imageCoordinates, place, fileCreatedDate, diff --git a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.java b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.java index 3d1fb28626..cf6372ef4e 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.java @@ -36,6 +36,8 @@ import fr.free.nrw.commons.edit.EditActivity; import fr.free.nrw.commons.filepicker.UploadableFile; import fr.free.nrw.commons.kvstore.JsonKvStore; +import fr.free.nrw.commons.location.LatLng; +import fr.free.nrw.commons.location.LocationServiceManager; import fr.free.nrw.commons.nearby.Place; import fr.free.nrw.commons.recentlanguages.RecentLanguagesDao; import fr.free.nrw.commons.settings.Prefs; @@ -127,6 +129,11 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements */ private Place nearbyPlace; private UploadItem uploadItem; + /** + * inAppPictureLocation: use location recorded while using the in-app camera if + * device camera does not record it in the EXIF + */ + private LatLng inAppPictureLocation; /** * editableUploadItem : Storing the upload item before going to update the coordinates */ @@ -143,9 +150,10 @@ public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); } - public void setImageTobeUploaded(UploadableFile uploadableFile, Place place) { + public void setImageTobeUploaded(UploadableFile uploadableFile, Place place, LatLng inAppPictureLocation) { this.uploadableFile = uploadableFile; this.place = place; + this.inAppPictureLocation = inAppPictureLocation; } @Nullable @@ -170,7 +178,7 @@ private void init() { tooltip.setOnClickListener( v -> showInfoAlert(R.string.media_detail_step_title, R.string.media_details_tooltip)); initPresenter(); - presenter.receiveImage(uploadableFile, place); + presenter.receiveImage(uploadableFile, place, inAppPictureLocation); initRecyclerView(); if (callback.getIndexInViewFlipper(this) == 0) { @@ -235,7 +243,7 @@ private void showInfoAlert(int titleStringID, int messageStringId) { @OnClick(R.id.btn_next) public void onNextButtonClicked() { - presenter.verifyImageQuality(callback.getIndexInViewFlipper(this)); + presenter.verifyImageQuality(callback.getIndexInViewFlipper(this), inAppPictureLocation); } @OnClick(R.id.btn_previous) @@ -489,6 +497,9 @@ private void goToLocationPickerActivity(final UploadItem uploadItem) { double defaultLongitude = -122.431297; double defaultZoom = 16.0; + /* Retrieve image location from EXIF if present or + check if user has provided location while using the in-app camera. + Use location of last UploadItem if none of them is available */ if (uploadItem.getGpsCoords() != null && uploadItem.getGpsCoords() .getDecLatitude() != 0.0 && uploadItem.getGpsCoords().getDecLongitude() != 0.0) { defaultLatitude = uploadItem.getGpsCoords() diff --git a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailsContract.java b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailsContract.java index 3904c0ec28..a37e79ab9d 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailsContract.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailsContract.java @@ -2,6 +2,7 @@ import fr.free.nrw.commons.BasePresenter; import fr.free.nrw.commons.filepicker.UploadableFile; +import fr.free.nrw.commons.location.LatLng; import fr.free.nrw.commons.nearby.Place; import fr.free.nrw.commons.upload.ImageCoordinates; import fr.free.nrw.commons.upload.SimilarImageInterface; @@ -45,9 +46,9 @@ interface View extends SimilarImageInterface { interface UserActionListener extends BasePresenter { - void receiveImage(UploadableFile uploadableFile, Place place); + void receiveImage(UploadableFile uploadableFile, Place place, LatLng inAppPictureLocation); - void verifyImageQuality(int uploadItemIndex); + void verifyImageQuality(int uploadItemIndex, LatLng inAppPictureLocation); void copyTitleAndDescriptionToSubsequentMedia(int indexInViewFlipper); diff --git a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaPresenter.java b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaPresenter.java index 7ec6ea819c..3b53390352 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaPresenter.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaPresenter.java @@ -87,11 +87,12 @@ public void onDetachView() { * @param place */ @Override - public void receiveImage(final UploadableFile uploadableFile, final Place place) { + public void receiveImage(final UploadableFile uploadableFile, final Place place, + LatLng inAppPictureLocation) { view.showProgress(true); compositeDisposable.add( repository - .preProcessImage(uploadableFile, place, this) + .preProcessImage(uploadableFile, place, this, inAppPictureLocation) .map(uploadItem -> { if(place!=null && place.isMonument()){ if (place.location != null) { @@ -177,15 +178,15 @@ private void checkNearbyPlaces(final UploadItem uploadItem) { * @param uploadItemIndex */ @Override - public void verifyImageQuality(int uploadItemIndex) { + public void verifyImageQuality(int uploadItemIndex, LatLng inAppPictureLocation) { final UploadItem uploadItem = repository.getUploads().get(uploadItemIndex); - if (uploadItem.getGpsCoords().getDecimalCoords() == null) { + if (uploadItem.getGpsCoords().getDecimalCoords() == null && inAppPictureLocation == null) { final Runnable onSkipClicked = () -> { view.showProgress(true); compositeDisposable.add( repository - .getImageQuality(uploadItem) + .getImageQuality(uploadItem, inAppPictureLocation) .observeOn(mainThreadScheduler) .subscribe(imageResult -> { view.showProgress(false); @@ -208,7 +209,7 @@ public void verifyImageQuality(int uploadItemIndex) { view.showProgress(true); compositeDisposable.add( repository - .getImageQuality(uploadItem) + .getImageQuality(uploadItem, inAppPictureLocation) .observeOn(mainThreadScheduler) .subscribe(imageResult -> { view.showProgress(false); diff --git a/app/src/main/java/fr/free/nrw/commons/upload/worker/UploadWorker.kt b/app/src/main/java/fr/free/nrw/commons/upload/worker/UploadWorker.kt index 6a4497ea15..c26243762f 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/worker/UploadWorker.kt +++ b/app/src/main/java/fr/free/nrw/commons/upload/worker/UploadWorker.kt @@ -1,17 +1,20 @@ package fr.free.nrw.commons.upload.worker import android.annotation.SuppressLint +import android.app.Notification import android.app.PendingIntent import android.app.TaskStackBuilder import android.content.Context import android.content.Intent import android.graphics.BitmapFactory +import android.os.Build import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import androidx.work.CoroutineWorker import androidx.work.Data import androidx.work.WorkerParameters import androidx.multidex.BuildConfig +import androidx.work.ForegroundInfo import dagger.android.ContributesAndroidInjector import fr.free.nrw.commons.CommonsApplication import fr.free.nrw.commons.Media @@ -40,6 +43,7 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import timber.log.Timber +import java.net.SocketTimeoutException import java.util.* import java.util.regex.Pattern import javax.inject.Inject @@ -161,6 +165,8 @@ class UploadWorker(var appContext: Context, workerParams: WorkerParameters) : override suspend fun doWork(): Result { var countUpload = 0 + // Start a foreground service + setForeground(createForegroundInfo()) notificationManager = NotificationManagerCompat.from(appContext) val processingUploads = getNotificationBuilder( CommonsApplication.NOTIFICATION_CHANNEL_ID_ALL @@ -170,15 +176,15 @@ class UploadWorker(var appContext: Context, workerParams: WorkerParameters) : .blockingGet() //Showing initial notification for the number of uploads being processed - Timber.e("Queued Contributions: "+ queuedContributions.size) + Timber.e("Queued Contributions: " + queuedContributions.size) processingUploads.setContentTitle(appContext.getString(R.string.starting_uploads)) processingUploads.setContentText( appContext.resources.getQuantityString( - R.plurals.starting_multiple_uploads, - queuedContributions.size, - queuedContributions.size - ) + R.plurals.starting_multiple_uploads, + queuedContributions.size, + queuedContributions.size + ) ) notificationManager?.notify( PROCESSING_UPLOADS_NOTIFICATION_TAG, @@ -191,7 +197,7 @@ class UploadWorker(var appContext: Context, workerParams: WorkerParameters) : so that the next one does not process these contribution again */ queuedContributions.forEach { - it.state=Contribution.STATE_IN_PROGRESS + it.state = Contribution.STATE_IN_PROGRESS contributionDao.saveSynchronous(it) } @@ -223,10 +229,37 @@ class UploadWorker(var appContext: Context, workerParams: WorkerParameters) : PROCESSING_UPLOADS_NOTIFICATION_ID ) } - //TODO make this smart, think of handling retries in the future + // Trigger WorkManager to process any new contributions that may have been added to the queue + val updatedContributionQueue = withContext(Dispatchers.IO) { + contributionDao.getContribution(statesToProcess).blockingGet() + } + if (updatedContributionQueue.isNotEmpty()) { + return Result.retry() + } + return Result.success() } + /** + * Create new notification for foreground service + */ + private fun createForegroundInfo(): ForegroundInfo { + return ForegroundInfo( + 1, + createNotificationForForegroundService() + ) + } + + override suspend fun getForegroundInfo(): ForegroundInfo { + return createForegroundInfo() + } + private fun createNotificationForForegroundService(): Notification { + // TODO: Improve notification for foreground service + return getNotificationBuilder( + CommonsApplication.NOTIFICATION_CHANNEL_ID_ALL)!! + .setContentTitle(appContext.getString(R.string.upload_in_progress)) + .build() + } /** * Returns true is the limited connection mode is enabled */ @@ -540,7 +573,12 @@ class UploadWorker(var appContext: Context, workerParams: WorkerParameters) : val intent = Intent(appContext,toClass) return TaskStackBuilder.create(appContext).run { addNextIntentWithParentStack(intent) - getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + getPendingIntent(0, + PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT) + } else { + getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT) + } }; } diff --git a/app/src/main/java/fr/free/nrw/commons/upload/worker/WorkRequestHelper.kt b/app/src/main/java/fr/free/nrw/commons/upload/worker/WorkRequestHelper.kt new file mode 100644 index 0000000000..9c0bbb6f42 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/upload/worker/WorkRequestHelper.kt @@ -0,0 +1,41 @@ +package fr.free.nrw.commons.upload.worker + +import android.content.Context +import androidx.work.* +import androidx.work.WorkRequest.Companion.MIN_BACKOFF_MILLIS +import java.util.concurrent.TimeUnit + +/** + * Helper class for all the one time work requests + */ +class WorkRequestHelper { + + companion object { + fun makeOneTimeWorkRequest(context: Context, existingWorkPolicy: ExistingWorkPolicy) { + /* Set backoff criteria for the work request + The default backoff policy is EXPONENTIAL, but while testing we found that it + too long for the uploads to finish. So, set the backoff policy as LINEAR with the + minimum backoff delay value of 10 seconds. + + More details on when exactly it is retried: + https://developer.android.com/guide/background/persistent/getting-started/define-work#retries_backoff + */ + val constraints: Constraints = Constraints.Builder() + .setRequiredNetworkType(NetworkType.CONNECTED) + .build() + val uploadRequest: OneTimeWorkRequest = + OneTimeWorkRequest.Builder(UploadWorker::class.java) + .setBackoffCriteria( + BackoffPolicy.LINEAR, + MIN_BACKOFF_MILLIS, + TimeUnit.MILLISECONDS + ) + .setConstraints(constraints) + .build() + WorkManager.getInstance(context).enqueueUniqueWork( + UploadWorker::class.java.simpleName, existingWorkPolicy, uploadRequest + ) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/utils/DownloadUtils.kt b/app/src/main/java/fr/free/nrw/commons/utils/DownloadUtils.kt index d7d5ae3aa8..480823d1f6 100644 --- a/app/src/main/java/fr/free/nrw/commons/utils/DownloadUtils.kt +++ b/app/src/main/java/fr/free/nrw/commons/utils/DownloadUtils.kt @@ -38,7 +38,7 @@ object DownloadUtils { } PermissionUtils.checkPermissionsAndPerformAction( activity, - permission.WRITE_EXTERNAL_STORAGE, + PermissionUtils.PERMISSIONS_STORAGE, { enqueueRequest(activity, req) }, { Toast.makeText( diff --git a/app/src/main/java/fr/free/nrw/commons/utils/PermissionUtils.java b/app/src/main/java/fr/free/nrw/commons/utils/PermissionUtils.java index 4ffbec99b5..b14a51f067 100644 --- a/app/src/main/java/fr/free/nrw/commons/utils/PermissionUtils.java +++ b/app/src/main/java/fr/free/nrw/commons/utils/PermissionUtils.java @@ -1,118 +1,140 @@ package fr.free.nrw.commons.utils; +import android.Manifest; import android.app.Activity; import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; +import android.os.Build; import android.provider.Settings; import androidx.annotation.StringRes; import androidx.core.content.ContextCompat; import com.karumi.dexter.Dexter; +import com.karumi.dexter.MultiplePermissionsReport; import com.karumi.dexter.PermissionToken; -import com.karumi.dexter.listener.PermissionDeniedResponse; -import com.karumi.dexter.listener.PermissionGrantedResponse; import com.karumi.dexter.listener.PermissionRequest; -import com.karumi.dexter.listener.single.BasePermissionListener; +import com.karumi.dexter.listener.multi.MultiplePermissionsListener; import fr.free.nrw.commons.CommonsApplication; import fr.free.nrw.commons.R; +import java.util.List; public class PermissionUtils { + public static String[] PERMISSIONS_STORAGE = isSDKVersionScopedStorageCompatible() ? + isSDKVersionTiramisu() ? new String[]{ + Manifest.permission.READ_MEDIA_IMAGES} : + new String[]{Manifest.permission.READ_EXTERNAL_STORAGE} + : isSDKVersionTiramisu() ? new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, + Manifest.permission.READ_MEDIA_IMAGES} + : new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, + Manifest.permission.READ_EXTERNAL_STORAGE}; + + private static boolean isSDKVersionScopedStorageCompatible() { + return Build.VERSION.SDK_INT > Build.VERSION_CODES.P; + } + + public static boolean isSDKVersionTiramisu() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU; + } + /** - * This method can be used by any activity which requires a permission which has been blocked(marked never ask again by the user) - It open the app settings from where the user can manually give us the required permission. + * This method can be used by any activity which requires a permission which has been + * blocked(marked never ask again by the user) It open the app settings from where the user can + * manually give us the required permission. + * * @param activity */ private static void askUserToManuallyEnablePermissionFromSettings(Activity activity) { Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); Uri uri = Uri.fromParts("package", activity.getPackageName(), null); intent.setData(uri); - activity.startActivityForResult(intent,CommonsApplication.OPEN_APPLICATION_DETAIL_SETTINGS); + activity.startActivityForResult(intent, + CommonsApplication.OPEN_APPLICATION_DETAIL_SETTINGS); } /** * Checks whether the app already has a particular permission * * @param activity - * @param permission permission to be checked + * @param permissions permissions to be checked * @return */ - public static boolean hasPermission(Activity activity, String permission) { - return ContextCompat.checkSelfPermission(activity, permission) == PackageManager.PERMISSION_GRANTED; - + public static boolean hasPermission(Activity activity, String permissions[]) { + boolean hasPermission = true; + for (String permission : permissions + ) { + hasPermission = hasPermission && + ContextCompat.checkSelfPermission(activity, permission) + == PackageManager.PERMISSION_GRANTED; + } + return hasPermission; } /** - * Checks for a particular permission and runs the runnable to perform an action when the permission is granted - * Also, it shows a rationale if needed - * - * rationaleTitle and rationaleMessage can be invalid @StringRes. If the value is -1 then no permission rationale - * will be displayed and permission would be requested - * + * Checks for a particular permission and runs the runnable to perform an action when the + * permission is granted Also, it shows a rationale if needed + *

+ * rationaleTitle and rationaleMessage can be invalid @StringRes. If the value is -1 then no + * permission rationale will be displayed and permission would be requested + *

* Sample usage: - * - * PermissionUtils.checkPermissionsAndPerformAction(activity, - * Manifest.permission.WRITE_EXTERNAL_STORAGE, - * () -> initiateCameraUpload(activity), - * R.string.storage_permission_title, - * R.string.write_storage_permission_rationale); - * + *

+ * PermissionUtils.checkPermissionsAndPerformAction(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE, + * () -> initiateCameraUpload(activity), R.string.storage_permission_title, + * R.string.write_storage_permission_rationale); + *

* If you don't want the permission rationale to be shown then use: + *

+ * PermissionUtils.checkPermissionsAndPerformAction(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE, + * () -> initiateCameraUpload(activity), - 1, -1); * - * PermissionUtils.checkPermissionsAndPerformAction(activity, - * Manifest.permission.WRITE_EXTERNAL_STORAGE, - * () -> initiateCameraUpload(activity), - * - 1, -1); - * - * - * - * @param activity activity requesting permissions - * @param permission the permission being requests + * @param activity activity requesting permissions + * @param permissions the permissions array being requests * @param onPermissionGranted the runnable to be executed when the permission is granted - * @param rationaleTitle rationale title to be displayed when permission was denied. It can be an invalid @StringRes - * @param rationaleMessage rationale message to be displayed when permission was denied. It can be an invalid @StringRes + * @param rationaleTitle rationale title to be displayed when permission was denied. It can + * be an invalid @StringRes + * @param rationaleMessage rationale message to be displayed when permission was denied. It + * can be an invalid @StringRes */ - public static void checkPermissionsAndPerformAction(Activity activity, String permission, + public static void checkPermissionsAndPerformAction(Activity activity, String[] permissions, Runnable onPermissionGranted, @StringRes int rationaleTitle, @StringRes int rationaleMessage) { - checkPermissionsAndPerformAction(activity, permission, onPermissionGranted, null, + checkPermissionsAndPerformAction(activity, permissions, onPermissionGranted, null, rationaleTitle, rationaleMessage); } /** - * Checks for a particular permission and runs the corresponding runnables to perform an action when the permission is granted/denied - * Also, it shows a rationale if needed - * + * Checks for a particular permission and runs the corresponding runnables to perform an action + * when the permission is granted/denied Also, it shows a rationale if needed + *

* Sample usage: + *

+ * PermissionUtils.checkPermissionsAndPerformAction(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE, + * () -> initiateCameraUpload(activity), () -> showMessage(), R.string.storage_permission_title, + * R.string.write_storage_permission_rationale); * - * PermissionUtils.checkPermissionsAndPerformAction(activity, - * Manifest.permission.WRITE_EXTERNAL_STORAGE, - * () -> initiateCameraUpload(activity), - * () -> showMessage(), - * R.string.storage_permission_title, - * R.string.write_storage_permission_rationale); - * - * - * @param activity activity requesting permissions - * @param permission the permission being requests + * @param activity activity requesting permissions + * @param permissions the permissions array being requested * @param onPermissionGranted the runnable to be executed when the permission is granted - * @param onPermissionDenied the runnable to be executed when the permission is denied(but not permanently) - * @param rationaleTitle rationale title to be displayed when permission was denied - * @param rationaleMessage rationale message to be displayed when permission was denied + * @param onPermissionDenied the runnable to be executed when the permission is denied(but not + * permanently) + * @param rationaleTitle rationale title to be displayed when permission was denied + * @param rationaleMessage rationale message to be displayed when permission was denied */ - public static void checkPermissionsAndPerformAction(Activity activity, String permission, + public static void checkPermissionsAndPerformAction(Activity activity, String[] permissions, Runnable onPermissionGranted, Runnable onPermissionDenied, @StringRes int rationaleTitle, @StringRes int rationaleMessage) { Dexter.withActivity(activity) - .withPermission(permission) - .withListener(new BasePermissionListener() { - @Override public void onPermissionGranted(PermissionGrantedResponse response) { - onPermissionGranted.run(); - } - - @Override public void onPermissionDenied(PermissionDeniedResponse response) { - if (response.isPermanentlyDenied()) { + .withPermissions(permissions) + .withListener(new MultiplePermissionsListener() { + @Override + public void onPermissionsChecked(MultiplePermissionsReport report) { + if (report.areAllPermissionsGranted()) { + onPermissionGranted.run(); + } + if (report.isAnyPermissionPermanentlyDenied()) { + // permission is denied permanently, we will show user a dialog message. DialogUtil.showAlertDialog(activity, activity.getString(rationaleTitle), activity.getString(rationaleMessage), activity.getString(R.string.navigation_item_settings), null, @@ -125,7 +147,7 @@ public static void checkPermissionsAndPerformAction(Activity activity, String pe } @Override - public void onPermissionRationaleShouldBeShown(PermissionRequest permission, + public void onPermissionRationaleShouldBeShown(List permissions, PermissionToken token) { if (rationaleTitle == -1 && rationaleMessage == -1) { token.continuePermissionRequest(); @@ -141,7 +163,7 @@ public void onPermissionRationaleShouldBeShown(PermissionRequest permission, false); } }) + .onSameThread() .check(); } - } diff --git a/app/src/main/res/values-anp/strings.xml b/app/src/main/res/values-anp/strings.xml index e441215514..6e72c9973e 100644 --- a/app/src/main/res/values-anp/strings.xml +++ b/app/src/main/res/values-anp/strings.xml @@ -45,7 +45,7 @@ अपलोड अपलोड सहेजौ - रिफ़्रेश करौ + टटका करौ सूची श्रेणी सिनी सेटिंग्स @@ -74,7 +74,7 @@ कोय विवरण नाय कोय चर्चा नाय अज्ञात लाइसेन्स - रिफ़्रेश करौ + टटका करौ ठीक छै चेतावनी अपलोड करौ diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 4d7d33fd60..33baeaa184 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -463,9 +463,7 @@ ينتهي في: عرض الحملات انظر الحملات الجارية - استخدم منتقي الصور GET_CONTENT - تعطيل إذا تم تحميل صورك بدون موقع - يرجى التأكد من أن منتقي أندرويد الجديد هذا لا يزيل الموقع من صورك. + يرجى التأكد من أن منتقي أندرويد الجديد هذا لا يزيل الموقع من صورك. لن ترى الحملات بعد الآن، ومع ذلك، يمكنك إعادة تمكين هذا الإشعار في الإعدادات إذا كنت ترغب. تتطلب هذه الوظيفة الاتصال بالشبكة; يُرجَى التحقق من إعدادات اتصالك. حدث خطأ أثناء معالجة الصورة. رجاءً حاول مرة أخرى! diff --git a/app/src/main/res/values-ast/strings.xml b/app/src/main/res/values-ast/strings.xml index c01a6dd879..be722b57ca 100644 --- a/app/src/main/res/values-ast/strings.xml +++ b/app/src/main/res/values-ast/strings.xml @@ -1,6 +1,7 @@ @@ -9,7 +10,22 @@ Códigu fonte de Commons en GitHub Logo de Commons Sitiu web de Commons + Salir del selector d\'allugamientu Unviar + Añedir otra descripción + Añedir nueva contribución + Añedir contribución dende la cámara + Añedir contribución dende Fotos + Añedir contribución dende la galería de contribuciones previes + Pies + Descripción de llingua + Pie + Descripción + Imaxe + Toos + Prender pa enriba + Buscar vista + Estáu del llugar Semeya del día Xubiendo un ficheru @@ -19,13 +35,14 @@ (%1$d) (%1$d) - - Principiando %1$d carga - Principiando %1$d cargues + Empiezando les xubíes + + Procesando %d xubida + Procesando %d xubíes - - %1$d carga - %1$d cargues + + %d xubida + %d xubíes Esta imaxe quedará baxo la llicencia %1$s @@ -56,18 +73,23 @@ Date d\'alta Aniciando sesión Espera… + Actualizando pies y descripciones + Porfavor espera... ¡Identificación correuta! ¡Falló l\'aniciu de sesión! Nun s\'alcontró\'l ficheru. Tenta con otru. Falló la identificación, anicia sesión nuevamente Principió la xuba + Xubida en cola (mou de conexón llindada activáu) ¡%1$s xubíu! Toque pa ver la xuba - Principiando la xuba de %1$s + Xubiendo archivu: %s %1$s ta xubiendo Acabando de xubir %1$s - Falló la xuba de %1$s + Falló la xubida de %1$s + Actualizando %1$s pausáu Toque pa velo + Toque pa velo Les mios xubes recién En cola Falló @@ -78,6 +100,7 @@ Cercanu Les mios xubes Compartir + Ver páxina del archivu Pie (Riquíu) Apurre un pie pa esti ficheru Descripción @@ -106,6 +129,7 @@ Configuración Date d\'alta Imáxenes destacaes + Selector personalizáu Categoría Revisión por pares Tocante a @@ -170,6 +194,7 @@ Pidiendo permisu d\'allugamientu Aceutar Avisu + Nome d\'archivu duplicáu atopáu Xubir Non @@ -215,7 +240,7 @@ Elementu de WikiData Artículu de Wikipedia Por favor, describi l\'elementu multimedia tantu como sía posible: ¿ónde se tomó?, ¿qué amuesa?, ¿cuál ye\'l contestu? Por favor, describi los oxetos o persones. Revela la información que nun pueda aldovinase de mou cenciellu, por casu el momentu del día si ye un paisaxe. Si\'l mediu amuesa daqué desacostumao, esplica qué lo fai raro. - Escribi una descripción curtia de la imaxe. (Llende de 255 caráuteres) + Porfavor escribi una curtia descripción de la imaxe. La primera leyenda será usada como\'l Títulu pa la imaxe. Llinde de 255 carauteres. Problemes potenciales con esta imaxe: La imaxe ye enforma escura. La imaxe ta borrosa. @@ -223,10 +248,12 @@ Esta imaxe tomóse nun llugar distintu. Por favor xube solo fotografies que tomasti tu mesmu. Nun xubas imaxes qu\'atopasti nes cuentes de Facebook d\'otres persones. ¿Inda quies xubir esta imaxe? + Error de conexón + El procesu de xubida requier accesu activu a internet. Por favor, compruebi la to conexón de rede. Alcontráronse problemes na imaxe Por favor xube solo fotografies que tomasti tu mesmu. Nun xubas imaxes que descargasti d\'Internet. - Usar almacenamientu esternu - Guardar nel preséu les imaxes tomaes cola cámara de la app + Guardar tomes na aplicación. + Guardar nel almacenamientu del dispositivu les imaxes tomaes cola cámara de la app Anicia sesión na to cuenta Unviar ficheru de rexistru Unviar ficheru de rexistru a los desendolcadores per corréu electrónicu p\'ayudar a depurar problemes cola aplicación. Nota: los rexistros pueden contener potencialmente información que t\'identifique @@ -250,6 +277,7 @@ Commons Puntúanos EMF (entrugues más frecuentes) + Guía d\'usuariu Saltar el tutorial Internet nun ta disponible Error al llograr les notificaciones @@ -262,6 +290,9 @@ Encaboxar Retentar Estos son sitios cercanos que precisen imaxes para ilustrar los sos artículos de Wikipedia.\n\nAl pulsiar en \'BUSCAR NESTA ÁREA\' bloquiase\'l mapa y llánzase una busca cercana alredor d\'esi llugar. + Esti llugar precisa una semeya. + Esti llugar yá tien una semeya. + Esti llugar yá nun esiste. Nun s\'alcontró nenguna imaxe Asocedió un error al cargar les imáxenes. Xubida por: %1$s @@ -273,6 +304,7 @@ Buscar Guetao de recién: Consultes buscaes apocayá + Consultes llingüístiques recientes Asocedió un error al cargar les categoríes. Asocedió un error al cargar les imáxenes. Multimedia @@ -280,6 +312,7 @@ Elementos Destacada Xubío dende\'l móvil + Mapa Añadióse la imaxe a %1$s en Wikidata. Nun pudo anovase la entidá de Wikidata correspondiente. Poner como fondu @@ -305,13 +338,17 @@ ¿Puede xubise esta imaxe de pantalla? Compartir app Error al llograr los llugares cercanos. + Nun hay llugares cercanos alredor + Error al alcontrar monumentos ciercanos. Nun hai guetes de recién ¿Tas seguru de que quies llimpiar el to historial de guetes? + ¿Tas seguru de que quies atayar esta xubida? ¿Quies desaniciar esta gueta? Desanicióse l\'historial de guetes Nomar pa desaniciar Desaniciar Llogros + Perfil Estadístiques Agradecimientos recibíos Imáxenes destacaes @@ -336,7 +373,7 @@ Avisos Avisos (lleíos) Amosar notificaciones de cercanía - Toca equí para ver el llugar más cercanu que precisa semeyes + Amosar notificación na aplicación pal llugar mas ciercanu que necesita semeyes. Llista Permisu d\'almacenamientu Precisamos el to permisu p\'aportar al almacenamientu esternu del to preséu pa poder xubir imaxes. @@ -344,7 +381,7 @@ Pasu %1$d de %2$d: %3$s Siguiente Anterior - Yá esiste un ficheru col nome %1$. ¿Tas seguru de que quies siguir? + Yá esiste un archivu col nome %1$s. ¿Tas seguru de que desees continuar?\n\nNota: añediráse un sufixu apropiáu al nome del archivu automáticamente. Nun s\'atopó una aplicación de mapes compatible nel to preséu. Instala una pa usar esta carauterística. Imaxes Llugares @@ -364,19 +401,23 @@ Les imáxenes ensin categoríes raramente puen usase. ¿Seguro que quies siguir ensin escoyer categoría dala? Nun tien retratos seleicionaos Les imáxenes con retratos alcuéntrense más fácilmente y ye más fácil que s\'usen. ¿Seguro que quies siguir ensin retratos? - (Pa toles imaxes del conxuntu) + Atayar xubida + Usar el botón de retrocesu cancelará esta xubida y perderás tol to progresu. + Continuar xubida + (Pa toles imaxes del conxuntu) Buscar nesta área Solicitú de permisu ¿Quies qu\'usemos el to allugamientu actual p\'amosate\'l llugar más cercanu que precisa imaxes? Nun ye posible amosar el sitiu más cercanu que precisa semeyes ensin permisos d\'allugamientu Nun entrugar más esto - Amosar permisu d\'allugamientu + Pedir permisu de allugamientu Pidir permisu d\'allugamientu cuando se precise pa la función de ver la tarxeta de notificación cercana. Daqué salió mal, nun pudimos recibir los tos llogros Ficisti tantes collaboraciones que superen al nuesu sistema de cálculu de llogros. Esti ye\'l llogru definitivu. Remata el: Amosar campañes Ver les campañes en cursu + Por favor asegúrate de qu\'esti selector d\'Android nuevu nun esancia l\'allugamientu de les tos semeyes. Yá nun verás más les campañes. Sicasí, si quies puedes reactivar esti avisu na configuración. Esta función rique conexón de rede, comprueba la configuración de conexón. Asocedió un error al procesar la imaxe. Téntalo nuevamente. @@ -406,8 +447,8 @@ Esta imaxe ta baxo %1$s categoríes. Esto ta fora de la tema porque ye Ye un frayamientu de derechos d\'autor porque ye - Sí, por qué non - Imaxe siguiente + Imaxe siguiente + Sí, por qué non Faciendo clic nesti botón recibirás otra imaxe de Wikimedia Commons xubida apocayá Puedes revisar imaxes y ameyorar la calidá de Wikimedia Commons.\nLos cuatro parámetros de revisión son: \n - ¿Ésta imaxe tien rellación col contestu? \n - ¿La imaxe cumple les riegles de copyright? \n - ¿La imaxe ta correchamente categorizada? \n - Si too ta correuto, tamién pues dar les gracies al collaborador. Nun s\'usó nenguna imaxe diff --git a/app/src/main/res/values-ba/error.xml b/app/src/main/res/values-ba/error.xml index ed64904ec1..c6e01c3b2a 100644 --- a/app/src/main/res/values-ba/error.xml +++ b/app/src/main/res/values-ba/error.xml @@ -1,11 +1,12 @@ Викимилек боҙолдо Ой. Нимәлер дөрөҫ эшләнмәне! - Нимә эшләгәнегеҙҙе электорн почтаға ебәрегеҙ. Был проблеманы хәл итергә ярҙам итәсәк. + Нимә эшләгәнегеҙ тураһында һөйләгеҙ һәм электрон почтаға ебәрегеҙ. Был проблеманы хәл итергә ярҙам итәсәк. Рәхмәт! diff --git a/app/src/main/res/values-ba/strings.xml b/app/src/main/res/values-ba/strings.xml index 158aa3ba55..84e8fb14e1 100644 --- a/app/src/main/res/values-ba/strings.xml +++ b/app/src/main/res/values-ba/strings.xml @@ -3,35 +3,38 @@ * Lizalizaufa * MR973 * Sagan +* З. ӘЙЛЕ * Рустам Нурыев --> - Commons Facebook-бите - Гитхабтағы Commons сығанаҡ кодтары - Викисклад логотибы - Commons веб-сайты + Викимилектең Facebook-бите + Гитхабта Викимилектең сығанаҡ кодтары + Викимилек логотибы + Викимилектең веб-сайты Урынлашҡан ерҙе билдәләү тәҙрәһенән сығырға Раҫларға Башҡа таусирлама яҙырға Яңы өлөш өҫтәргә - Камеранан өлөш өҫтәргә - Фотоларҙан өлөш өҫтәргә - Галереялағы элекке өлөштән өҫтәргә - Ҡултамға - Тел яҙмаһы + Камеранан фото өҫтәргә + Фотолар өҫтәргә + Галереялағы элекке фотоларҙан өҫтәргә + Ҡултамғалар + Тасуирлама теле Ҡултамға Тасуирлама Рәсем Барыһы ла Өҫкә күсерергә + Көн рәсеме - %1$d файл тейәлә - %1$d файл тейәлә + %1$d файл йөкләнә + %1$d файл йөкләнә (%1$d) (%1$d) + Йөкләүҙе башлау %1$d тейәү башлана %1$d тейәү башлана @@ -43,76 +46,96 @@ %1$d тейәүҙәре Был рәсем %1$s аҫтында лицензия аласаҡ + Тикшереү Тышҡы күренеш - Ғәҙәти - Яуап алыу + Дөйөм + Кире бәйләнеш + Сер һаҡлау Викимилек Көйләүҙәр - Ҡулланыусы исеме + Викимилеккә йөкләргә + Ҡатнашыусы исеме Серһүҙ - Commons Beta лағы иҫәп яҙмаңа ин + Commons Beta-лағы иҫәп яҙмаңа ин Танылыу - Серһүҙҙе оноттоңмо? - Теркәл - Системаға ин + Серһүҙҙе оноттоғоҙмо? + Теркәлеү + Системаға инеү Зинһар, көтөгөҙ... - Танышыу уңышлы үтте - Танылыу хатаһы - Файл табылманы. Башҡа файлды эҙлә. - Кем икәнегеҙ танылманы! - Тейәү башланды! - %1$s тейәлде! - Ошонда баҫып тейәлгән файлды ҡара - %1$s тейәү башланды - %1$s тейәлә - Тейәү %1$s тамамланды - Тейәү %1$s килеп сыҡманы - Ошонда баҫ та ҡара - Һуңғы тейәүҙәрем - Сират - Булманы + Аңлатмалар һәм тасуирламалар яңыртыла + Зинһар, көтөгөҙ... + Уңышлы танылдығыҙ! + Танылыу хатаһы! + Файл табылманы. Башҡа файлды эҙләп ҡарағыҙ. + Танылыу хатаһы. Зинһар, тағы бер тапҡыр танылығыҙ. + Йөкләү башланды! + %1$s йөкләнде! + Ошонда баҫып йөкләнгән файлды ҡара + Файлды йөкләү: %s + %1$s йөкләнә + Йөкләү %1$s тамамланды + %1$s йөкләп булманы + Ҡарау өсөн баҫығыҙ + Ҡарау өсөн баҫығыҙ + Һуңғы йөкләүҙәрем + Сиратта + Йөкләү хатаһы %1$d%% тамам - Тейәү + Йөкләү Галереянан Фотоға төшөрөргә - Минең тейәүҙәрем + Яҡын-тирәлә + Минең йөкләүҙәрем Уртаҡлашырға - Исем + Файл битен ҡарау + Ҡултамға (Мотлаҡ) + Зинһар, был файлдың исемен күрһәтегеҙ Тасуирлама + Ҡултамға (Имза) Инеп булмай - интернет хатаһы - Күп тапҡыр яңылыштың. Зинһар, бер-нисә минуттан тағы ла инеп ҡара - Ғәфү итегеҙ, әммә был исемдәге ҡатнашыусыға Викискладҡа инеү тыйылған - Ике тапҡыр раҫлай торған шәхси кодты яҙырға кәрәк - Системаға инеү хатаһы! - Тейәү + Бик күп уңышһыҙ ынтылыштар булды. Зинһар, бер нисә минуттан тағы ла инеп ҡара. + Ғәфү итегеҙ, әммә был ҡатнашыусы Викимилектә бикләнгән + Ике тапҡыр раҫлай торған кодты яҙырға кәрәк + Танылыу хатаһы! + Йөкләү Был файлдар төкөмөнә атама бирегеҙ - Үҙгәрештәр + Үҙгәртеүҙәр + Йөкләү Категория буйынса эҙләү + Һеҙҙең һүрәттә төшөрөлгән элементтарҙы эҙләргә (тау, Тадж-Махал һ.б.) Һаҡларға - Тейәүҙәр әлегә юҡ + Яңыртырға + Исемлек + (Тейәүҙәр әлегә юҡ) %1$s тап килгән категориялар табылманы - Викискладта һәрәтегеҙҙе тиҙерәк табыу өсөн категоиялар өҫтәгеҙ.\nКатегория өҫтәү өсөн атама яҙа башлағыҙ. + %1$s-ға тап килгән Wikidata элементы табылманы. + Викимилектә һүрәтегеҙҙе тиҙерәк табыу өсөн категориялар өҫтәгеҙ.\nКатегория өҫтәү өсөн атама яҙа башлағыҙ. + Категориялар Көйләүҙәр - Теркәл + Теркәлеү + Тәҡдим ителгән һүрәттәр + Ҡулланыусы селекторы + Категория + Эксперт баһаһы Ҡушымта тураһында - \"Викисклад\" ҡушылмаһы - асыҡ коды булған программа. Уны Викимедиала гранттарын алған ҡатнашыусы һәм ирекмәндәр эшләгән. Викимедиа Фонды был ҡушылманы булдырыу,яңыртыу йәки эшләтеүҙә ҡатнашмай. + \"Викимилек\" ҡушылмаһы - асыҡ кодлы программа. Уны Викимедиа гранттарын алған ҡатнашыусы һәм ирекмәндәр эшләгән. Викимедиа Фонды был ҡушымтаны булдырыу, яңыртыу йәки эшләтеүҙә ҡатнашмай. Һеҙ ҙә <a href=\"%1$s\"> булдырып, хата йәки берәй тәҡдим тураһында ошонда GitHub</a> яҙып ебәрә алаһығыҙ. - <u>Сер һаҡлау сәйәсәте</u> - <u>Рәхмәттәр</u> - Аныҡлау - Фекереңде ебәр(эл.почта аша) + Сер һаҡлау сәйәсәте + Яһаусылар + Ҡушымта тураһында + Фекереңде ебәр (эл.почта аша) Почта клиенты асыҡланмаған Яңыраҡ ҡулланылған категориялар - Тәүге синхронлауҙы көтөү... - Әлегә бер фото ла тейәмәгәнһең - Йәнә бер тапҡыр - Кәрәкмәй - Был рәсемде ебәргәндә шуны раҫлайым: был минең шәхси эшем, автор хоҡуҡтарын һаҡлаған материал йәки селфи түгел, шулай уҡ Викискладтың <a href=\"https://commons.wikimedia.org/wiki/Commons:Policies_and_guidelines/ru\">ҡағиҙәләренә тап килә</a>. - Тейәргә - Нығытылған рөхсәтнамә - Алдағы атама/һәрәтләмәне ҡулланыу - Төнгө режим + Тәүге синхронлаштырыуҙы көтөү... + Әлегә бер фото ла йөкләмәгәнһегеҙ + Ҡабатларға + Кире алыу + Был рәсемде ебәргәндә шуны раҫлайым: был минең шәхси эшем, автор хоҡуҡтарын һаҡлаған материал йәки селфи түгел, шулай уҡ Викимилектең <a href=\"https://commons.wikimedia.org/wiki/Commons:Policies_and_guidelines/ru\">ҡағиҙәләренә тап килә</a>. + Ташып алырға + Программа көйләгән лицензия + Элекке атаманы һәм тасуирламаны ҡулланыу + Биҙәү темаһы Attribution-ShareAlike 4.0 Attribution 4.0 Attribution-ShareAlike 3.0 @@ -122,43 +145,58 @@ CC BY 3.0 CC-BY-SA 3.0 CC BY 4.0 - Викисклад Википедияла ҡулланылғандан күберәк рәсемде һаҡлай. - Һеҙ ҡуйған рәсемдәр ер шары халҡын мәғрифәтлерәк итә! - Зинһар, тик үҙегеҙ төшөргән йәки үҙегеҙ эшләгән фотографияларҙы ғына тейәгеҙ: + Викимилектә Википедияла ҡулланылған рәсемдәрҙең күп өлөшө бар. + Һеҙ ҡуйған рәсемдәр ер шары халҡының белемен арттырыуға ярҙам итә ала! + Зинһар, тик үҙегеҙ төшөргән йәки үҙегеҙ эшләгән һүрәттәрҙе генә йөкләгеҙ: Тәбиғи объекттар (сәскәләр, хайуандар, тауҙар) Файҙалы объекттар (велосипедтар, вокзалдар) Билдәле шәхестәр (ҡала мэры, олимпия атлеттары һ.б.) Зинһар, ЙӨКЛӘМӘГЕҘ: Селфи йәки дуҫтарығыҙҙың фотоһүрәттәре Интернеттан күсереп алынған һүрәттәр - Патентлы ҡушылмаларҙан алынған скриншоттар - Тейәү миҫалы: + Патентлы ҡушымталарҙан алынған скриншоттар + Йөкләү миҫалы: Атама: Сидней опера театры - Һүрәтләмә: Сидней опера театры ҡултыҡ яғынан ҡарағанда + Тасуирлама: Сидней опера театры, ҡултыҡ аша ҡарағандағы күренеш Категориялар: Сидней опера театры көнбайыштан ҡарағанда, Сидней опера театры - Рәсемдәрегеҙҙе тейәгеҙ. Википедия мәҡәләләрен йәнләндереүгә булышығыҙ! - Википедиялағы рәсемдәр Викискладта һаҡлана. - Рәсемдәрегеҙ ер шарындағы бар кешеләрҙе мәғрифәтлерәк итеүгә булыша. - Интернетта табылған, плакат рәсемдәре, китап тышлыҡтары һ.б. төрлө авторлыҡ хоҡуғы менән һаҡланған материалдарҙы ситләп үтегеҙ. + Рәсемдәрегеҙҙе йөкләгеҙ. Википедия мәҡәләләрен йәнләндереүгә булышығыҙ! + Википедиялағы рәсемдәр Викимилектә һаҡлана. + Һеҙ ҡуйған рәсемдәр ер шары халҡының белемен арттырыуға ярҙам итә ала! + Интернетта табылған, шулай уҡ плакат рәсемдәре, китап тышлыҡтары һ.б. авторлыҡ хоҡуғы менән һаҡланған төрлө материалдарҙы урап үтегеҙ. Быны аңланығыҙмы? Эйе! + Ентеклерәк Категориялар - Тейәлә башланы... + Йөкләнә башланы... Бер ни ҙә һайланмаған - Һүрәтләүе юҡ + Тасуирламаһы юҡ + Фекер алышыу юҡ Билдәһеҙ лицензия - Яңыртып алыу + Яңыртырға Кәрәкле рөхсәт: тышҡы һаҡлағыстан алып уҡыу. Ҡушымта шунһыҙ эшләмәйәсәк. Кәрәкле рөхсәт: тышҡы һаҡлағысҡа яҙыу. Ҡушымта шунһыҙ эшләмәйәсәк. + Урынды билдәләү өсөн һорау + Ярар + Иҫкәртеү + Йөкләргә + Эйе + Юҡ + Ҡултамға (Имза) Атама - Тейәү датаһы + Тасуирламалар + Тасуирлама + Фекер алышыу + Автор + Йөкләү датаһы + Лицензия Координаттар Бер ни ҙә бирелмәне Бета-тестер булыу - Google Play аша беҙҙең бета-версия каналына яҙыл һәм иң тәүгеләрҙән булып яңы көйләүҙәр,хата төҙәтеүҙәрҙе бел + Google Play аша беҙҙең бета-версия каналына яҙыл һәм иң тәүгеләрҙән булып яңы көйләүҙәр, хата төҙәтеүҙәр тураһында хәбәрҙар бул 2ФА Коды - Ысынлап та сыҡҡың киләме? + Ысынлап та системанан сыҡҡығыҙ киләме? Медиарәсем хатаһы + Субкатегориялар табылманы Зао тауы Ламалар Йәйғорло күпер @@ -166,30 +204,53 @@ Википедияға рәхим итегеҙ Рәхим итегеҙ - автор хоҡуҡтары Сидней опера театры - Кәрәкмәй - Асам - Ябам - Ҡайтам + Кире алыу + Асырға + Ябырға + Баш бит + Тейәргә + Эргәлә + Ҡушымта тураһында Көйләүҙәр - Яуап кәрәк - Сығам - Өйрәтмә - һүрәтләү табылманы - Викискладтағы файл бите - Викимәғлүмәт өлөшө - Зинһар, тейәләсәк файлды тәфсирләп һүрәтлә:ҡайҙа төшөрөлгән? нимә һәрәтләнә? һүрәт нимәне аңлата? Рәсемдәге кешеләр йәки объекттарҙы ла һүрәтлә. Һүрәткә ҡарап ҡына белеп булмаған мәғлүмәттәрҙе өҫтә: мәҫәлән, тәүлектең ниндәй мәлендә, ҡасан төшөрөлгән был файл. Әгәр ғәҙәти булмаған әйбер төшөрөлһә, уның нимәһе шаҡ ҡатырғанын аңлат. + Кире бәйләнеш + Сығырға + Ҡулланма + Белдереүҙәр + Ҡарап сығырға + тасуирлау табылманы + Викимилектә файл бите + Викимәғлүмәт элементы + Википедия мәҡәләһе + Зинһар, йөкләнәсәк файлды тәфсирләп тасуирлағыҙ: ҡайҙа төшөрөлгән? нимә һүрәтләнә? һүрәт нимәне аңлата? Рәсемдәге кешеләр йәки объекттарҙы ла һүрәтләгеҙ. Һүрәткә ҡарап ҡына белеп булмаған мәғлүмәттәрҙе өҫтәгеҙ: мәҫәлән, был файл тәүлектең ниндәй мәлендә, ҡасан төшөрөлгән. Әгәр ғәҙәти булмаған әйбер төшөрөлһә, уның үҙенсәлеген аңлатығыҙ. + Зинһар, һүрәттең ҡыҫҡаса тасуирламаһын яҙығыҙ. Һүрәттең исеме булараҡ беренсе аңлатма ҡулланыласаҡ. Лимит 255 символ. + Һүрәткә бәйле ихтимал булған проблемалар: + Һүрәт бик ҡараңғы. + Һүрәт тоноҡ. + Был һүрәт Викимилектә бар инде. + Был һүрәт башҡа ерҙә төшөрөлгән. + Зинһар, Фейсбукта тапҡанды түгел, үҙегеҙ төшөргән һүрәттәрҙе генә йөкләгеҙ. + Барыбер был һүрәтте йөкләргә теләйһегеҙме? + Бәйләнеш хатаһы + Йөкләү өсөн Интернетҡа инеү талап ителә. Элемтәне тикшерегеҙ. + Зинһар, Интернеттан күсереп алғандарҙы түгел, үҙегеҙ төшөргән һүрәттәрҙе генә йөкләгеҙ. Тышҡы һаҡлағысты ҡуллан Ҡулайламаның камераһы ярҙамында төшөрөлгән һүрәттәрҙе һаҡлау - Шәхси теркәү яҙмаһына ин + Иҫәп яҙмағыҙға инегеҙ Теркәлеү файлын ебәр Теркәлеү файлын эл-почта аша ҡораусыларға ебәр + URL-адресты асыу өсөн браузер табылманы + Хата! URL-адрес табылманы + Юйыуға тәҡдим итергә + Был файл юйыуға тәҡдим ителгән. + Тулыраҡ мәғлүмәт алыу өсөн веб-битте ҡарағыҙ + Танылыу Википедия Викимилек Беҙҙе баһалағыҙ - FAQ + ЙБҺ (ЧаВо) Интернетҡа инеп булмай Тәржемә итергә Телдәр - Кире алыу - Ҡабатлау + Кире алырға + Ҡабатларға diff --git a/app/src/main/res/values-blk/error.xml b/app/src/main/res/values-blk/error.xml new file mode 100644 index 0000000000..a24f4bce2a --- /dev/null +++ b/app/src/main/res/values-blk/error.xml @@ -0,0 +1,10 @@ + + + + ကောင်မွန်း ထဲ့ကုဲင်ထိုꩻ + အဝ်း။ အမာႏထိုꩻလဲဉ်းသွူတစွိုးစွိုး! + နာꩻမာꩻတမုဲင်ꩻကရိုꩻနဝ်ꩻခြုဲင်းနယ်သွော့ꩻနီယို၊ အဝ်ႏနဝ်ꩻထွူ အဝ်ႏEmailယိုတရန်း ဖန်းဖြယ်လွဉ်သွော့ꩻနီထျꩻယို။ နီစွဲးကမ်းစေလီစဲင်းဖေႏနာꩻ! + ကေꩻဇူꩻတင်ႏငါႏဩ! + diff --git a/app/src/main/res/values-blk/strings.xml b/app/src/main/res/values-blk/strings.xml index 9a6ae414b1..bdb8b498c9 100644 --- a/app/src/main/res/values-blk/strings.xml +++ b/app/src/main/res/values-blk/strings.xml @@ -2,9 +2,17 @@ + Commons(လိုꩻအာသုင်ꩻ) Facebook လိတ်မဲ့ငါ + Commons(လိုꩻအာသုင်ꩻ) Github ရွီးခိုႏကိုဒ် + Commons(လိုꩻအာသုင်ꩻ) လိုင်ကို + Commons(လိုꩻအာသုင်ꩻ) ဝဲက်သုဲက် + အဝ်ႏအုံထွောင်းထာꩻလွိုက်ကို ထန်ႏသွော့ꩻ + ဒင်ႏနယ်သွော့ꩻ + ကားကအဝ်ႏ ဟန်ႏနီꩻ ဓာတ်ပုင်ႏတဲးဝါး အဝ်ႏထူႏဒင်ႏဖုဲင် %1$d ဗာႏကျာꩻ diff --git a/app/src/main/res/values-bn/strings.xml b/app/src/main/res/values-bn/strings.xml index eaa38dce8a..c096fd4dbb 100644 --- a/app/src/main/res/values-bn/strings.xml +++ b/app/src/main/res/values-bn/strings.xml @@ -16,6 +16,7 @@ * Sibabrata Banerjee * Tahmid * Tahmid02016 +* Tanbiruzzaman * Tauhid16 * Titodutta * WikiAbuHuraira @@ -203,11 +204,13 @@ অবস্থানের অনুমতির অনুরোধ ঠিক আছে সতর্কীকরণ + সদৃশ ফাইলের নাম পাওয়া গেছে আপলোড হ্যাঁ না চিত্রবর্ণনা শিরোনাম + বর্ণনা বিবরণ আলোচনা স্রষ্টা @@ -221,6 +224,7 @@ আপনি কি সত্যিই প্রস্থান করতে চান? মিডিয়া চিত্র ব্যর্থ হয়েছে কোন উপবিষয়শ্রেনী পাওয়া যায় নি। + কোনও মূল বিষয়শ্রেণী পাওয়া যায়নি জাও পর্বত লামা রংধনুর সেতু @@ -341,6 +345,9 @@ নির্বাচিত ছবি স্তর আপলোডকৃত চিত্র + ছবিগুলো প্রত্যাবর্তন করা হয়নি + ব্যবহৃত ছবি + আপনার বন্ধুদের সাথে আপনার অর্জন শেয়ার করুন! নূন্যতম আবশ্যকতা: ত্রুটি ঘটেছে! কমন্স বিজ্ঞপ্তি @@ -366,6 +373,10 @@ বুকমার্ক আমি ভুল করে আপলোড করেছি আপলোড বাতিল করুন + আপলোড চালিয়ে যান + এই এলাকায় অনুসন্ধান করুন + অনুমতির অনুরোধ + আবার কখনো দেখাবেন না আপনি এত অবদান রেখেছেন যে আমাদের গণনা ব্যবস্থা তা গণনা করতে পারছে না। অভিনন্দন, এটাই চূড়ান্ত অর্জন। সম্পন্ন এই চিত্রটি কি কপিরাইটের নিয়ম অনুসরণ করে? @@ -393,9 +404,9 @@ বিবরণের পূর্বনির্ধারিত ভাষা সফল ব্যর্থ হয়েছে - একটি সেলফি - ঝাপসা - আজেবাজে + একটি সেলফি যা কোনও নিবন্ধে ব্যবহৃত হয়নি + একদম ঝাপসা + আজেবাজে, কোনও নিবন্ধেই একেবারে ব্যবহারের যোগ্য নয় সংবাদপত্রের চিত্র ইন্টারনেট থেকে নেওয়া অজানা চিত্র লোগো diff --git a/app/src/main/res/values-cy/strings.xml b/app/src/main/res/values-cy/strings.xml index 0c672ee5c3..006595c6b0 100644 --- a/app/src/main/res/values-cy/strings.xml +++ b/app/src/main/res/values-cy/strings.xml @@ -9,9 +9,15 @@ Tudalen Adborth Comin Logo Comin Gwefan Comin + Cyflwyno Ychwanegu disgrifiad arall + Ychwanegu cyfraniad newydd + Penawdau + Disgrifiad Iaith + Pennawd Disgrifiad Delwedd + Popeth Llun y Dydd %1$d ffeil yn uwchlwytho @@ -60,10 +66,10 @@ Dechreuodd yr uwchlwytho! Uwchlwythwyd %1$s! Tapiwch i weld eich uwchlwythiad - Yn dechrau uwchlwytho %1$s + Wrthi\'n uwchlwytho ffeil: %s Wrthi\'n uwchlwytho %1$s Yn gorffen uwchlwytho %1$s - Methwyd uwchlwytho %1$s + Wedi methu uwchlwytho %1$s Tapiwch i weld Tapiwch i weld Fy Uwchlwythiadau Diweddaraf @@ -76,8 +82,9 @@ Gerllaw Fy uwchlwythiadau Rhannu - Teitl (angenrheidiol) + Pennawd (Gofynnol) Disgrifiad + Pennawd Yn methu mewngofnodi - methodd y rhwydwaith Cafwyd gormod o ymgeision aflwyddiannus. Oedwch ennyd cyn ceisio eto. Ymddiheurwn. Mae\'r defnyddiwr hwn wedi ei flocio ar Gomin Wikimedia @@ -91,7 +98,7 @@ Cadw Ailgyrchu Rhestr - Heb uwchlwytho eto + (Dim uwchlwythiadau eto) Dim categori\'n cyfateb i %1$s ar gael Ychwanegwch gategori/au er mwyn i bobl fedru canfod eich ffeiliau ar Gomin Wicimedia.\n\nCychwynwch deipio i ychwanegu categori/au. Categorïau @@ -103,8 +110,8 @@ Amdanom Ap Cynnwys Agored a grewyd ac a gefnogir gan wirfoddolwyr cymuned Wicimedia yw ap Comin Wicimedia. Does a wnelo Sefydliad Wicimedia ddim byd ag e (ei greu, ei gynnal na\'i ddatblygu). \n\nCrewch <a href=\"%1$s\">ymholiad GitHub</a> os oes gennych fyg, broblem neu awgrym. - <u>Polisi preifatrwydd</u> - Clod a bri + Polisi preifatrwydd + Cydnabyddiaeth Amdanom Danfonwch Adborth (drwy Ebost) Dim ebost client wedi\'i ganfod @@ -117,7 +124,7 @@ Lawrlwytho Trwydded Ddiofyn (\'default\') Defnydiwch y teitl/disgrifiad blaenorol - Modd fin nos + Thema Attribution-ShareAlike 4.0 Attribution 4.0 CC Attribution-ShareAlike 3.0 @@ -147,7 +154,7 @@ Cadwch yn glir o ddeunydd a hawlfraint arno a ganfyddwch ar y we, ac hefyd lluniau o bosteri, cloriau llyfrau, ayb. Ydych chi wedi deall? Do! - <u>Rhagor o wybodaeth</u> + Mwy o wybodaeth Categorïau Wrthi\'n llwytho… Dim categorïau wedi eu dewis @@ -210,7 +217,7 @@ Cymerwyd y llun mewn lleoliad gwahanol. Wyt ti\'n dal yn awyddus i uwchlwytho\'r ddelwedd? Gwallau yn y ddelwedd - Defnyddia storfa allanol + Cadw delweddau Mewn-ap Mewngofnodwch i\'ch cyfri Danfonwch y ffeil log Gwall! Ni chafwyd hyd i\'r URL @@ -292,6 +299,15 @@ Disgrifiad Eitemau DYSGU MWY + Ailosod Manylion + Lefel API + Fersiwn Android + Enw dyfais Eich adborth + Adrodd + Adrodd toriad + Adrodd y defnyddiwr hwn + Adrodd y cynnwys hwn + Cais i flocio\'r defnyddiwr hwn diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 0b50b9ddba..f32ad1cf35 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -381,9 +381,7 @@ Μην το ρωτήσετε ξανά αυτό Ζητήστε άδεια τοποθεσίας Τελειώνει σε: - Χρησιμοποιείστε τον επιλογές φωτογραφιών GET_CONTENT - Απενεργοποιήστε εάν οι εικόνες ανεβαίνουν χωρίς τοποθεσία - Παρακαλώ σιγουρευτείτε ότι αύτος ο κανούριος επιλογέας Android δεν αφαιρεί την τοποθεσία από τις εικόνες. + Παρακαλώ σιγουρευτείτε ότι αύτος ο κανούριος επιλογέας Android δεν αφαιρεί την τοποθεσία από τις εικόνες. Ναι, γιατί όχι Επόμενη εικόνα Δεν χρησιμοποιούνται εικόνες diff --git a/app/src/main/res/values-eo/strings.xml b/app/src/main/res/values-eo/strings.xml index c60a42fcdb..8e4b9380cc 100644 --- a/app/src/main/res/values-eo/strings.xml +++ b/app/src/main/res/values-eo/strings.xml @@ -3,6 +3,7 @@ * Jakub Fabijan * Javiero * KuboF +* Martelkapo * Metraduk * Mirin * Robin van der Vliet @@ -15,6 +16,20 @@ Emblemo de Komunejo Retejo de Komunejo Sendi + Aldoni alian priskribon + Aldoni novan kontribuaĵon + Aldoni kontribuaĵon de fotilo + Aldoni kontribuaĵon de Fotoj + Aldoni kontribuaĵon de antaŭa kontribuaĵa galerio + Subteksto + Lingva Priskribo + Subteksto + Priskribo + Bildo + Ĉiuj + Baskuli Supren + Serĉa Vido + Lokstato Bildo de la Tago %1$d dosiero alŝutata @@ -25,9 +40,10 @@ (%1$d) (%1$d) - - Komencante %1$d alŝuton - Komencante %1$d alŝutojn + Komencanta Alŝutojn + + Komencanta %d alŝuton + Komencanta %d alŝutojn %d alŝuto @@ -62,6 +78,8 @@ Registriĝi Ensalutado Bonvolu atendi... + Ĝisdatiganta subtekstojn kaj priskribojn + Bonvolu atendi… Ensalutado sukcesis Ensalutado malsukcesis Dosiero ne trovita. Bonvolu provi alian dosieron. @@ -72,8 +90,10 @@ Alŝutas dosieron: %s Alŝutante %1$s Finiĝis la alŝutado de %1$s - La alŝutado de %1$s malsukcesis + La alŝutado de %1$s malsukcesis + Alŝutado de %1$s paŭzita Tuŝetu por vidi + Frapeti por rigardi Miaj lastaj alŝutoj Envicigita Malsukcesis @@ -84,8 +104,11 @@ Apuda Miaj alŝutoj Konigi - Titolo (deviga) + Rigardi dosierpaĝon + Subteksto (devigita) + Bonvolu provizi subtekston por ĉi tiu dosiero Priskribo + Subteksto Ne eblas ensaluti. Estas problemo pri la loka reto. Tro da,nesukcesaj provoj. Bonvolu provi denove post kelkaj minutoj. Pardonon, ĉi tiu uzanto estas barita de la Vikimedia Komunejo @@ -96,23 +119,28 @@ Ŝanĝoj Alŝuti Serĉi tra kategorioj + Serĉi erojn, kiujn prezentas via enhavo (ekz. monton, Taĝ-Mahalon, ktp.) Konservi Aktualigi Listo - Neniuj alŝutaĵoj ĝis nun! + (Ankoraŭ neniuj alŝutoj) Troviĝis neniu kategorio kongrua kun serĉoteksto %1$s + Neniu Wikidatumoj-eroj kongruantaj al %1$s trovitaj + %1$s havas neniujn infanajn klasojn + %1$s havas neniujn gepatrajn klasojn Aldonu kategoriojn por plitrovebligi viajn bildojn ĉe Vikimeda Komunejo.\nEktajpu por aldoni kategoriojn. Kategorioj Agordoj Registriĝi Elstarigitaj bildoj + Laŭmenda Elektilo Kategorio Reviziado de kolegoj Pri La aplikaĵo de Vikimedia Komunejo estas malfermitkoda aplijaĵo kreita kaj subtenata de la gratifikitoj kaj voluntuloj de la Vikimedia komunumo. La Vikimedia Fondaĵo ne partoprenas en la kreado, evoluigado, aŭ subtenado de la aplikaĵo. Krei novan <a href=\"%1$s\">problemon ĉe GitHub</a> por raportoj pri cimoj kaj sugestoj. - <u>Privateca politiko</u> - <u>Agnoskoj</u> + <u>Privateca politiko</u> + <u>Agnoskoj</u> Pri Sendi viajn komentojn (per retpoŝto) Neniu retpoŝtilo instalita @@ -124,8 +152,8 @@ Per submetado de ĉi tiu bildo, mi certigas ke ĝi estas mia propra verko, ke ĝi ne enhavas ion protektatan de aŭtorrajtoj, ke ĝi ne estas memfoto, kaj ke ĝi alimaniere observas la <a href=\"https://commons.wikimedia.org/wiki/Commons:Policies_and_guidelines/eo\">regularojn de Vikipedia Komunejo</a>. Elŝuti Defaŭlta permesilo - Uzi la antaŭan titolon/priskribon - Nokta reĝimo + Uzi la antaŭajn titolon kaj priskribon + Etoso Atribuite-Samkondiĉe 4.0 Atribuite 4.0 Atribuite-Samkondiĉe 3.0 @@ -155,10 +183,11 @@ Evitu aĵojn kun aŭtorrajtoj, trovitajn ĉe la Interreto, kaj ankaŭ bildojn de afiŝoj, kovrilojn de libroj, ktp. Ĉu vi komprenis? Jes! - <u>Ekscii pli</u> + <u>Ekscii pli</u> Kategorioj Ŝargado... Neniu elektita + Neniu substeksto Sen priskribo Neniu diskuto Nekonata permesilo @@ -169,9 +198,12 @@ Petante Permeson por Lokado Bone Averto + Alŝuti Jes Ne + Subteksto Titolo + Figuraĵoj Priskribo Diskuto Kreinto @@ -211,6 +243,7 @@ Vikidatumero Vikipedia artikolo Bonvolu priskribi la aŭdvidaĵon plej detale laŭeble: Kie ĝi estis fotita? Kio estas la kunteksto? Bonvolu priskribi la prezentatajn objektojn aŭ homojn. Rivelu informon ne facile konjekteblan, ekz. la horo de la tago por pejzaĝo. Se la aŭdvidaĵo prezentas ion malordinaran, bonvolu klarigi kiel ĝi estas malordinara. + Bonvolu skribi mallongan priskribon pri la bildo. La unua subteksto estus uzita kiel la Titilo por la bildo. La limo estas 255 signoj. Eblaj problemoj pri ĉi tiu bildo: Bildo estas tro malluma. Bildo estas malklara. @@ -218,9 +251,12 @@ Ĉi tiu bildo estis fotita ĉe alia loko. Bonvolu nur alŝuti bildojn faritajn de vi mem. Ne alŝutu bildojn kiujn vi trovis ĉe Facebook-kontoj de aliuloj. Ĉu vi ankoraŭ volas alŝuti ĉi tiun bildon? + Konekta Eraro + La alŝuta procezo postulas aktivan interretan aliron. Bonvolu kontroli vian retan konekton. + Problemoj trovitaj en la bildo Bonvolu nur alŝuti bildojn faritajn de vi mem. Ne alŝutu bildojn kiujn vi elŝutis el la Interreto. - Uzi eksteran konservejon - Konservi bildojn fotitajn per la en-aplikaĵa fotilo sur via aparato + Konservi en-aplikaĵajn fotojn + Konservi bildojn fotitajn per la en-aplikaĵa fotilo en vian aparaton Ensaluti en vian konton Sendi la protokolan dosieron Sendi protokolan dosieron al evoluigantoj per retpoŝto por helpi riparadon de problemoj pri la aplikaĵo. Notu: protokoloj eble enhavas identigan informon @@ -228,7 +264,7 @@ Eraro! URL ne trovita Proponi forigon Oni proponis ke ĉi tiu bildo estu forigota. - <u>Vidu la retpaĝon por pliaj detaloj</u> + <u>Rigardu la retpaĝon por pliaj detaloj</u> Preterpasi Ensaluti Ĉu vere vi volas transpasi la ensalutadon? @@ -242,23 +278,28 @@ Vikidatumoj Vikipedio Komunejo - <u>Aprezi nin</u> - <u>Oftaj demandoj</u> + <u>Aprezi nin</u> + <u>Oftaj demandoj</u> + Uzanta Gvidilo Preterpasi Lernilon Interreto ne disponas Eraro pri akirado de sciigoj Eraro dum akirado de bildo por revizio. Alklaku \'Aktualigi\' por reprovi. Neniu sciigo troviĝis - <u>Traduki</u> + <u>Traduki</u> Lingvoj Elekti la lingvon, por kiu vi volas submeti tradukojn Daŭrigi Nuligi Reprovi Jen lokoj apud vi bezonantajn bildojn por ilustri siajn Vikipediaj artikolojn.\n\nAlklakado de \'Serĉi ĉi tiun areon\' ŝlosas la mapon kaj komencas apudan serĉon ĉirkaŭ tiu loko. + Ĉi tiu loko bezonas foton. + Ĉi tiu loko jam havas foton. + Ĉi tiu loko ne plu ekzistas. Neniuj bildoj trovitaj! Okazis eraro ŝargante la bildojn. Alŝutita de: %1$s + Forbarita Vi estas forbarata el redaktado de Komunejo Bildo de la Tago Serĉi @@ -266,12 +307,16 @@ Serĉi Lastaj serĉoj: Ĵusaj serĉoj + Lastatempaj lingvaj demandoj Eraro okazis dum ŝargado de kategorioj. + Okazis eraro dum ŝargado de kategorioj. Aŭdvidaĵo Kategorioj + Eroj Elstaraj Alŝutita per poŝaparato - Bildo sukcese aldoniĝis al %1$s ĉe Vikidatumoj! + Mapo + Bildo sukcese aldonita al %1$s ĉe Vikidatumoj! Malsukcesis aktualigo de la respondanta Vikidatuma ero! Ekranfonigi Sukcese ŝanĝis ekranfonon! @@ -296,13 +341,16 @@ Ĉu ĉi tiu ekrankopio estas alŝutebla? Diskonigi Aplikaĵon Eraro dum akirado de apudaj lokoj. + Neniuj apudaj lokoj ĉirkaŭ Neniu ĵusa serĉo Ĉu vi certas ke vi volas malplenigi vian serĉ-historion? + Ĉu vi certas, ke vi volas nuligi ĉi tiun alŝuton? Ĉu vi volas forigi ĉi tiun serĉon? Forviŝis serĉadan historion Proponi Forigon Forigi Atingoj + Profilo Statistiko Ricevitaj dankoj Elstaraj Bildoj @@ -325,7 +373,7 @@ Kontribuoj Apude Sciigoj - Sciigoj (enarkivigitaj) + Sciigoj (legita) Montri sciigojn pri apudaĵoj Tuŝetu ĉi tie por vidi apudan lokon kiu bezonas bildojn Listo @@ -335,7 +383,7 @@ Paŝo %1$d el %2$d Sekva Antaŭa - Ekzistas dosiero kun la nomo %1$s. Ĉu vi certas ke vi volas daŭrigi? + Ekzistas dosiero kun la nomo %1$s. Ĉu vi certas, ke vi volas daŭrigi?\n\nNotu: taŭga sufikso estos aŭtomate aldonita al la dosiernomo. Neniu kongrua aplikaĵo troveblas en via aparato. Bonvolu instali tian por uzo kun ĉi tiu funkcio. Bildoj Lokoj @@ -352,7 +400,7 @@ Alŝutita de mi je %1$s, uzata en %2$d artikolo(j). Bonvenon al Komunejo!\n\nAlŝutu vian unuan aŭdvidaĵon per frapeto de la butono \'Aldoni\'. Neniu Elektita Kategorio - Bildo sen kategorioj estas malofte uzebla. Ĉu vi nepre volas aldoni sen elekti kategoriojn? + Bildoj sen kategorioj estas malofte uzebla. Ĉu vi certas, ke vi volas daŭrigi sen elekti kategoriojn? (Je ĉiuj bildoj en aro) Serĉi ĉi tiun areon Peto por Permeso @@ -440,10 +488,19 @@ Ne povis proponi forigon. Memfoto Malklara - Sensencaĵo + volapukaĵo, absolute neuzebla en iu ajn artikolo Gazetara showImageWithItem Hazarda foto el Interreto Emblemo + Breĉo de Libereco de Panoramo Ĉar ĝi estas + Provanta ĝisdatigi kategoriojn. + Kategoria ĝisdatigo + Sukcese + Ne povis aldoni kategoriojn. + Ĝisdatigi kategoriojn + Provanta ĝisdatigi prezentadojn. + Redakti prezentadojn + Ne povis aldoni prezentadojn. Priskribo diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 583a162576..dea2bc1728 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -327,7 +327,7 @@ Aucune image trouvée ! Une erreur s’est produite durant le chargement des images. Téléversé par : %1$s - Bloqué{{GENDER:||e}} + {{GENDER:|Bloqué|Bloquée}} Vous avez été bloqué et ne pouvez plus modifier sur Commons Image du jour Chercher @@ -448,9 +448,9 @@ Se termine le : Campagnes d’affichage Voir les campagnes en cours - Utiliser le sélecteur de photos GET_CONTENT - Désactiver si vos photos sont téléversées sans emplacement - Assurez-vous que ce nouveau sélecteur Android n’élimine pas l’emplacement de vos photos. + Utiliser le sélecteur de photos basé sur un document + Le nouveau sélecteur de photos de Android risque de perdre des informations de localisation. Activez-le si vous semblez l’utiliser. + Le désactiver pourrait déclencher le nouveau sélecteur de photos de Android. Il risque de perdre des informations de localisation.\n\nTaper sur \'En savoir plus\' pour plus d’informations. Vous ne verrez plus les campagnes. Néanmoins, vous pouvez réactiver cette notification dans vos paramètres de configuration, si vous le souhaitez. Cette fonction nécessite une connexion réseau, veuillez consulter vos paramètres de connexion. Une erreur est survenue durant le traitement de l’image. Veuillez recommencer ! diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index 69e8574f27..6c131b2568 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -12,7 +12,7 @@ * Vivaelcelta --> - Páxina de Facebook en Commons + Páxina de Commons en Facebook Código fonte de Commons en Github Logo de Commons Sitio web de Commons diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml index b6cb51d71d..a3608b01a1 100644 --- a/app/src/main/res/values-iw/strings.xml +++ b/app/src/main/res/values-iw/strings.xml @@ -285,7 +285,7 @@ שמירת תמונות שצולמו באמצעות מצלמה בתוך היישום לאחסון של המכשיר שלך כניסה לחשבון שלך שליחת קובץ יומן - שליחת קובץ יומן למפתחים בדואר אלקטרוני כדי לחפש בעיות עם היישום. לתשומת לבך: יומנים יכולים להכיל מידע מזהה + שליחת קובץ יומן למפתחים בדואר אלקטרוני כדי לחפש בעיות עם היישום. לתשומת ליבך: יומנים יכולים להכיל מידע מזהה לא נמצא דפדפן כדי לפתוח את כתובת ה־URL שגיאה! כתובת ה־URL לא נמצאה העמדה למחיקה @@ -410,7 +410,7 @@ שלב %1$d מתוך %2$d:‏ %3$s הבא הקודם - כבר קיים קובץ בשם %1$s. להמשיך?\n\nלתשומת לבך: לשם הקובץ תווסף סיומת מתאימה אוטומטית. + כבר קיים קובץ בשם %1$s. להמשיך?\n\nלתשומת ליבך: לשם הקובץ תווסף סיומת מתאימה אוטומטית. לא נמצא יישום מפה תואם במכשיר שלך. נא להתקין יישום מפה כדי להשתמש בתכונה זו. תמונות מיקומים @@ -446,9 +446,9 @@ מסתיים ב־: הצגת מסעי פרסום ר\' את מסעי פרסום שמתרחשים כרגע - להשתמש בבורר התמונות GET_CONTENT - כדאי לכבות את זה אם התמונות שלך נשלחות ללא מיקום - נא לוודא שהבורר החדש הזה ב־Android לא מסיר את המיקום מהתמונות שלך. + להשתמש בבורר תמונות מבוסס מסמכים + בורר התמונות החדש של אנדרואיד עלול לאבד מידע על מיקום. יש להפעיל אותו רק אם נראה שהוא באמת בשימוש שלך. + כיבוי של זה עשוי להפעיל את בורר התמונות החדש של אנדרואיד. זה עלול לאבד מידע על מיקום.\n\nיש להקיש על \"מידע נוסף\" כדי לקרוא על זה עוד. מסעי הפרסום לא יופיעו עוד. עם זאת, ניתן להפעיל את ההתראות האלה מחדש בהגדרות בהתאם לרצונך. תכונה זו דורשת חיבור לרשת, נא לבדוק את הגדרות החיבור שלך. אירעה שגיאה בעת עיבוד התמונה. נא לנסות שוב! diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 7370ca1d2a..bf1618b96e 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -214,7 +214,7 @@ ライセンス 緯度経度 情報なし - ベータ版を使ってみましょう! + ベータ版テスターになる Google Playのベータ版チャンネルにオプトインして、新機能やバグ修正プログラムに早期にアクセス 2段階認証コード ログアウトしてもよろしいですか? @@ -407,7 +407,7 @@ アップロードをキャンセル バックボタンを使うとこのアップロードはキャンセルされ、これまでの進行は消失します アップロードを続ける - (画像全点を組み写真にする場合) + (画像全点を組み写真にする場合) この地域を検索 許可を申請 画像を募集している近くの場所を表示するため、あなたの現在の位置情報を提供してもよいですか? @@ -509,6 +509,12 @@ カテゴリを付与できませんでした。 カテゴリを更新 + 題材を更新しようとしています。 + 題材を編集する + + 題材 %1$s が追加されました。 + + 題材を追加できませんでした。 画像をシェア %sはまだ投稿をしたことがありません アカウントを作成しました @@ -613,6 +619,7 @@ 項目 完了 戻る + 題材を編集する カテゴリを編集 適用 リセット diff --git a/app/src/main/res/values-ky/strings.xml b/app/src/main/res/values-ky/strings.xml index ce4dc2e62d..0512aebaf1 100644 --- a/app/src/main/res/values-ky/strings.xml +++ b/app/src/main/res/values-ky/strings.xml @@ -6,6 +6,12 @@ * NR Deblocked --> + Викиказынанын Facebook баракчасы + Викиказынанын логотиби + Викиказынанын сайты + Сыпаттама + Сүрөт + Баары 1 файл жүктөлүүдө %1$d файл жүктөлүүдө @@ -19,23 +25,26 @@ 1 жүктөө башталды %1$d жүктөө башталды - - 1 жүктөө - %1$d жүктөө + + %d жүктөө + %d жүктөө Бул сүрөт %1$s лицензияланат + Купуялык Викиказына Ырастоолор - Колдонуучунун аты + Колдонуучунун ысымы Сырсөз Кирүү - Системага кирүү + Катталуу + Кирүү Сураныч, күтө туруңуз… + Күтө туруңуз… Сиз ийгиликтүү кирдиңиз Системага кирүүдө катачылык бар! Таану катачылыгы! Жүктөө башталды! - %1$s жүктөлүүдө + %1$s жүктөлдү ! Жүктөлгөн файлды көрүү үчүн басыңыз Жүктөө %1$s башталды %1$s жүктөлүүдө @@ -46,10 +55,10 @@ Кезек Жүктөө каталары %1$d%% соңуна чыкты - Жүктөө + Жүктөлүүдө Галереядан алынган Сүрөткө тартуу - Менин жүктөөлөрүм + Жүктөөлөрүм Бөлүшүү Аталышы Баяндамасы @@ -63,14 +72,16 @@ Жүктөө Түрмөктөрдү издөө Сактоо + Жаңылоо %1$s түрмөктөрү табылган жок Уикиказынада Сиздин сүрөттөрдү жеңил табуу үчүн түрмөктөрдү кошуңуз.\n\nТүрмөктөрдү жазууну баштаңыз.\nБул кадамды аттап өтүү үчүн, бул билдирүүнү (же кийинкини) басыңыз. - Түрмөктөр + Категориялар Ырастоолор + Катталуу Тиркеме жөнүндө баштапкы коду ачык тиркемелер, <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">Apache License v2</a> лицензиясынын негизинде чыгарылган <a href=\"https://github.com/commons-app/apps-android-commons\">GitHub</a> шилтемесине баштапкы код. <a href=\" https://github.com/commons-app/apps-android-commons/issues\">Github</a> шилтемесиндеги катачылык. - <a href=\"https://wikimediafoundation.org/wiki/Конфиденциалдуулук_саясаты\">Конфиденциалдуулук саясаты</a> + Купуялык саясаты Тиркеме жөнүндө Шарттуу жооп жөнөтүү (Email) Жакында колдонулган түрмөктөр @@ -90,16 +101,30 @@ Сиздин сүрөттөр дүйнө жүзүндөгү адамдардын билим алышына өбөлгө түзүүдө. Интернетте жарыяланган автордук укукка ээ сүрөттөрдөн, ошондой эле плакаттардан жана китептердин мукабасынан ж.б. четтеңиз. Сизге бул түшүнүктүүбү? - Ооба! - Түрмөктөр - Жүктөлүүдө… + Ооба ! + Категориялар + Жүктөлүүдө… Тандалган жок + Сыпаттама жок Жаңылоо + Макул + Ооба + Жок + Аталыш + Автор Жокко чыгаруу + Жабуу + Жүктөө + Тууралуу + Кабарламалар Викимаалымат Википедия + КБС + Которуу + Тилдер Жокко чыгаруу Жүктөөнү жокко чыгаруу Артка баскычын колдонуу менен бул жүктөө жокко чыгарылат жана сиз ийгиликти жоготосуз Жүктөөнү улантуу + Элементтер diff --git a/app/src/main/res/values-mk/strings.xml b/app/src/main/res/values-mk/strings.xml index 0847306843..fbe0867504 100644 --- a/app/src/main/res/values-mk/strings.xml +++ b/app/src/main/res/values-mk/strings.xml @@ -416,9 +416,9 @@ Завршува: Прикажи походи Погледајте ги тековните походи - Користи го сликоизбирачот GET_CONTENT - Оневозможи ако сликите се подигаат без местоположба - Осигурајте се дека овој нов избирач за Андроид не ги трга местоположбите од вашите слики. + Користи избирач на слики од документи + Новиот избирач на слики на Андроид ризикува да ги изгуби информациите за местоположба. Овозможете ги ако го користите. + Со исклучување на ова ќе го повикате избирачот на слики на Андроид. Ризикува загуба на информации за местоположба.\n\nДопрете на „Прочитајте повеќе“ за повеќе информации. Повеќе нема да ви се прикажуваат походите. Доколку се премислите, таа поставка ќе ја најдете во Нагодувањата. Оваа можност бара да бидете поврзани со мрежата. Проверете ги поставките за поврзување. Се појави грешка при обработката на сликата. Обидете се повторно! diff --git a/app/src/main/res/values-ms/strings.xml b/app/src/main/res/values-ms/strings.xml index d82c1e3546..5301b84eec 100644 --- a/app/src/main/res/values-ms/strings.xml +++ b/app/src/main/res/values-ms/strings.xml @@ -8,6 +8,7 @@ --> Laman Facebook Commons + Kod Sumber Github Commons Logo Commons Tapak Sesawang Commons %d fail sedang dimuat naik @@ -36,9 +37,11 @@ Lupa Kata Laluan? Sedang log masuk Sila tunggu… + Sila tunggu… Berjaya log masuk! Gagal log masuk! - Penentusahan gagal! + Fail tidak dijumpai. Sila cuba fail lain. + Pendaftaran gagal, sila log masuk lagi Pemuatnaikan telah bermula! %1$s telah dimuat naik! Ketik untuk melihat muatan naik anda @@ -162,6 +165,7 @@ Wikidata Wikipedia Commons + Tiada pemberitahuan ditemui Terjemah Bahasa Batalkan diff --git a/app/src/main/res/values-nb/strings.xml b/app/src/main/res/values-nb/strings.xml index e00f31af1c..308d756864 100644 --- a/app/src/main/res/values-nb/strings.xml +++ b/app/src/main/res/values-nb/strings.xml @@ -410,6 +410,7 @@ Slutter: Vis kampanjer Se aktive kampanjer + Bruk dokumentbasert bildevelger Du vil ikke se kampanjer lenger. Du kan slå på dette igjen i innstillingene om du ønsker. Denne funksjonen trenger nettverksforbindelse, sjekk tilkoblingsinnstillingene dine. Feil oppsto under prosessering av bildet. Prøv igjen! diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml new file mode 100644 index 0000000000..7465ef30e7 --- /dev/null +++ b/app/src/main/res/values-night/colors.xml @@ -0,0 +1,5 @@ + + + #1e8cab + #1e8cab + \ No newline at end of file diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 1e199c0da6..a444c493c7 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -435,6 +435,9 @@ Eindigt op: Campagnes weergeven Bekijk de lopende campagnes + Gebruik een op documenten gebaseerde fotokiezer + Met de nieuwe Android-fotokiezer riskeert u het verlies van locatiegegevens. Schakel dit in als u deze lijkt te gebruiken. + Als u dit uitschakelt, kan de nieuwe Android-fotokiezer worden geactiveerd. Daarbij bestaat het risico dat locatie-informatie verloren gaat.\n\nTik op \'Lees meer\' voor meer informatie. U ziet de campagnes niet meer. U kunt deze melding desgewenst echter opnieuw inschakelen in de Instellingen. Voor deze functie is een netwerkverbinding vereist, controleer uw verbindingsinstellingen. Er is een fout opgetreden tijdens het verwerken van de afbeelding. Probeer het opnieuw! diff --git a/app/src/main/res/values-nqo/strings.xml b/app/src/main/res/values-nqo/strings.xml index 84aee64512..af51113266 100644 --- a/app/src/main/res/values-nqo/strings.xml +++ b/app/src/main/res/values-nqo/strings.xml @@ -555,6 +555,9 @@ ߖߌ߬ߦߊ߬ߓߍ߫ ߕߴߦߋ߲߬ ߊ߬ ߞߍ߫ ߌ ߞߐߛߊ߬ߦߌ߬ + ߌ ߣߌ߫ ߛߣߍ߫ ߘߎ߲߬ߘߎ߬ߡߊ߬ ߖߌߦߊߓߍ ߟߎ߬ ߛߎߥߊ߲ߘߌߟߊ߲ ߞߊ߲߬ + ߛߎߥߊ߲ߘߌߟߊ߲ ߣߌ߲߬ ߦߴߌ ߟߊ߫ ߖߌ߬ߦߊ߬ߓߍ߬ ߟߊߦߟߍ߬ߣߍ߲ ߠߎ߬ ߟߋ߬ ߦߌ߬ߘߊ߬ ߟߴߌ ߟߊ߫ ߞߐߡߐ߲ߛ ߞߊ߲߬. + ߣߎߡߊ߲߫ ߝߍ߫ ߖߌߦߊߓߍ ߟߎ߬ ߞߏ߫ ߘߍ߫ ߘߋ߬߸ ߞߐߡߐ߲ߛ ߕߐ߰ߛߙߋ ߦߋ߫ ߞߌߣߌ߲ߝߍߕߊ ߡߍ߲ ߠߎ߬ ߟߊ߫ ߣߌ߲߬، ߏ߬ ߟߎ߬ ߦߴߊ߬ ߦߌ߬ߘߊ߬ ߟߊ߫ ߟߋ߬ ߞߴߌ ߓߘߊ߫ ߓߊ߲߫ ߏ߬ ߟߎ߬ ߟߊߦߟߍ߬ ߟߊ߫.\nߌ ߡߊ߰ ߏ߬ ߟߊ߫ ߞߵߊ߬ ߘߐߡߌ߬ߣߊ߬ ߖߌ߬ߦߊ߬ߓߍ ߡߊߝߟߍߞߏ ߘߐ߫ ߡߎߣߎ߲߬. ߡߊ߲߬ߕߊ߬ߣߌ߲߬ߡߊ߬ߞߏ ߖߌ߬ߦߊ߬ߓߍ ߣߌ߲߬ ߓߘߊ߫ ߓߊ߲߫ ߠߊߦߟߍ߬ ߟߊ߫ ߞߐߡߐ߲ߛ ߞߣߐ߫ ߞߘߐ߬ߡߊ߲߫ ߖߌ߬ߦߊߓߍ ߣߌ߲߬ ߓߍߣߊ߬ ߟߊߕߘߍ߬ ߥߞߌ ߝߙߎߕߎ ߞߊ߬ߣߌ߲߬ߓߊ߮ ߂߀߂߁ ߜߊ߬ߛߊ ߘߐ߫ @@ -575,6 +578,7 @@ ߡߎ߲߬ ߟߐ߬ ߕߐ߫ ߦߋ߫ ߖߌ߬ߦߊ߬ߓߍ ߕߊ߬ ߦߙߐ ߝߙߊ߬ߟߌ ߟߊ߫ ߞߏ ߡߊ߬؟ \nߘߌ߲߬ߞߌߙߊ ߓߟߏߡߟߊ ߟߋ߬ ߦߋ߫ ߓߟߏߡߊߜߍ߲ߠߊ ߟߎ߬ ߘߍ߬ߡߍ߲߬ ߠߊ߫ ߞߵߌ ߟߊ߫ ߖߌ߬ߦߊ߬ߓߍ ߟߎ߬ ߢߌߣߌ߲߫߸ ߡߍ߲ ߞߍ߫ ߕߐ߫ ߦߴߊ߬ ߞߍ߫ ߣߝߊ߬ߡߊ ߘߌ߫. \nߌ ߣߌ߫ ߗߋ߫߹ ߘߌ߲߬ߞߌߙߊ ߝߊ߬ߙߊ߫ ߝߊߙߊ߲ߝߊ߯ߛߌ ߟߎ߬ + API ߞߊߓߋ ߊ߲ߘߙߏߦߌߘ ߦߌߟߡߊ ߞߍߟߊ߲ ߝߊ߲ߓߏ߲ ߞߍߟߊ߲ ߛߎ߯ߦߊ diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 68fdd1ae25..008f203c4e 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -30,6 +30,20 @@ Strona internetowa Commons Wyjdź z selektora lokalizacji Prześlij + Dodaj kolejny opis + Dodaj nowy wkład + Dodaj wkład z kamery + Dodaj wkład ze zdjęć + Dodaj wkład z galerii poprzednich wkładów + Podpisy + Opis języka + Podpis + Opis + Obraz + Wszystkie + Przełącz w górę + Widok wyszukiwania + Stan miejsca Zdjęcie dnia Przesyłanie %1$d pliku @@ -435,6 +449,7 @@ Kończy się na: Wyświetl kampanie Zobacz trwające kampanie + Upewnij się, że ten nowy selektor Androida nie usuwa lokalizacji ze zdjęć. Kampanie już nie będą widoczne. Jednak w razie potrzeby możesz ponownie włączyć to powiadomienie w ustawieniach. Ta funkcja wymaga połączenia sieciowego, sprawdź ustawienia połączenia. Wystąpił błąd podczas przetwarzania obrazu. Spróbuj ponownie! @@ -513,9 +528,9 @@ Nominowane %1$s do usunięcia. Nie powiodło się Nie można zażądać usunięcia. - Selfie - Niewyraźne - Nonsens + selfie, które nie jest używane w żadnym artykule + całkowicie niewyraźne + nonsens, absolutnie nieprzydatny w żadnym artykule Zdjęcie prasowe Losowe zdjęcie z internetu Logo @@ -571,7 +586,7 @@ KLASY POTOMNE KLASY NADRZĘDNE Znaleziono miejsce w pobliżu - Czy jest to zdjęcie miejsca %1$s? + Czy to zdjęcie %1$s? Zakładki Ustawienia Usunięto z zakładek @@ -670,7 +685,7 @@ Gotowe Cofnij Witamy w niestandardowym selektorze zdjęć - Ten selektor pokazuje inaczej zdjęcia, które są już na Commons. + Ten selektor pokazuje, które zdjęcia zostały już przesłane do Commons. W przeciwieństwie do zdjęcia po lewej, zdjęcie po prawej ma logo Commons wskazujące, że zostało już przesłane.\n Dotknij i przytrzymaj, aby wyświetlić podgląd obrazu. Niesamowite Tez plik został już przesłany do Commons. @@ -710,4 +725,23 @@ Błąd podczas wysyłania opinii Jaka jest Twoja opinia? Twoja opinia + Oznacz jako nieprzeznaczone do przesłania + Odznacz jako nie do przesłania + Pokaż już wykonane zdjęcia + Ukrywanie już wykonanych zdjęć + Nie znaleziono więcej obrazów + To zdjęcie zostało już przesłane + Nie można wybrać tego obrazu do przesłania + Wybrano obraz + Obraz oznaczony jako nieprzeznaczony do przesłania + Raport + Zgłoś naruszenie + Zgłoś tego użytkownika + Zgłoś ten komentarz + Prośba o zablokowanie tego użytkownika + Witamy w trybie wyboru na pełnym ekranie + Użyj dwóch palców, aby powiększyć lub pomniejszyć. + Przesuń szybko i długo, aby wykonać następujące czynności: \n- Lewo/Prawo: Przejdź do poprzedniego/następnego \n- Góra: Wybierz\n- Dół: Oznacz jako nieprzeznaczone do przesłania. + Aby skonfigurować swojego awatara rankingu, dotknij \'Ustaw jako awatar\' w menu z trzema kropkami dowolnego obrazu. + Współrzędne nie są dokładnymi współrzędnymi, ale osoba, która przesłała to zdjęcie, uważa, że są wystarczająco blisko. diff --git a/app/src/main/res/values-pms/strings.xml b/app/src/main/res/values-pms/strings.xml index 361ed11706..a871ba283c 100644 --- a/app/src/main/res/values-pms/strings.xml +++ b/app/src/main/res/values-pms/strings.xml @@ -414,9 +414,9 @@ A finiss ai: Campagne ëd visualisassion Vëdde le campagne an cors - Dovré ël selessionator ëd fòto GET_CONTENT - Disativé si soe fòto a son carià sensa locassion - Ch\'a contròla che ës neuv seletor Android a elìmina pa la locassion ëd soe fòto. + Dovré ël seletor ëd fòto basà an s\'un document + Ël neuv seletor ëd fòti d\'Android a arziga ëd perde dj\'anformassion ëd localisassion. Ch\'a lo abìlita s\'a smija ch\'a lo deuvra. + Dëstisselo a podrìa fé parte ël neuv seletor ëd fòto d\'Android. A arziga ëd perde dj\'anformassion ëd localisassion.\n\nSgnaché su \'Lese ëd pi\' për pi d\'anformassion. A vëdrà pi nen le campagna. Comsëssìa, a peul torna abilité costa notìfica ant ij paràmeter, s\'a veul. Sa fonsion a l\'ha damanca ëd na conession ëd rej, për piasì ch\'a contròla ij sò paràmtere ëd conession. A-i è staje n\'eror durant ël tratament ëd la plancia. Ch\'a ancamin-a torna, për piasì! diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 796c669846..2c04d0b733 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -16,6 +16,7 @@ * Maxan * Mello25 * Re demz +* Stephanyb76 * TheEduGobi * TheGabrielZaum * Tks4Fish @@ -34,7 +35,7 @@ Adicionar contribuição da câmera Adicionar contribuição de Fotos Adicionar contribuição da galeria de contribuições anteriores - Legenda + Legendas Descrição do idioma Legenda Descrição @@ -89,7 +90,7 @@ Esqueceu a senha? Cadastre-se Efetuar login - Aguarde… + Por favor, aguarde... Atualizando legendas e descrições Por favor, aguarde... Login bem sucedido @@ -118,7 +119,7 @@ Meus envios Compartilhar Ver página do arquivo - Legenda (obrigatório) + Legenda (Obrigatório) Forneça uma legenda para este arquivo Descrição Legenda @@ -295,7 +296,7 @@ Avalie-nos Perguntas frequentes Guia de usuario - Pular tutoril + Pular Tutorial A Internet não está disponível Erro ao tentar obter as notificações Erro ao buscar a imagem para revisão. Pressione atualizar para tentar novamente. @@ -332,8 +333,8 @@ Mapa Imagem adicionada a %1$s na Wikidata! Falha ao atualizar a entidade Wikidata correspondente! - Definir como imagem de fundo - Imagem de fundo definida! + Definir como papel de parede + Papel de parede definido! Questionário Esta fotografia pode ser carregada? Pergunta diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index d77958e92d..80114be9e7 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -429,6 +429,7 @@ Termina em: Apresentar campanhas Ver as campanhas em andamento + Assegure-se de que este novo seletor Android não remove a localização das suas fotografias. Deixará de ver as campanhas. No entanto, pode reativar esta notificação nas configurações, se desejar. Esta função requer uma ligação de rede. Verifique as suas configurações de ligação, por favor. Ocorreu um erro ao processar a imagem. Tente novamente, por favor! diff --git a/app/src/main/res/values-qq/strings.xml b/app/src/main/res/values-qq/strings.xml index 0372ee70d5..91cb623176 100644 --- a/app/src/main/res/values-qq/strings.xml +++ b/app/src/main/res/values-qq/strings.xml @@ -169,6 +169,7 @@ Refers to the next \'\'\'step\'\'\' in the uploading process. Refers to the previous \'\'\'step\'\'\' in the uploading process. \"Send log file\" is {{msg-wm|Commons-android-strings-send log file}}. + \"Read more\" is {{msg-wm|Commons-android-strings-read help link}}. {{Identical|Done}} \'%1$s\' is replaced by a formatted number (of categories). {{Identical|Please wait}} diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index fd9a71895e..24bedb29f0 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -465,6 +465,9 @@ Заканчивается: Показ кампаний Просмотр текущих кампаний + Использовать средство выбора фотографий на основе документа + Новый инструмент выбора фотографий Android рискует потерять информацию о местоположении. Включите, если вы собираетесь его использовать + Отключение может привести к запуску нового средства выбора фотографий Android. Из-за этого может потеряться информация о местоположении.\n\nНажмите «Подробнее» для получения дополнительной информации. Вы больше не увидите кампаний. Однако, если захочете, вы сможете включить нотификации о кампаниях в настройках. Для этой функции требуется доступ к интернет. Пожалуйста, проверьте своё подключение. Произошла ошибка при загрузке файла. Пожалуйста, попробуйте позднее! diff --git a/app/src/main/res/values-se/strings.xml b/app/src/main/res/values-se/strings.xml new file mode 100644 index 0000000000..e92ce3f422 --- /dev/null +++ b/app/src/main/res/values-se/strings.xml @@ -0,0 +1,241 @@ + + + + Commons Facebook-siidu + Commons váldokoda Githubis + Commons-logo + Commons-neahttasiidu + Guođe báikeválljejeaddji + Sádde + Lasit nuppi govvádusa + Lasit ođđa sisdoalu + Lasit buvttu kameras + Lasit buvttu Govat máhppas + Lasit buvttu ovddit buktogalleriijas + Govvateavsttat + Giellagovvádus + Govvateaksta + Govvádus + Govva + Buot + Jorgal bajás + Ohcama čájeheapmi + Sadjestáhtus + Beaivvi govva + Álggaha bajásluđemiid + Suokkar + Olggosoaidnin + Obbalaš + Máhcahat + Peršuvdnasuodjalus + Commons + Ásahusat + Bajásluđe Commons:ii + Geavaheaddjinamma + Beassansátni + Čálit sisa du Commons Beta dovddaldahkii + Čálit sisa + Vajáldahttetgo beassansáni? + Searvva + Čáliha sisa + Vuordil... + Ođasmáhttá govvateavsttaid ja govvádusaid + Vuordil... + Sisačáliheapmi lihkostuvai! + Sisačáliheapmi ii lihkostuvvan! + Fiila ii gávdnon. Geahččal áinnas eará fiilla. + Autentiseren ii doaibman, čálit mes oktii vel sisa + Bajásluđen álggahuvvui! + Bajásluđen ráiduduvvon (ráddjejuvvon čatnasanvuohki doaimmas) + %1$s bajásluđejuvvon! + Deaddel vai oidno du bajásluđen + Bajásluđeme fiilla: %s + %1$s bajásluđeme + Gárvvisteme bajásluđema %1$s + Fiila %1$s vurken ii lihkostuvvan + %1$s bajásluđen gaskaboddosaččat bissehuvvon + Deaddel vai oidno + Deaddel vai oidno + Mu maŋemus bajásluđemat + Ráiddus + Ii lihkostuvvan + %1$d%% gárvvis + Bajásluđeme + Govvagalleriijas + Govve + Láhkosis + Mu bajásluđemat + Juoge + Čájet fiilasiiddu + Govvateaksta (Gáibiduvvo) + Atte mes govvateavstta dán fiilii + Govvádus + Govvateaksta + Ii sáhte čálihit sisa - fierpmádagas feaila + Beare máŋga eahpelihkostuvvan geahččaleami. Geahččal mat fas moatti minuvtta geahčen + Dađibahábuht, dát geavaheaddji lea hehttejuvvon Commons:is + Fertet addit iežat guovtteceahkat dovddaldatkoda. + Sisačáliheapmi ii lihkostuvvan + Vurke + Nammat dán ráiddu + Rievdadusat + Vurke + Oza kategoriijaid + Oza merkošiid maid du media govahallá (várri, Taj Mahal, jná.) + Vurke + Ođasmahte + Liste + Eai vuos bajásluđemat) + Ii oktage kategoriija heiven oktii %1$s:ain + Ii oktage Wikidata mearkkuš heiven oktii %1$s:ain + %1$s:as eai leat vuolledásit + %1$s:as eai leat badjedásit + Lasit kategoriijaid vai čalmmustáhttat iežat govaid Wikimedia Commonsis.\nÁlgge čállit lasihan dihte kategoriijaid. + Kategoriijat + Ásahusat + Searvva + Vállju govat + Heivehuvvon válljejeaddji + Kategoriija + Ovttadássásaš árvvoštallan + Lassedieđut + Ráhkat ođđa <a href=\"%1$s\">GitHub ášši</a> dieđihit boasttuvuođaid ja evttohusaide. + Diehtosuodjalanvuohki + Ráhkadeaddjit + Lassedieđut + Atte máhcahaga (e-poasttain) + Ii leat ásahuvvon epoastadoaimmaheaddji + Áitto geavahuvvon kategoriijat + Vuordime vuosttaš synkroniserema... + It leat vel bajásluđen ovttage gova. + Geahččal ođđasit + Gaskkalduhte + Go sádden dán gova sisa, de seammás julggaštan ahte lea mu bargu, mas ii leat duogášvuoigatvuohta bargui dahje selfiide, ja muđuige soahpá <a href=\"https://commons.wikimedia.org/wiki/Commons:Policies_and_guidelines\">Wikimedia Commons policies</a>. + Viečča + Dábálaš liseansa + Ane ovddit nama ja govvádusa + Temá + CC0 + Du govat veahkehit skuvlet olbmuid miehtá máilmmi! + Luđe bajás govaid maid dievaslaččat leat ieš govven dahje ráhkadan: + Lunddolaš objeavttat (lieđit, eallit, várit) + Ábas objeavttat (duolbmunsihkkelat, ruovdemáđi stášuvnnat) + Beakkán olbmot (du sátnejođiheaddji, olympiagilvaleaddjit geaigguin leat deaivvadan) + ALE fal bajásluđe: + Selfiid dahje iežat olbmáid govaid + Govaid maid leat luđen Interneahtas + Šearbmagovva eaiggáduvvon appain + Bajásluđen ovdamearka: + Namma: Sydney Operaviessu + Govvideapmi: Sydney Operaviessu nu go oidno nuppebeallái mohki + Kategoriijat: Sydney Operaviessu oarjjál, Sydney Operaviessu go guhkkin oidno + Fála iežat govaid. Ealáskahte Wikipedia ártihkkaliid! + Govat Wikipedias bohtet Wikimedia Commonsis. + Du govat leat veahkkin oahpaheame olbmuid miehtá máilmmi. + Garvve govaid main lea duogášvuoigatvuohta maid leat gávdnan Interneahtas, ja plakáhtaid, girjebirrasiid, jná. + Ipmirdit go? + Jua! + Lassedieđut + Kategoriijat + Luđeme... + Ii guhtege válljejuvvon + Ii leat govvateaksta + Ii gávdno govvádus + Ii digaštallojuvvo + Dovdameahttun liseansa + Ođasmahte + Bivdá vurkenlobi + Gáibiduvvon lohpi: Lohkat olgguldas vurkema. Appa ii beasa du galleriijai dan haga. + Gáibiduvvon lohpi: Čállit olgguldas vurkemii. Appa ii beasa du kamerai/galleriijai dan haga. + Bivdá sadjái lobi + OK + Várrehus + Duppál fiilanamma gávdnon + Vurke + Jua + Ii + Govvateaksta + Namma + Govvádusat + Govvádus + Ságastallan + Vuoigŋadahkki + Vurkenbeaivemearri + Liseansa + Koordináhtat + Ii addon bajás + Šatta Beta geahččaleaddji + Leat go sihkar, ahte háliidat olggos čálihit? + Mediagovva ii lihkostuvvan + Gaskkalduhte + Raba + Gidde + Ruoktu + Vurke + Lassedieđut + Máhcahat + Čálit olggos + Almmuhusat + Árvvoštala + Commons-fiilasiidu + Wikipedia-artihkal + Čálit sisa + Leat go sihkar, ahte háliidat joatkit čálitkeahttá sisa? + Wikidata + Wikipedia + Commons + Dávjá jerrojuvvon gažaldagat + Jorgal + Gielat + Gaskkalduhte + Beaivvi govva + Oza + Ohcan + Kategoriijat + Kárta + Boađus + Joatkke + Leat go sihkar, ahte háliidat gaskkalduhttit dán fiilla vurkema? + Leat go sihkar, ahte háliidat sihkkut dán ohcama? + Sihko + Statistihkka + Dássi + Almmuhusat + Listu + Čuovvovaš + Govat + Sajit + Gaskkalduhte fiilla vurkema + Čuovvovaš govva + Vuoigŋadahkki + Sadji + Govvadieđut + Fiilla vurkema gaskkalduhtton + Ii lihkostuvvan + Logo + Koordináhtaid lasiheapmi ii lihkostuvvan. + Govvateavstta lasiheapmi ii lihkostuvvan. + Rievdat govvádusaid ja govvateavsttaid + Šaldi, musea, hotealla jna. + joatkke + Geavaheaddji + Fiilla vurkema gaskkalduhttomin… + Medialiseansa + Mediadieđut + Čájet kategoriijasiiddu + Sihko govvateavstta ja govvádusa + Loga eambbo + Buot gielaide + Govvádus + LASSEDIEĐUT + Čájet geavaheaddjisiiddu + Rievdat kategoriijaid + Geavat + Lassedieđut + Android-veršuvdna + diff --git a/app/src/main/res/values-skr/error.xml b/app/src/main/res/values-skr/error.xml index 4829c95c2d..99e8dd5bf5 100644 --- a/app/src/main/res/values-skr/error.xml +++ b/app/src/main/res/values-skr/error.xml @@ -3,5 +3,6 @@ * Saraiki --> + اوہو۔ کجھ خراب تھی ڳئے! شکریہ! diff --git a/app/src/main/res/values-skr/strings.xml b/app/src/main/res/values-skr/strings.xml index c4c627470f..aea0e53f1a 100644 --- a/app/src/main/res/values-skr/strings.xml +++ b/app/src/main/res/values-skr/strings.xml @@ -170,6 +170,7 @@ پروفائل شماريات درجہ + ورتیل تصویراں خرابی تھی ڳئی ہے! ونگاراں نیڑے @@ -182,8 +183,12 @@ کتاب نشان کتاب نشان اپ لوڈ منسوخ کرو + اپ لوڈ کرݨ جاری رکھو + ایں علاقے وچ ڳولو + اجازت دی ارداس تھی ڳیا اڳلی تصویر + جیا، کیوں نہ سوپݨا، انتظار کرو۔۔۔ نقل تھی ڳئے ایہ تصویر چھوڑو @@ -202,6 +207,7 @@ کامیابی ونکیاں اپ ڈیٹ کرو کامیابی + موجود ہے میڈیا نشانیاں ترتیباں @@ -229,6 +235,7 @@ نیڑے ورتے ہوئے میݙا رینک + اپ لوڈ منسوخ کرو میڈیا لائسنس میڈیا تفصیلاں ونکی ورقہ ݙیکھو @@ -244,6 +251,7 @@ کمال ہے! ٻیا سکھو اجازت دی لوڑ ہے + ورتݨ آلے دا ورقہ ݙیکھو ونکیاں وچ تبدیلی کرو اعلیٰ اختیارات لاڳو کرو @@ -255,5 +263,7 @@ ڈیوائس ناں نیٹ ورک قسم تہاݙی فیڈ بیک + ٻیاں تصویراں کائنی لبھیاں + تصویر چݨیج ڳئی رپورٹ diff --git a/app/src/main/res/values-sl/strings.xml b/app/src/main/res/values-sl/strings.xml index ca6ee97a6c..3cffbf6769 100644 --- a/app/src/main/res/values-sl/strings.xml +++ b/app/src/main/res/values-sl/strings.xml @@ -430,9 +430,9 @@ Konec: Prikaži akcije Nastavi tekoče kampanje - Uporaba izbirnika fotografij GET_CONTENT - Onemogočite, če se vaše slike naložijo brez lokacije - Zagotovite, da ta novi izbirnik za Android ne odstranjuje lokacije iz vaših slik. + Uporaba izbirnika fotografij na podlagi dokumenta + Novi izbirnik fotografij Android lahko izgubi podatke o lokaciji. Omogočite ga, če ga želite uporabljati. + Izklop te možnosti lahko aktivira novi izbirnik fotografij za Android. S tem tvegate izgubo podatkov o informacijah.\n\nZa več informacij kliknite »Preberi več«. Kampanj ne boste več videli. Če želite, lahko tovrstno obveščanje znova vklopite v nastavitvah. Ta možnost zahteva omrežno povezavo. Prosimo, preverite vaše nastavitve povezave. Pri obdelavi slike je prišlo do napake. Prosimo, poskusite znova! diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml index 77eca2d8df..44f9eda732 100644 --- a/app/src/main/res/values-sr/strings.xml +++ b/app/src/main/res/values-sr/strings.xml @@ -82,6 +82,7 @@ Отпремање датотеке „%1$s” Завршавање отпремања датотеке „%1$s” Отпремање датотеке „%1$s” није успело + Отпремање %1$s паузирано Додирните да бисте видели Ваша недавна отпремања На чекању @@ -118,9 +119,10 @@ Отвори налог Изабране слике Категорија + Преглед слика О апликацији - Софтвер отвореног кода доступан под лиценцом <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">Apache вер. 2</a> Викимедијина Остава и њен лого су заштитни знаци Викимедијине Фондације и користе се са дозволом Викимедијине Фондацине. Ми не одобравамо или подржавмо Викимедијину Фондацију.\n\nАпликација за Викимедијину оставу је апликација отвореног кода која је направљена и која се одржава помоћу грантова и волонтера Викимедијине заједнице. Задужбина Викимедија није укључена у стварање, развој или одржавање апликације. - Направите нови <a href=\"%1$s\">захтев на GitHub-у</a> да бисте пријавили грешке или дали предлоге. + Апликација за Викимедијину оставу је апликација отвореног кода која је направљена и која се одржава помоћу грантова и волонтера Викимедијине заједнице. Задужбина Викимедија није укључена у стварање, развој или одржавање апликације. + Багове и сугестије можете пријавити на <a href=\"%1$s\">GitHub-у</a>. Политика приватности Заслуге О апликацији @@ -185,6 +187,7 @@ Не Натпис Наслов + Приказује Опис Дискусија Аутор @@ -248,8 +251,8 @@ Заиста желите да прескочите пријаву? Мораћете да се пријавите да бисте отпремали слике у будућности. Пријавите се да бисте користили ову функцију - Копирај викитекст у оставу - Викитекст је копиран у оставу + Копирај викитекст + Викитекст је копиран у привремену меморију „У близини” можда не ради како треба. Локација није доступна. Потребна је дозвола за приказивање листе локација у близини Упутства @@ -316,6 +319,7 @@ Не постоје најближа места у близини Нема недавних претрага Да ли сте сигурни да желите да очистите своју историју претраге? + Желите ли заиста да зауставите ово отпремање? Да ли желите да избришете ову претрагу? Историја претраге је избрисана Номиновање за брисање @@ -323,14 +327,14 @@ Достигнућа Профил Статистика - Примљене захвале + Захваљивања Изабране слике Ниво Отпремљене слике - Слике које нису опозване + Слике које нису уклоњене Искоришћене слике Делите своја достигнућа са пријатељима. - Ваш ниво се повећава ако се придржавате ових захтева. Предмети у одељку „Статистика” се не рачунају. + Ваш ниво се повећава како испуњавате доле наведене услове. Ставке у делу „Статистика” се не рачунају. потребно бар: Број слика које сте отпремили на Оставу помоћу било ког софтвера за отпремање Проценат слика који сте отпремили на Оставу, а које нису избрисане @@ -404,9 +408,13 @@ Слање захвалница за %1$s Да ли прати правила ауторских права? Да ли је ово коректно категоризовано? + Да ли је за Оставу? Да ли желите да се захвалите доприносиоцу? + Кликните НЕ да номинујете слику за брисање ако је потпуно бескорисна. Ох, ово чак није категоризовано! Ова слика је под %1$s категорије. + Није за Оставу зато што + Крши ауторска права зато што Следећа слика Да, зашто не Нема коришћених датотека @@ -422,7 +430,7 @@ Копирано Примери добрих слика за отпремање на Оставу Примери слика које нису за отпремање - Прескочи ову слику + Прескочи слику Преузимање није успело!! Не можемо преузети датотеку без дозволе за приступ спољашњој меморији. Управљање EXIF ознакама Одаберите које EXIF желите да сачувате у отпремањима @@ -441,7 +449,7 @@ Нема података за наслов или опис претходне слике Зашто би %1$s требало обрисати? Датотека %1$s је отпремљена од стране: %2$s - Подразумевани језик отписа + Подразумевани језик описа Предлагање за брисање Успешно Номинован %1$s за брисање. @@ -455,6 +463,7 @@ Логотип Зато што је то Успешно + Уреди приказе Успешно координате %1$s су додате. Описи су додати. @@ -462,10 +471,12 @@ Није могуће додавање координата. Није могуће додавање описа. Није могуће додавање натписа. + Уреди описе и натписе Подели слику преко + Немате још увек доприноса %s још није доприносио/ла Налог је отворен. - Текст је копиран у оставу + Текст је копиран у привремену меморију Обавештење је означено прочитаним Дошло је до грешке. Тип места: @@ -484,10 +495,13 @@ Као на уређају Тамна Светла + Укључи локацију? Учитај још Додај слику на Википедији Потврди Упутства + паузирај + настави Паузирано Више Обележивачи @@ -505,6 +519,7 @@ Годишње Недељно Сво време + Отпреми У близини Употребе Мој ранк @@ -512,11 +527,13 @@ Настављам отпремање… Паузирам отпремање… Отказујем отпремање… + Откажи отпремање Приказује Лиценца медија Детаљи медија Прикажи страницу категорије Прикажи страницу ставке + Језик апликације Уклања натпис и опис Детаљније На свим језицима @@ -530,6 +547,7 @@ Нема слика Готово Назад + Одлично Ова слика је већ отпремљена на Оставу. Вики воли споменике Потребна је дозвола @@ -551,4 +569,8 @@ Није пронађено више слика Ова слика је већ отпремљена Одабрана слика + Пријави + Пријави овог корисника + Пријави овај садржај + Захтевај блокаду овог корисника diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index d7faa6235e..d50011a8a0 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -421,6 +421,7 @@ Slutar den: Visa kampanjer Se pågående kampanjer + Se till att denna nya Android-bläddraren inte tar bort platspositionen från dina bilder. Du kommer inte längre se kampanjerna. Om du vill kan du återaktivera denna avisering i inställningarna. Denna funktion kräver nätverksuppkoppling. Kontrollera dina anslutningsinställningar. Fel uppstod när bilden behandlades. Försök igen! diff --git a/app/src/main/res/values-ta/strings.xml b/app/src/main/res/values-ta/strings.xml index dbe054817d..754e986aed 100644 --- a/app/src/main/res/values-ta/strings.xml +++ b/app/src/main/res/values-ta/strings.xml @@ -4,6 +4,7 @@ * Gurulenin * Kaartic * Sank +* Sriveenkat * Yuvipanda --> @@ -11,6 +12,8 @@ காமென்ஸ் கிட்கப் மூலம் பொதுவகம் இலச்சினை காமென்ஸ் இணையதளம் + படம் + அனைத்தும் %1$d கோப்பு தரவேற்றப்படுகிறது %1$d கோப்புகள் தரவேற்றப்படுகின்றன @@ -44,7 +47,7 @@ கோப்பை பதிவேற்றுகிறது: %s %1$s தரவேற்றப்படுகிறது %1$s தரவேற்றம் முடிக்கப்படுகிறது - %1$s பதிவேற்றுவது தோல்வியுற்றது + %1$s பதிவேற்றமானது தோல்வியுற்றது %1$s பதிவேற்றுவது இடைநிறுத்தப்பட்டுள்ளது காண்பதற்கு தட்டுக காண்பதற்கு தட்டுக @@ -219,4 +222,6 @@ தொடர் ரத்து செய் மறு முயற்சி செய் + தேடல் + அண்மைய தேடல்கள்: diff --git a/app/src/main/res/values-tcy/error.xml b/app/src/main/res/values-tcy/error.xml index 89ba9b19d3..5a1b6e6170 100644 --- a/app/src/main/res/values-tcy/error.xml +++ b/app/src/main/res/values-tcy/error.xml @@ -1,6 +1,7 @@ diff --git a/app/src/main/res/values-tcy/strings.xml b/app/src/main/res/values-tcy/strings.xml index 22b95be033..a890f12aa1 100644 --- a/app/src/main/res/values-tcy/strings.xml +++ b/app/src/main/res/values-tcy/strings.xml @@ -1,6 +1,7 @@ @@ -11,6 +12,8 @@ కామన్స్ వెబ్‌సైటు స్థానం ఎంపిక నుండి నిష్క్రమించు పంపించు + మరొక వివరణను జోడించండి + కొత్త సహకారాన్ని జోడించండి నేటి బొమ్మ %1$d ఫైలు అప్‌లోడవుతోంది diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 819e6fa93a..0312e2731c 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -3,6 +3,7 @@ * Alex Khimich * Alexander Yukal * Andriykopanytsia +* Anntinomy * Base * DDPAT * Fifty Pro @@ -445,6 +446,7 @@ Завершується: Показати кампанії Чинні кампанії + Переконайтеся, що цей новий засіб вибору Android не видаляє геоданы з ваших зображень. Ви більше не бачитимете кампаній. Однак Ви можете увімкнути це сповіщення повторно в своїх налаштуваннях, якщо забажаєте. Ця функція вимагає доступу до інтернету. Будь ласка, перевірте своє з\'єднання. Сталася помилка при обробці зображення. Будь ласка, спробуйте ще раз! diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 176156470d..954bd1f2c2 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -1,6 +1,7 @@ - 維基共享資源已當機 + 共享資源崩潰了 哎呀,出錯了! 透過電子郵件告訴我們您先前做了什麼,這將協助我們修復它! 謝謝! diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 73f8f435f7..13359840b0 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -3,6 +3,7 @@ * DinoWP * Diskdance * GuoPC +* HellojoeAoPS * Justincheng12345 * Kly * Koala0090 @@ -42,7 +43,7 @@ 圖片 全部 向上切換 - 搜尋檢視 + 搜索視圖 地點狀態 每日圖片 @@ -129,11 +130,11 @@ 必須提供您的雙重驗證代碼。 登入失敗 上傳 - 命名此組圖像 + 給這個集合命名 修改 上傳 搜尋分類 - 搜尋您的媒體所描寫項目(高山、泰姬瑪哈陵、其它等等。) + 搜尋您的媒體描繪項目(山、泰姬瑪哈陵等) 儲存 重新整理 清單 @@ -146,12 +147,12 @@ 分類 設定 註冊 - 特色圖片 + 精選圖片 自訂選擇器 分類 同行評審 關於 - 維基共享資源應用程式是透過維基媒體社群上的受讓人,與志願者們所建立及維護的開源應用程式。維基媒體基金會並不參與此應用程式的建立、開發,與維護。 + 維基共享資源應用程式是一款開放原始碼應用程式,由維基媒體社群的受助者和志工創建和維護。維基媒體基金會不參與該應用程式的創建、開發或維護。 建立新的<a href=\"%1$s\"> GitHub 問題</a>來回報程式錯誤和提出建議。 隱私權政策 製作群 @@ -177,7 +178,7 @@ CC姓名標示3.0 CC姓名標示-相同方式分享4.0 創用CC姓名標示4.0 - 維基共享資源管理大部份使用在維基百科的圖片。 + 維基共享資源託管維基百科中使用的大部分圖像。 您的圖片有助教育世人! 請上傳完全由您自己拍攝或創作的圖片: 自然事物(花、動物、山峰) @@ -192,7 +193,7 @@ 描述:從對面海灣所看到的雪梨歌劇院 分類:Sydney Opera House from the west(雪梨歌劇院的西側)、Sydney Opera House remote views(雪梨歌劇院的遠方外觀) 貢獻您的圖片,使維基百科的文章更加生動! - 維基百科的圖片,來自維基共享資源。 + 維基百科的圖片來自維基共享資源。 您的圖片可以幫助教育世界各地的人。 避免使用受版權保護的材料,例如從網際網路找來的圖片、海報、書籍封面等 以上您明白了嗎? @@ -204,11 +205,11 @@ 沒有說明 無描述 沒有討論 - 不明授權 + 未知許可證 重新整理 - 請求儲存裝置權限 + 請求存儲權限 必要權限:讀取外部儲存裝置。否則應用程式無法存取您的圖庫。 - 必要權限:寫入外部存儲裝置。否則應用程式無法取用您的相機/圖庫。 + 必要權限:寫入外部存儲裝置。否則應用程式無法訪問您的相機/圖庫。 請求位置權限 警告 @@ -226,15 +227,15 @@ 授權協議 座標 未提供 - 成為測試人員 - 選擇加入我們在 Google Play 上的 beta 測試版本,以提早取用新功能及程式修正 - 雙重驗證代碼 + 成為 Beta 測試員 + 選擇加入我們在 Google Play 上的測試版頻道並儘早訪問新功能和錯誤修復 + 2FA 代碼 您確定要登出嗎? 媒體圖片失敗 找不到子分類 找不到母分類 - 藏王連峰 - 大羊駝 + 藏王山 + 美洲駝 彩虹橋 鬱金香 歡迎來到維基百科 @@ -258,7 +259,7 @@ 維基數據項目 維基百科條目 請盡可能描述媒體內容:拍攝於何處?是顯示什麼事物?有什麼脈絡?請描述對象或人物。透露出一些較不易猜測的訊息,例如是風景的話,可以是一天裡的時間。如果媒體顯示出一些不尋常的事物,請說明不尋常原因。 - 請寫入圖片的簡要描述。首個說明會作為圖片的標題。不能超過 225 個字元。 + 請寫入圖片的簡要描述。首個說明會作為圖片的標題。長度不能超過 255 個字元。 此圖片有以下潛在問題: 圖片太暗。 圖片模糊。 @@ -267,11 +268,11 @@ 請僅上傳您自己拍攝的圖片。不要上傳您從別人臉書帳號裡所找到的圖片。 您仍要上傳此圖片嗎? 連接錯誤 - 更新處理需要有效的網際網路存取。請檢查您的網路連線。 + 上傳過程需要有效的網際網路存取。請檢查您的網路連線。 在圖片中發現的問題 請僅上傳您自己拍攝的圖片。不要上傳您從網路下載來的圖片。 儲存應用程式所提供的截圖 - 將應用程式相機所拍攝的圖片存到您的裝置空間裡 + 將使用應用內相機拍攝的照片保存到您的設備存儲中 登入您的帳號 發送日誌檔案 透過電子郵件寄送日誌檔案給開發人員,來協助除錯應用程式上的問題。注意:日誌可能會包含識別方面的資訊 @@ -279,7 +280,7 @@ 錯誤!查無 URL 提名刪除 此圖片已被提名刪除。 - 有關詳細信息,請參閱網頁 + 詳情請參閱網頁 略過 登入 您確定要略過登入嗎? @@ -299,22 +300,22 @@ 跳過敎程 網路不可用 檢索通知時出錯 - 檢索評審圖片時出錯,請按下「重新整理」按鈕重試。 + 獲取圖像以供審核時出錯。按刷新重試。 查無通知 翻譯 語言 選擇您想要提交翻譯的語言 - 已完成 + 繼續 取消 重試 - 這些是在您的周遭,並且需要圖片來圖解、描述它們本身的維基百科條目之地點。\n\n請點擊「搜尋此區域」來查看地圖,然後在位置啟動周遭搜尋。 + 這些是您附近的地方,需要圖片來說明其維基百科條目。\n\n點擊「搜尋此區域」會鎖定地圖,並啟動對該位置周圍附近的搜尋。 此地點需要照片。 此地點已有照片。 此地點已不存在。 找不到圖片! 載入圖片時發生錯誤。 由%1$s上傳 - 已封鎖 + 被阻止 您被禁止編輯維基共享資源 每日圖片 搜尋 @@ -329,7 +330,7 @@ 分類 項目 特色 - 以流動裝置上傳 + 通過移動端上傳 地圖 圖片已添加到維基數據上的%1$s! 更新所對應的維基數據項目失敗! @@ -340,8 +341,8 @@ 問題 結果 如果您持續上傳需要刪除的圖片,您的帳號可能會被封禁。你確定要結束測驗嗎? - 您上傳的圖片已有%1$s遭到刪除。如果您持續上傳不符收錄標準的圖片,您的帳號可能會遭到封禁。\n\n您要不要重新閱覽教程,然後再做一次測驗,以幫助您分辨哪些類型的圖片可以上傳、哪些不可以? - 自拍照沒有百科價值。除非維基百科上已經有介紹您的條目,否則請不要上傳拍攝您自己的照片。 + 您上傳的圖片已有%1$s遭到刪除。如果您持續上傳需要刪除的圖片,您的帳號可能會遭到封禁。\n\n您要不要重新閱覽教程,然後再做一次測驗,以幫助您分辨哪些類型的圖片可以上傳、哪些不可以? + 自拍照沒有百科價值。除非維基百科上已經有介紹您的條目,否則請不要上傳您自己的照片。 在大多數的國家,上傳古蹟或室外景的照片是沒有問題的。請留意,臨時性的戶外裝置藝術常常是有著作權保護的,不可上傳。 網站的螢幕截圖被視為衍生作品,受到該網站本身所有著作權保護。這些截圖可以在獲得原作者許可之後使用。若未經許可,您根據他們的作品而創作的任何藝術品,在法律上均會被視為未經原作者許可的複製品。 收錄高畫質圖像是維基共享資源的目標之一,所以不應該上傳模糊的圖片。儘量試著只上傳光線良好的優良圖片。 @@ -370,7 +371,7 @@ 統計內容 已收到的感謝 特色圖片 - 透過「附近地點」的圖片 + 圖片來自“附近的地方” 等級 已上傳的圖片 沒有被還原回復的圖片 @@ -379,7 +380,7 @@ 在您符合這些需求時,您的等級會增加。在「統計內容」裡的項目,則不會計算到您的等級。 最低要求: 您透過任一上傳軟體將圖片上傳至維基共享資源的數目 - 您所上傳至共享資源且尚未被刪除掉的圖片數目 + 您上傳到維基共享資源的圖像中未被刪除的百分比 您所上傳至維基共享資源且被使用在維基百科條目中的圖片數目 發生錯誤! 共享資源通知 @@ -391,12 +392,12 @@ 通知 通知(已讀) 顯示附近地點通知 - 顯示用於缺少圖片之最近地點的應用程式所提供通知 + 顯示最近需要圖片的地點的應用內通知 清單 儲存裝置權限 - 為了上傳圖片,我們需要存取裝置的外部儲存空間的權限。 + 為了上傳圖像我們需要您的許可以訪問您設備的外部存儲。 您不會再看到缺少圖片的最近地點。然而,若您有意的話您可以在設定裡重新啟動此通知。 - 步驟 %2$d 之 %1$d:%3$s + %2$d之步驟%1$d:%3$s 下一個 上一個 檔案名稱為「%1$s」的檔案已存在,您確定要繼續嗎?\n\n註:合適的後綴會自動添加到檔案名稱。 @@ -413,35 +414,35 @@ 我認為這會干擾到我的隱私 我改變我的主意,我不想要公開可見 真抱歉,此圖片對於百科全書沒有意義 - 由我自己上傳於 %1$s,使用在 %2$d 個條目。 + 我自己在%1$s上上傳,在%2$d篇條目中使用。 歡迎來到維基共享資源!\n\n透過觸碰添加按鈕來上傳您的首個多媒體內容。 未選擇分類 不帶分類的圖片很難有機會被利用到,您確定您要不選擇分類來繼續嗎? 沒有選擇描寫 - 帶有描寫的圖片會更容易被找到,並且更可能被拿來使用。您確定您要不選擇描寫來繼續嗎? + 帶有描述的圖像更容易找到並且更有可能被使用。您確定要繼續而不選擇描述嗎? 取消上傳 - 使用倒退按鈕將會取消此上傳,您並且會失去您的進度 + 使用後退按鈕將取消此上傳,並且您將丟失進度 繼續上傳 (在集合的所有圖片) 搜尋此區域 權限請求 - 您是否願意讓我們知道您的目前位置,以顯示附近缺少圖片的地點? - 沒有位置權限會無法顯示附近缺少圖片的地點 + 您希望我們使用您當前的位置來顯示最近的需要圖片的地點嗎? + 沒有位置權限,無法顯示需要圖片的最近地點 永遠不再詢問 要求位置權限 當需要附近地點通知卡片的檢視功能時,要求位置權限。 - 發生了一些狀況,我們無法索取您的成果 - 您做出的貢獻多到讓我們的成果系統無法因應,此為終極成果。 + 出了點問題,我們無法獲取您的成就 + 您做出的貢獻多到讓我們的成就計算系統無法因應,此為終極成果。 結束於: 顯示活動 檢視發生中的活動 - 使用 GET_CONTENT 照片點選器 - 如果您上傳缺少位置資訊的圖片,請停用 - 請確認這個新的 Android 點選器不會從您的圖片裡刪除位置資訊。 + 使用基於照片點選器的文件 + 新的 Android 照片點選器有遺失位置資訊的風險。若您正在使用該點選器,請啟用它。 + 關閉這個可能會觸發新的 Android 照片點選器。該點選器有遺失位置資訊的風險。\n\n請輕觸「閱讀更多」來獲得更多相關資訊。 您不會再看到活動。然而,若您有意的話您可以在設定裡重新啟動此通知。 此功能需要連接到網路,請檢查您的連線設定。 處理圖片時出現錯誤。請重試! - 取得編輯用訊標 + 獲取用於編輯的令牌 添加用於分類檢查的模板 請求%1$s的分類檢查 請求分類檢查 @@ -457,8 +458,8 @@ 發送感謝:失敗 向%1$s發送感謝 這是否遵守版權規範? - 這有分類正確嗎? - 這有符合收錄範圍嗎? + 這是正確分類的嗎? + 這在範圍內嗎? 您想要向貢獻者感謝嗎? 若此圖片無用,點擊「否」來提名刪除。 商標、螢幕截圖、電影海報通常都會違反版權。\n點擊「否」來提名刪除此圖片。 @@ -470,7 +471,7 @@ 下一張圖片 是的,為何不呢 點擊此按鈕會提供給您其它近期從維基共享資源上傳的圖片 - 您可以檢閱來改善維基共享資源的品質。\n檢閱的三個項目有:\n- 此圖片是否符合範圍?\n當您輕觸「否」(代表不在符合範圍),您會對此圖片添加刪除提名模板。\n\n- 此圖片是否遵守版權規範?\n當您輕觸「否」(代表未遵守版權規範),您會對此圖片添加刪除提名模板。\n\n- 此圖片是否有分類正確?\n當您輕觸「否」(代表分類不正確),您會對此圖片添加分類請求模板。\n\n如果一切都沒問題,就不會對圖片添加模板,您另外可以對貢獻者表達感謝。 + 您可以檢閱來改善維基共享資源的品質。\n檢閱的三個指標有:\n- 此圖片是否符合範圍?\n當您輕觸「否」(代表不在符合範圍),您會對此圖片添加刪除提名模板。\n\n- 此圖片是否遵守版權規範?\n當您輕觸「否」(代表未遵守版權規範),您會對此圖片添加刪除提名模板。\n\n- 此圖片是否有分類正確?\n當您輕觸「否」(代表分類不正確),您會對此圖片添加分類請求模板。\n\n如果一切都沒問題,就不會對圖片添加模板,您另外可以對貢獻者表達感謝。 沒有圖片被使用到 沒有圖片被還原回復 未上傳圖片 @@ -481,13 +482,13 @@ 檢視未讀 選擇圖片時發生錯誤 請稍待… - 特色圖片是出自於高水準技巧的攝影師或繪圖師,且被維基共享資源社群挑選為在站台上的最高品質圖片。 - 以「附近地點」所上傳的圖片,是透過找出在地圖上地點來上傳的。 - 此功能允許編輯者透過利用在歷史頁面或是差異頁面上的感謝連結,來發送感謝通知給做出有用編輯的使用者。 + 特色圖片是來自高技能攝影師和插畫家的圖片,維基共享資源社區將其選為網站上最高質量的圖片。 + 通過附近位置上傳的圖片是指那些使用地圖上發現位置功能上傳的圖片。 + 此功能允許編輯者通過使用歷史頁面或差異頁面上的小感謝鏈接向進行有用編輯的用戶發送感謝通知。 複製到後續媒體 已複製 - 上傳到維基共享資源的優良圖片範例 - 未上傳範例圖片 + 上傳到共享資源的好圖片示例 + 未上傳的範例圖片 略過此圖片 下載失敗!因為缺少外部儲存裝置權限緣故,我們無法下載檔案。 管理 EXIF 標籤 @@ -496,16 +497,16 @@ 版權 位置 相機型號 - 透鏡型號 + 鏡頭型號 序號 軟體 - 已拒絕媒體位置存取 - 我們無法從您上傳的圖片裡自動取得位置資料。請在提交前為每張圖片添加恰當的位置 + 媒體位置訪問被拒絕 + 我們可能無法自動從您上傳的圖片中獲取位置數據。提交前請為每張圖片添加適當的位置 從您的手機上直接上傳照片到維基共享資源,立即下載共享資源應用程式:%1$s 分享應用程式透過… 圖片資訊 找不到分類 - 找不到描寫 + 沒有找到描述 已取消上傳 前一張圖片的標題或描述沒有資料 為何應刪除%1$s? @@ -519,7 +520,7 @@ 未在任何條目中使用的自拍照 完全糢糊 無意義,無法使用在任何條目 - 按壓照片 + 新聞照片 來自網路的隨機照片 標誌 違反全景自由 @@ -565,7 +566,7 @@ 存在 需要照片 地點類型: - 橋樑、美術館、旅館等。 + 橋樑、博物館、酒店等 登入時發生一些問題,您必須重新設定您的密碼!! 媒體 子類別 @@ -593,7 +594,7 @@ - 添加分類到此圖片以增加可用性。 - 添加此圖片到相關但尚未有圖片的維基百科條目。 添加圖片到維基百科 - 您想要添加此圖片到%1$s維基百科條目上嗎? + 您想將此圖片添加到%1$s語言維基百科條目中嗎? 確認 說明 1. 使用以下wikitext: @@ -617,7 +618,7 @@ 使用者 數量 設定成排行榜頭像 - 正在設定成頭像,請稍待 + 正在設置頭像,請稍候 頭像設定 設定新頭像發生錯誤,請重試 設定成頭像 @@ -641,9 +642,9 @@ 您已啟用限制連線模式。在您停用此模式前,所有上傳將會暫停。 限制連線模式啟用中。 請寫一個簡短說明來描述您的圖片所展示內容。在描述中,請說出是什麼內容讓圖片有趣、典型、或罕見,並解釋來龍去脈(可見或不可見)。另外盡可能使用確切的行話術語。 - 請找出並選擇該圖片所描繪出的所有概念,並盡可能具體。若圖片描繪了多個項目,請依據合理的原因內將項目給全部選擇。如果有可用的特定標籤,就不必選擇通用標籤。 + 請找出並選擇該圖片所描繪出的所有概念,並盡可能具體。若圖片描繪了多個項目,請依據合理的原因選擇全部項目。如果有可用的特定標籤,就不必選擇通用標籤。 請選擇適當的分類。與描寫不同,分類僅使用英文。 - 維基共享資源讓您的圖片可重複使用,並可讓任何人修改。您想要放棄所有使用權嗎?您想要被標示出嗎?您想要依據相同的授權條款來改編內容嗎? + 維基共享資源讓您的圖片可重複使用,並可讓任何人改編。您想要放棄所有使用權嗎?您想要被標示出嗎?您想要改編內容使用相同的授權條款嗎? 描述 媒體授權條款 媒體詳細資料 @@ -689,7 +690,7 @@ 編輯描寫 編輯分類 進階選項 - 您可以自定義附近查詢。若發生錯誤,請重新設定並套用。 + 您可以自定義附近查詢。如果出現錯誤,請重置並應用。 套用 重新設定 位置資料能幫助 Wiki 編輯者來找到您的圖片,來讓圖片更有用。\n您近期上傳的內容沒有位置。\n因此我們建議您啟動在您的相機應用程式設定裡的位置。\n非常感謝您的上傳! @@ -700,7 +701,7 @@ 詳細資料 成果僅在生產版本可用,請查閱開發者文件。 排行榜僅在生產版本可用,請查閱開發者文件。 - 請只上傳由您自己拍攝的圖片。上傳受版權保護圖片的使用者將會被阻擋。Beta測試版本也適用此規則。感謝您測試此應用程式! + 請僅上傳您自己拍攝的照片。受版權保護的圖像的上傳者將被阻止。這也適用於 beta 版本。感謝您測試該應用程序! 請取消選擇您不要公開分享的資訊。 API 層級 Android 版本 @@ -721,10 +722,10 @@ 無法選擇此圖片來上載 已選圖片 圖片標記為不上傳 - 回報 - 回報違反行為 - 回報該名使用者 - 回報這份內容 + 報告 + 舉報違規行為 + 檢舉此使用者 + 舉報此內容 請求封鎖該名使用者 歡迎使用全螢幕選擇模式 用兩根手指來放大和縮小。 diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index b9dd4f9867..77e0d0a8fa 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -29,6 +29,7 @@ * Stang * StarrySky * Tranve +* U.T. * Vikarna * VulpesVulpes825 * Willy1018 @@ -46,6 +47,7 @@ * 神樂坂秀吉 * 铁桶 * 阿pp +* 410063005 --> 共享资源Facebook页面 @@ -235,6 +237,7 @@ 请求位置权限 确定 警告 + 发现重复的文件名 上传 @@ -457,6 +460,7 @@ 结束于: 显示活动 显示正在进行的活动 + 将此关闭可能会触发新的 Android 照片选择器。这可能会有丢失位置信息的风险。\n\n点击“阅读更多”以获取更多信息。 你不会再看到这个活动。如果需要可以在设置里重新启用此通知。 这个功能需要网络连接,请检查你的网络设置。 处理图像时出错。请再试一次! @@ -518,6 +522,8 @@ 镜头型号 序列号 软件 + 已拒绝访问媒体位置 + 我们可能无法自动从你上传的图片中获取位置数据。提交前请为每张图片添加适当的位置 直接在您手机上的维基共享资源应用中上传照片。立即下载共享资源应用:%1 分享到... 图像信息 @@ -539,6 +545,7 @@ 按压图片 来自网络的随机图片 标志 + 侵犯全景自由 由于他是 正在尝试更新分类。 分类更新 @@ -587,7 +594,7 @@ 子类别 父类别 找到附近地点 - 这是地点%1$s的照片吗? + 这是%1$s的照片吗? 书签 设置 已从书签中移除 @@ -744,4 +751,6 @@ 欢迎使用全屏选择模式 用两根手指放大和缩小。 快速长距离滑动来执行以下操作:\n- 向左/右:前往上一个/下一个\n- 向上:选择\n- 向下:标记为不上传 + 要设置你的排行榜头像,请点击任意图片上三点式菜单中的\"设置为头像\" + 图片坐标并不是准确的坐标,但上传这张图片的人认为它们足够接近。 diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 787bea2a52..dd0750baeb 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -79,5 +79,6 @@ #EDEDED #339966 + #1e8cab diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 098818abfd..515b6fdda8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -62,6 +62,7 @@ Settings Upload to Commons + Upload in progress Username Password Log in to your Commons Beta account @@ -75,6 +76,9 @@ Login success! Login failed! File not found. Please try another file. + Maximum retry limit reached! Please cancel the upload and try again + Turn battery optimization off? + Uploading more than 3 images works more reliably when the battery optimization is turned off. Please turn battery optimization off for the Commons app from the settings for a smooth upload experience. \n\nPossible steps to turn battery optimization off:\n\nStep 1: Tap on the \'Settings\' button below.\n\nStep 2: Switch from \'Not optimized\' to \'All apps\'.\n\nStep 3: Search for \"Commons\" or \"fr.free.nrw.commons\".\n\nStep 4: Tap it and select \'Don\'t optimize\'.\n\nStep 5: Press \'Done\'. Authentication failed, please login again Upload started! Upload queued (limited connection mode enabled) @@ -190,6 +194,8 @@ Required permission: Read external storage. App cannot access your gallery without this. Required permission: Write external storage. App cannot access your camera/gallery without this. Requesting Location Permission + Record location for in-app shots + Enable this to record location with in-app shots in case the device camera does not record it OK Warning Duplicate File Name found @@ -274,6 +280,7 @@ Copy the wikitext to the clipboard The wikitext was copied to the clipboard Nearby might not work properly, Location not available. + Location access denied. Please set your location manually to use this feature. Permission required to display a list of nearby places Directions @@ -441,9 +448,16 @@ Upload your first media by tapping on the add button. Ends on: Display campaigns See the ongoing campaigns - Use GET_CONTENT photo picker - Disable if your pictures get uploaded without location - Please make sure that this new Android picker does not strip location from your pictures. + Allow the app to fetch location in case the camera does not record it. Some device cameras do not record location. In such cases, letting the app fetch and attach location to it makes your contribution more useful. You may change this any time from the Settings + Allow + Dismiss + Please turn on location access from the Settings and try again. \n\nNote: The upload may not have location if app is unable to retrieve location from device within a short interval. + In-app camera needs location permission to attach it to your images in case location is not available in EXIF. Please allow the app to access your location and try again.\n\nNote: The upload may not have location if app is unable to retrieve location from device within a short interval. + The app would not record location along with in-shots due to lack of location permission + The app would not record location along with in-shots as the GPS is turned off + Use document based photo picker + The new Android photo picker risks losing location information. Enable if you seem to be using it. + Turning this off could trigger the new Android photo picker. It risks losing location information.\n\nTap on \'Read more\' for more information. You won\'t see the campaigns anymore. However, you can re-enable this notification in Settings if you wish. This function requires network connection, please check your connection settings. @@ -609,6 +623,7 @@ Upload your first media by tapping on the add button. For best results, choose the High Accuracy mode. Turn on location? Nearby needs location enabled to work properly + You need to give access to your current location to set location automatically. Did you shoot these two pictures at the same place? Do you want to use the latitude/longitude of the picture on the right? Load More No places found, try changing your search criteria. diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 002ba34a36..2de2481294 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -10,11 +10,11 @@ @color/achievement_background_dark @color/drawerHeader_background_dark @color/tutorial_background_dark - @color/primaryTextColor - @color/primaryColor - @color/primaryDarkColor - @color/primaryColor - @color/primaryColor + @color/primary_color_night + @color/primary_color_night + @color/primary_color_night + @color/primary_color_night + @color/primary_color_night @color/button_blue_dark @color/button_blue_dark @color/white diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 17360bd2e9..8ac890545d 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -19,13 +19,6 @@ - - - - - - + + + + + + + android:key="inAppCameraLocationPref" + android:title="@string/in_app_camera_location_permission_title" + android:summary="@string/in_app_camera_location_switch_pref_summary"/> + + + + + + + +