diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml
index a4682fd3c..dcbba0597 100644
--- a/.github/ISSUE_TEMPLATE/bug-report.yml
+++ b/.github/ISSUE_TEMPLATE/bug-report.yml
@@ -70,7 +70,7 @@ body:
required: false
- type: textarea
attributes:
- label: Screen-shots
+ label: Screenshots
description: Add screenshots related to the issue (if available). Can be created by pressing the Volume Down and Power Button at the same time on Android 4.0 and higher.
validations:
required: false
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 72fda2f5d..575aa6a32 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,31 @@
# Wikimedia Commons for Android
+## v6.0.2
+
+### What's changed
+* Addressed a bug that prevented the keyboard from appearing in various text fields, such as on the upload wizard
+* Links in the "File usages" list are now clickable and will take you to the correct page.
+* Titles for file usages are now clearer and easier to understand
+* Bug fixes and stability improvements
+
+## v6.0.1
+
+### What's changed
+* The app now supports Android 15 with an improved user interface
+* Enhanced Nearby with robust and more reliable labels
+* Bug fixes and stability improvements
+
+## v5.6.1
+
+### What's changed
+* The app no longer uploads images to Wikidata if one exists already for a given item
+* File usage displays correctly now
+* No more infinite circular progress bar on nominating an image for deletion
+* Enhanced location updates while using GPS
+* Author/uploader names are now available in Media Details for Commons licensing compliance
+* Improved usage of popups in Nearby
+* Bug fixes and stability improvements
+
## v5.5.0
### What's changed
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 2e391a24f..4bd228b75 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -18,14 +18,14 @@ if (isRunningOnTravisAndIsNotPRBuild) {
android {
namespace = "fr.free.nrw.commons"
- compileSdk = 34
+ compileSdk = 35
defaultConfig {
applicationId = "fr.free.nrw.commons"
minSdk = 21
- targetSdk = 34
- versionCode = 1053
- versionName = "5.5.0"
+ targetSdk = 35
+ versionCode = 1058
+ versionName = "6.0.2"
setProperty("archivesBaseName", "app-commons-v$versionName-" + getBranchName())
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
@@ -226,6 +226,7 @@ dependencies {
implementation(libs.rxbinding)
implementation(libs.rxbinding.appcompat)
implementation(libs.facebook.fresco)
+ implementation(libs.facebook.fresco.middleware)
implementation(libs.apache.commons.lang3)
// UI
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index d56a874b5..d83d645dd 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -57,8 +57,7 @@
tools:replace="android:appComponentFactory">
+ android:exported="false" />
@@ -101,7 +100,6 @@
android:name=".upload.UploadActivity"
android:configChanges="orientation|screenSize|keyboard"
android:exported="true"
- android:hardwareAccelerated="false"
android:icon="@mipmap/ic_launcher"
android:windowSoftInputMode="adjustResize">
diff --git a/app/src/main/java/fr/free/nrw/commons/AboutActivity.kt b/app/src/main/java/fr/free/nrw/commons/AboutActivity.kt
index ebbb4097a..865ad3ddb 100644
--- a/app/src/main/java/fr/free/nrw/commons/AboutActivity.kt
+++ b/app/src/main/java/fr/free/nrw/commons/AboutActivity.kt
@@ -19,6 +19,7 @@ import fr.free.nrw.commons.utils.ConfigUtils.getVersionNameWithSha
import fr.free.nrw.commons.utils.DialogUtil.showAlertDialog
import java.util.Collections
import androidx.core.net.toUri
+import fr.free.nrw.commons.utils.applyEdgeToEdgeTopInsets
import fr.free.nrw.commons.utils.handleWebUrl
import fr.free.nrw.commons.utils.setUnderlinedText
@@ -47,6 +48,7 @@ class AboutActivity : BaseActivity() {
*/
binding = ActivityAboutBinding.inflate(layoutInflater)
val view: View = binding!!.root
+ applyEdgeToEdgeTopInsets(binding!!.toolbarLayout)
setContentView(view)
setSupportActionBar(binding!!.toolbarBinding.toolbar)
diff --git a/app/src/main/java/fr/free/nrw/commons/CommonsApplication.kt b/app/src/main/java/fr/free/nrw/commons/CommonsApplication.kt
index 90ab0393a..89fdaa055 100644
--- a/app/src/main/java/fr/free/nrw/commons/CommonsApplication.kt
+++ b/app/src/main/java/fr/free/nrw/commons/CommonsApplication.kt
@@ -15,9 +15,8 @@ import com.facebook.drawee.backends.pipeline.Fresco
import com.facebook.imagepipeline.core.ImagePipelineConfig
import fr.free.nrw.commons.auth.LoginActivity
import fr.free.nrw.commons.auth.SessionManager
-import fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao
-import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao
-import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao
+import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable
+import fr.free.nrw.commons.bookmarks.pictures.BookmarksTable
import fr.free.nrw.commons.category.CategoryDao
import fr.free.nrw.commons.concurrency.BackgroundPoolExceptionHandler
import fr.free.nrw.commons.concurrency.ThreadPoolService
@@ -257,8 +256,8 @@ class CommonsApplication : MultiDexApplication() {
} catch (e: SQLiteException) {
Timber.e(e)
}
- BookmarkPicturesDao.Table.onDelete(db)
- BookmarkItemsDao.Table.onDelete(db)
+ BookmarksTable.onDelete(db)
+ BookmarkItemsTable.onDelete(db)
}
diff --git a/app/src/main/java/fr/free/nrw/commons/OkHttpConnectionFactory.kt b/app/src/main/java/fr/free/nrw/commons/OkHttpConnectionFactory.kt
index d15c72f57..1c28d5fe4 100644
--- a/app/src/main/java/fr/free/nrw/commons/OkHttpConnectionFactory.kt
+++ b/app/src/main/java/fr/free/nrw/commons/OkHttpConnectionFactory.kt
@@ -50,7 +50,7 @@ object OkHttpConnectionFactory {
}
}
-private class CommonHeaderRequestInterceptor : Interceptor {
+class CommonHeaderRequestInterceptor : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request().newBuilder()
diff --git a/app/src/main/java/fr/free/nrw/commons/WelcomeActivity.kt b/app/src/main/java/fr/free/nrw/commons/WelcomeActivity.kt
index 439ed1e92..0882ba117 100644
--- a/app/src/main/java/fr/free/nrw/commons/WelcomeActivity.kt
+++ b/app/src/main/java/fr/free/nrw/commons/WelcomeActivity.kt
@@ -9,6 +9,7 @@ import fr.free.nrw.commons.databinding.ActivityWelcomeBinding
import fr.free.nrw.commons.databinding.PopupForCopyrightBinding
import fr.free.nrw.commons.quiz.QuizActivity
import fr.free.nrw.commons.theme.BaseActivity
+import fr.free.nrw.commons.utils.applyEdgeToEdgeAllInsets
import fr.free.nrw.commons.utils.ConfigUtils.isBetaFlavour
class WelcomeActivity : BaseActivity() {
@@ -23,6 +24,7 @@ class WelcomeActivity : BaseActivity() {
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityWelcomeBinding.inflate(layoutInflater)
+ applyEdgeToEdgeAllInsets(binding!!.welcomePager.rootView)
setContentView(binding!!.root)
isQuiz = intent?.extras?.getBoolean("isQuiz", false) ?: false
diff --git a/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.kt b/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.kt
index a606d639f..688f508ae 100644
--- a/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.kt
+++ b/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.kt
@@ -22,6 +22,7 @@ import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.app.NavUtils
import androidx.core.content.ContextCompat
+import androidx.core.view.WindowCompat
import fr.free.nrw.commons.BuildConfig
import fr.free.nrw.commons.CommonsApplication
import fr.free.nrw.commons.R
@@ -32,11 +33,13 @@ import fr.free.nrw.commons.contributions.MainActivity
import fr.free.nrw.commons.databinding.ActivityLoginBinding
import fr.free.nrw.commons.di.ApplicationlessInjection
import fr.free.nrw.commons.kvstore.JsonKvStore
+import fr.free.nrw.commons.utils.applyEdgeToEdgeAllInsets
import fr.free.nrw.commons.utils.AbstractTextWatcher
import fr.free.nrw.commons.utils.ActivityUtils.startActivityWithFlags
import fr.free.nrw.commons.utils.ConfigUtils.isBetaFlavour
import fr.free.nrw.commons.utils.SystemThemeUtils
import fr.free.nrw.commons.utils.ViewUtil.hideKeyboard
+import fr.free.nrw.commons.utils.handleKeyboardInsets
import fr.free.nrw.commons.utils.handleWebUrl
import io.reactivex.disposables.CompositeDisposable
import timber.log.Timber
@@ -79,7 +82,14 @@ class LoginActivity : AccountAuthenticatorActivity() {
delegate.installViewFactory()
delegate.onCreate(savedInstanceState)
+ WindowCompat.getInsetsController(window, window.decorView)
+ .isAppearanceLightStatusBars = !isDarkTheme
+
+ WindowCompat.setDecorFitsSystemWindows(window, false)
+
binding = ActivityLoginBinding.inflate(layoutInflater)
+ applyEdgeToEdgeAllInsets(binding!!.root)
+ binding?.aboutPrivacyPolicy?.handleKeyboardInsets()
with(binding!!) {
setContentView(root)
@@ -92,7 +102,19 @@ class LoginActivity : AccountAuthenticatorActivity() {
aboutPrivacyPolicy.setOnClickListener { onPrivacyPolicyClicked() }
signUpButton.setOnClickListener { signUp() }
loginButton.setOnClickListener { performLogin() }
- loginPassword.setOnEditorActionListener(::onEditorAction)
+ loginPassword.setOnEditorActionListener { textView, actionId, keyEvent ->
+ if (binding!!.loginButton.isEnabled && isTriggerAction(actionId, keyEvent)) {
+ if (actionId == EditorInfo.IME_ACTION_NEXT && lastLoginResult != null) {
+ askUserForTwoFactorAuthWithKeyboard()
+ true
+ } else {
+ performLogin()
+ true
+ }
+ } else {
+ false
+ }
+ }
loginPassword.onFocusChangeListener =
View.OnFocusChangeListener(::onPasswordFocusChanged)
@@ -113,6 +135,39 @@ class LoginActivity : AccountAuthenticatorActivity() {
}
}
+ @VisibleForTesting
+ fun askUserForTwoFactorAuthWithKeyboard() {
+ if (binding == null) {
+ Timber.w("Binding is null, reinitializing in askUserForTwoFactorAuthWithKeyboard")
+ binding = ActivityLoginBinding.inflate(layoutInflater)
+ setContentView(binding!!.root)
+ }
+ progressDialog!!.dismiss()
+ if (binding != null) {
+ with(binding!!) {
+ twoFactorContainer.visibility = View.VISIBLE
+ twoFactorContainer.hint = getString(if (lastLoginResult is LoginResult.EmailAuthResult) R.string.email_auth_code else R.string._2fa_code)
+ loginTwoFactor.visibility = View.VISIBLE
+ loginTwoFactor.requestFocus()
+
+ val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
+ imm.showSoftInput(loginTwoFactor, InputMethodManager.SHOW_IMPLICIT)
+
+ loginTwoFactor.setOnEditorActionListener { _, actionId, event ->
+ if (actionId == EditorInfo.IME_ACTION_DONE ||
+ (event != null && event.keyCode == KeyEvent.KEYCODE_ENTER && event.action == KeyEvent.ACTION_DOWN)) {
+ performLogin()
+ true
+ } else {
+ false
+ }
+ }
+ }
+ } else {
+ Timber.e("Binding is null in askUserForTwoFactorAuthWithKeyboard after reinitialization attempt")
+ }
+ showMessageAndCancelDialog(getString(if (lastLoginResult is LoginResult.EmailAuthResult) R.string.login_failed_email_auth_needed else R.string.login_failed_2fa_needed))
+ }
override fun onPostCreate(savedInstanceState: Bundle?) {
super.onPostCreate(savedInstanceState)
delegate.onPostCreate(savedInstanceState)
@@ -236,7 +291,7 @@ class LoginActivity : AccountAuthenticatorActivity() {
} else false
private fun isTriggerAction(actionId: Int, keyEvent: KeyEvent?) =
- actionId == EditorInfo.IME_ACTION_DONE || keyEvent?.keyCode == KeyEvent.KEYCODE_ENTER
+ actionId == EditorInfo.IME_ACTION_NEXT || actionId == EditorInfo.IME_ACTION_DONE || keyEvent?.keyCode == KeyEvent.KEYCODE_ENTER
private fun skipLogin() {
AlertDialog.Builder(this)
@@ -286,14 +341,14 @@ class LoginActivity : AccountAuthenticatorActivity() {
Timber.d("Requesting 2FA prompt")
progressDialog!!.dismiss()
lastLoginResult = loginResult
- askUserForTwoFactorAuth()
+ askUserForTwoFactorAuthWithKeyboard()
}
- override fun emailAuthPrompt(loginResult: LoginResult, caught: Throwable, token: String?) {
+ override fun emailAuthPrompt(loginResult: LoginResult, caught: Throwable, token: String?) = runOnUiThread {
Timber.d("Requesting email auth prompt")
progressDialog!!.dismiss()
lastLoginResult = loginResult
- askUserForTwoFactorAuth()
+ askUserForTwoFactorAuthWithKeyboard()
}
override fun passwordResetPrompt(token: String?) = runOnUiThread {
@@ -348,12 +403,31 @@ class LoginActivity : AccountAuthenticatorActivity() {
@VisibleForTesting
fun askUserForTwoFactorAuth() {
+ if (binding == null) {
+ Timber.w("Binding is null, reinitializing in askUserForTwoFactorAuth")
+ binding = ActivityLoginBinding.inflate(layoutInflater)
+ setContentView(binding!!.root)
+ }
progressDialog!!.dismiss()
- with(binding!!) {
- twoFactorContainer.visibility = View.VISIBLE
- twoFactorContainer.hint = getString(if (lastLoginResult is LoginResult.EmailAuthResult) R.string.email_auth_code else R.string._2fa_code)
- loginTwoFactor.visibility = View.VISIBLE
- loginTwoFactor.requestFocus()
+ if (binding != null) {
+ with(binding!!) {
+ twoFactorContainer.visibility = View.VISIBLE
+ twoFactorContainer.hint = getString(if (lastLoginResult is LoginResult.EmailAuthResult) R.string.email_auth_code else R.string._2fa_code)
+ loginTwoFactor.visibility = View.VISIBLE
+ loginTwoFactor.requestFocus()
+
+ loginTwoFactor.setOnEditorActionListener { _, actionId, event ->
+ if (actionId == EditorInfo.IME_ACTION_DONE ||
+ (event != null && event.keyCode == KeyEvent.KEYCODE_ENTER && event.action == KeyEvent.ACTION_DOWN)) {
+ performLogin()
+ true
+ } else {
+ false
+ }
+ }
+ }
+ } else {
+ Timber.e("Binding is null in askUserForTwoFactorAuth after reinitialization attempt")
}
val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY)
diff --git a/app/src/main/java/fr/free/nrw/commons/auth/SignupActivity.kt b/app/src/main/java/fr/free/nrw/commons/auth/SignupActivity.kt
index 5b48ecd8f..22f557bcd 100644
--- a/app/src/main/java/fr/free/nrw/commons/auth/SignupActivity.kt
+++ b/app/src/main/java/fr/free/nrw/commons/auth/SignupActivity.kt
@@ -10,6 +10,7 @@ import android.widget.Toast
import fr.free.nrw.commons.BuildConfig
import fr.free.nrw.commons.R
import fr.free.nrw.commons.theme.BaseActivity
+import fr.free.nrw.commons.utils.applyEdgeToEdgeAllInsets
import timber.log.Timber
class SignupActivity : BaseActivity() {
@@ -21,6 +22,7 @@ class SignupActivity : BaseActivity() {
Timber.d("Signup Activity started")
webView = WebView(this)
+ applyEdgeToEdgeAllInsets(webView!!)
with(webView!!) {
setContentView(this)
webViewClient = MyWebViewClient()
diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarkFragment.java b/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarkFragment.java
deleted file mode 100644
index 9100fb63c..000000000
--- a/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarkFragment.java
+++ /dev/null
@@ -1,105 +0,0 @@
-package fr.free.nrw.commons.bookmarks;
-
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.fragment.app.FragmentManager;
-import fr.free.nrw.commons.contributions.MainActivity;
-import fr.free.nrw.commons.databinding.FragmentBookmarksBinding;
-import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
-import fr.free.nrw.commons.kvstore.JsonKvStore;
-import fr.free.nrw.commons.theme.BaseActivity;
-import javax.inject.Inject;
-import fr.free.nrw.commons.contributions.ContributionController;
-import javax.inject.Named;
-
-public class BookmarkFragment extends CommonsDaggerSupportFragment {
-
- private FragmentManager supportFragmentManager;
- private BookmarksPagerAdapter adapter;
- FragmentBookmarksBinding binding;
-
- @Inject
- ContributionController controller;
- /**
- * To check if the user is loggedIn or not.
- */
- @Inject
- @Named("default_preferences")
- public
- JsonKvStore applicationKvStore;
-
- @NonNull
- public static BookmarkFragment newInstance() {
- BookmarkFragment fragment = new BookmarkFragment();
- fragment.setRetainInstance(true);
- return fragment;
- }
-
- public void setScroll(boolean canScroll) {
- if (binding!=null) {
- binding.viewPagerBookmarks.setCanScroll(canScroll);
- }
- }
-
- @Override
- public void onCreate(@Nullable final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- }
-
- @Nullable
- @Override
- public View onCreateView(@NonNull final LayoutInflater inflater,
- @Nullable final ViewGroup container,
- @Nullable final Bundle savedInstanceState) {
- super.onCreateView(inflater, container, savedInstanceState);
- binding = FragmentBookmarksBinding.inflate(inflater, container, false);
-
- // Activity can call methods in the fragment by acquiring a
- // reference to the Fragment from FragmentManager, using findFragmentById()
- supportFragmentManager = getChildFragmentManager();
-
- adapter = new BookmarksPagerAdapter(supportFragmentManager, getContext(),
- applicationKvStore.getBoolean("login_skipped"));
- binding.viewPagerBookmarks.setAdapter(adapter);
- binding.tabLayout.setupWithViewPager(binding.viewPagerBookmarks);
-
- ((MainActivity) getActivity()).showTabs();
- ((BaseActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(false);
-
- setupTabLayout();
- return binding.getRoot();
- }
-
- /**
- * This method sets up the tab layout. If the adapter has only one element it sets the
- * visibility of tabLayout to gone.
- */
- public void setupTabLayout() {
- binding.tabLayout.setVisibility(View.VISIBLE);
- if (adapter.getCount() == 1) {
- binding.tabLayout.setVisibility(View.GONE);
- }
- }
-
-
- public void onBackPressed() {
- if (((BookmarkListRootFragment) (adapter.getItem(binding.tabLayout.getSelectedTabPosition())))
- .backPressed()) {
- // The event is handled internally by the adapter , no further action required.
- return;
- }
- // Event is not handled by the adapter ( performed back action ) change action bar.
- ((BaseActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(false);
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- binding = null;
- }
-}
diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarkFragment.kt b/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarkFragment.kt
new file mode 100644
index 000000000..51f15b23c
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarkFragment.kt
@@ -0,0 +1,98 @@
+package fr.free.nrw.commons.bookmarks
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import fr.free.nrw.commons.contributions.ContributionController
+import fr.free.nrw.commons.contributions.MainActivity
+import fr.free.nrw.commons.databinding.FragmentBookmarksBinding
+import fr.free.nrw.commons.di.CommonsDaggerSupportFragment
+import fr.free.nrw.commons.kvstore.JsonKvStore
+import fr.free.nrw.commons.theme.BaseActivity
+import javax.inject.Inject
+import javax.inject.Named
+
+class BookmarkFragment : CommonsDaggerSupportFragment() {
+ private var adapter: BookmarksPagerAdapter? = null
+
+ @JvmField
+ var binding: FragmentBookmarksBinding? = null
+
+ @JvmField
+ @Inject
+ var controller: ContributionController? = null
+
+ /**
+ * To check if the user is loggedIn or not.
+ */
+ @JvmField
+ @Inject
+ @Named("default_preferences")
+ var applicationKvStore: JsonKvStore? = null
+
+ fun setScroll(canScroll: Boolean) {
+ binding?.let {
+ it.viewPagerBookmarks.canScroll = canScroll
+ }
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ super.onCreateView(inflater, container, savedInstanceState)
+ binding = FragmentBookmarksBinding.inflate(inflater, container, false)
+
+ // Activity can call methods in the fragment by acquiring a
+ // reference to the Fragment from FragmentManager, using findFragmentById()
+ val supportFragmentManager = childFragmentManager
+
+ adapter = BookmarksPagerAdapter(
+ supportFragmentManager, requireContext(),
+ applicationKvStore!!.getBoolean("login_skipped")
+ )
+ binding!!.viewPagerBookmarks.adapter = adapter
+ binding!!.tabLayout.setupWithViewPager(binding!!.viewPagerBookmarks)
+
+ (requireActivity() as MainActivity).showTabs()
+ (requireActivity() as BaseActivity).supportActionBar!!.setDisplayHomeAsUpEnabled(false)
+
+ setupTabLayout()
+ return binding!!.root
+ }
+
+ /**
+ * This method sets up the tab layout. If the adapter has only one element it sets the
+ * visibility of tabLayout to gone.
+ */
+ fun setupTabLayout() {
+ binding!!.tabLayout.visibility = View.VISIBLE
+ if (adapter!!.count == 1) {
+ binding!!.tabLayout.visibility = View.GONE
+ }
+ }
+
+
+ fun onBackPressed() {
+ if (((adapter!!.getItem(binding!!.tabLayout.selectedTabPosition)) as BookmarkListRootFragment).backPressed()) {
+ // The event is handled internally by the adapter , no further action required.
+ return
+ }
+
+ // Event is not handled by the adapter ( performed back action ) change action bar.
+ (requireActivity() as BaseActivity).supportActionBar!!.setDisplayHomeAsUpEnabled(false)
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ binding = null
+ }
+
+ companion object {
+ fun newInstance(): BookmarkFragment = BookmarkFragment().apply {
+ retainInstance = true
+ }
+ }
+}
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
deleted file mode 100644
index e14cbbb6f..000000000
--- a/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarkListRootFragment.java
+++ /dev/null
@@ -1,267 +0,0 @@
-package fr.free.nrw.commons.bookmarks;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AdapterView;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentManager;
-import fr.free.nrw.commons.Media;
-import fr.free.nrw.commons.R;
-import fr.free.nrw.commons.bookmarks.category.BookmarkCategoriesFragment;
-import fr.free.nrw.commons.bookmarks.items.BookmarkItemsFragment;
-import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsFragment;
-import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesFragment;
-import fr.free.nrw.commons.category.CategoryImagesCallback;
-import fr.free.nrw.commons.category.GridViewAdapter;
-import fr.free.nrw.commons.contributions.MainActivity;
-import fr.free.nrw.commons.databinding.FragmentFeaturedRootBinding;
-import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
-import fr.free.nrw.commons.media.MediaDetailPagerFragment;
-import fr.free.nrw.commons.media.MediaDetailProvider;
-import fr.free.nrw.commons.navtab.NavTab;
-import java.util.ArrayList;
-import java.util.Iterator;
-import timber.log.Timber;
-
-public class BookmarkListRootFragment extends CommonsDaggerSupportFragment implements
- FragmentManager.OnBackStackChangedListener,
- MediaDetailProvider,
- AdapterView.OnItemClickListener, CategoryImagesCallback {
-
- private MediaDetailPagerFragment mediaDetails;
- //private BookmarkPicturesFragment bookmarkPicturesFragment;
- private BookmarkLocationsFragment bookmarkLocationsFragment;
- public Fragment listFragment;
- private BookmarksPagerAdapter bookmarksPagerAdapter;
-
- FragmentFeaturedRootBinding binding;
-
- public BookmarkListRootFragment() {
- //empty constructor necessary otherwise crashes on recreate
- }
-
- public BookmarkListRootFragment(Bundle bundle, BookmarksPagerAdapter bookmarksPagerAdapter) {
- String title = bundle.getString("categoryName");
- int order = bundle.getInt("order");
- final int orderItem = bundle.getInt("orderItem");
-
- switch (order){
- case 0: listFragment = new BookmarkPicturesFragment();
- break;
-
- case 1: listFragment = new BookmarkLocationsFragment();
- break;
-
- case 3: listFragment = new BookmarkCategoriesFragment();
- break;
- }
- if(orderItem == 2) {
- listFragment = new BookmarkItemsFragment();
- }
-
- Bundle featuredArguments = new Bundle();
- featuredArguments.putString("categoryName", title);
- listFragment.setArguments(featuredArguments);
- this.bookmarksPagerAdapter = bookmarksPagerAdapter;
- }
-
- @Nullable
- @Override
- public View onCreateView(@NonNull final LayoutInflater inflater,
- @Nullable final ViewGroup container,
- @Nullable final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- binding = FragmentFeaturedRootBinding.inflate(inflater, container, false);
- return binding.getRoot();
- }
-
- @Override
- public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
- if (savedInstanceState == null) {
- setFragment(listFragment, mediaDetails);
- }
- }
-
- public void setFragment(Fragment fragment, Fragment otherFragment) {
- if (fragment.isAdded() && otherFragment != null) {
- getChildFragmentManager()
- .beginTransaction()
- .hide(otherFragment)
- .show(fragment)
- .addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
- .commit();
- getChildFragmentManager().executePendingTransactions();
- } else if (fragment.isAdded() && otherFragment == null) {
- getChildFragmentManager()
- .beginTransaction()
- .show(fragment)
- .addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
- .commit();
- getChildFragmentManager().executePendingTransactions();
- } else if (!fragment.isAdded() && otherFragment != null) {
- getChildFragmentManager()
- .beginTransaction()
- .hide(otherFragment)
- .add(R.id.explore_container, fragment)
- .addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
- .commit();
- getChildFragmentManager().executePendingTransactions();
- } else if (!fragment.isAdded()) {
- getChildFragmentManager()
- .beginTransaction()
- .replace(R.id.explore_container, fragment)
- .addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
- .commit();
- getChildFragmentManager().executePendingTransactions();
- }
- }
-
- public void removeFragment(Fragment fragment) {
- getChildFragmentManager()
- .beginTransaction()
- .remove(fragment)
- .commit();
- getChildFragmentManager().executePendingTransactions();
- }
-
- @Override
- public void onAttach(final Context context) {
- super.onAttach(context);
- }
-
- @Override
- public void onMediaClicked(int position) {
- Timber.d("on media clicked");
- /*container.setVisibility(View.VISIBLE);
- ((BookmarkFragment)getParentFragment()).tabLayout.setVisibility(View.GONE);
- mediaDetails = new MediaDetailPagerFragment(false, true, position);
- setFragment(mediaDetails, bookmarkPicturesFragment);*/
- }
-
- /**
- * This method is called mediaDetailPagerFragment. It returns the Media Object at that Index
- *
- * @param i It is the index of which media object is to be returned which is same as current
- * index of viewPager.
- * @return Media Object
- */
- @Override
- public Media getMediaAtPosition(int i) {
- if (bookmarksPagerAdapter.getMediaAdapter() == null) {
- // not yet ready to return data
- return null;
- } else {
- return (Media) bookmarksPagerAdapter.getMediaAdapter().getItem(i);
- }
- }
-
- /**
- * This method is called on from getCount of MediaDetailPagerFragment The viewpager will contain
- * same number of media items as that of media elements in adapter.
- *
- * @return Total Media count in the adapter
- */
- @Override
- public int getTotalMediaCount() {
- if (bookmarksPagerAdapter.getMediaAdapter() == null) {
- return 0;
- }
- return bookmarksPagerAdapter.getMediaAdapter().getCount();
- }
-
- @Override
- public Integer getContributionStateAt(int position) {
- return null;
- }
-
- /**
- * Reload media detail fragment once media is nominated
- *
- * @param index item position that has been nominated
- */
- @Override
- public void refreshNominatedMedia(int index) {
- if (mediaDetails != null && !listFragment.isVisible()) {
- removeFragment(mediaDetails);
- mediaDetails = MediaDetailPagerFragment.newInstance(false, true);
- ((BookmarkFragment) getParentFragment()).setScroll(false);
- setFragment(mediaDetails, listFragment);
- mediaDetails.showImage(index);
- }
- }
-
- /**
- * This method is called on success of API call for featured images or mobile uploads. The
- * viewpager will notified that number of items have changed.
- */
- @Override
- public void viewPagerNotifyDataSetChanged() {
- if (mediaDetails != null) {
- mediaDetails.notifyDataSetChanged();
- }
- }
-
- public boolean backPressed() {
- //check mediaDetailPage fragment is not null then we check mediaDetail.is Visible or not to avoid NullPointerException
- if (mediaDetails != null) {
- if (mediaDetails.isVisible()) {
- // todo add get list fragment
- ((BookmarkFragment) getParentFragment()).setupTabLayout();
- ArrayList removed = mediaDetails.getRemovedItems();
- removeFragment(mediaDetails);
- ((BookmarkFragment) getParentFragment()).setScroll(true);
- setFragment(listFragment, mediaDetails);
- ((MainActivity) getActivity()).showTabs();
- if (listFragment instanceof BookmarkPicturesFragment) {
- GridViewAdapter adapter = ((GridViewAdapter) ((BookmarkPicturesFragment) listFragment)
- .getAdapter());
- Iterator i = removed.iterator();
- while (i.hasNext()) {
- adapter.remove(adapter.getItem((int) i.next()));
- }
- mediaDetails.clearRemoved();
-
- }
- } else {
- moveToContributionsFragment();
- }
- } else {
- moveToContributionsFragment();
- }
- // notify mediaDetails did not handled the backPressed further actions required.
- return false;
- }
-
- void moveToContributionsFragment() {
- ((MainActivity) getActivity()).setSelectedItemId(NavTab.CONTRIBUTIONS.code());
- ((MainActivity) getActivity()).showTabs();
- }
-
- @Override
- public void onItemClick(AdapterView> parent, View view, int position, long id) {
- Timber.d("on media clicked");
- binding.exploreContainer.setVisibility(View.VISIBLE);
- ((BookmarkFragment) getParentFragment()).binding.tabLayout.setVisibility(View.GONE);
- mediaDetails = MediaDetailPagerFragment.newInstance(false, true);
- ((BookmarkFragment) getParentFragment()).setScroll(false);
- setFragment(mediaDetails, listFragment);
- mediaDetails.showImage(position);
- }
-
- @Override
- public void onBackStackChanged() {
-
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- binding = null;
- }
-}
diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarkListRootFragment.kt b/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarkListRootFragment.kt
new file mode 100644
index 000000000..a9ed33abc
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarkListRootFragment.kt
@@ -0,0 +1,226 @@
+package fr.free.nrw.commons.bookmarks
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.AdapterView
+import android.widget.AdapterView.OnItemClickListener
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentManager
+import fr.free.nrw.commons.Media
+import fr.free.nrw.commons.R
+import fr.free.nrw.commons.bookmarks.category.BookmarkCategoriesFragment
+import fr.free.nrw.commons.bookmarks.items.BookmarkItemsFragment
+import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsFragment
+import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesFragment
+import fr.free.nrw.commons.category.CategoryImagesCallback
+import fr.free.nrw.commons.category.GridViewAdapter
+import fr.free.nrw.commons.contributions.MainActivity
+import fr.free.nrw.commons.databinding.FragmentFeaturedRootBinding
+import fr.free.nrw.commons.di.CommonsDaggerSupportFragment
+import fr.free.nrw.commons.media.MediaDetailPagerFragment
+import fr.free.nrw.commons.media.MediaDetailPagerFragment.Companion.newInstance
+import fr.free.nrw.commons.media.MediaDetailProvider
+import fr.free.nrw.commons.navtab.NavTab
+import timber.log.Timber
+
+class BookmarkListRootFragment : CommonsDaggerSupportFragment,
+ FragmentManager.OnBackStackChangedListener, MediaDetailProvider, OnItemClickListener,
+ CategoryImagesCallback {
+ private var mediaDetails: MediaDetailPagerFragment? = null
+ private val bookmarkLocationsFragment: BookmarkLocationsFragment? = null
+ var listFragment: Fragment? = null
+ private var bookmarksPagerAdapter: BookmarksPagerAdapter? = null
+
+ var binding: FragmentFeaturedRootBinding? = null
+
+ constructor()
+
+ constructor(bundle: Bundle, bookmarksPagerAdapter: BookmarksPagerAdapter) {
+ val title = bundle.getString("categoryName")
+ val order = bundle.getInt("order")
+ val orderItem = bundle.getInt("orderItem")
+
+ when (order) {
+ 0 -> listFragment = BookmarkPicturesFragment()
+ 1 -> listFragment = BookmarkLocationsFragment()
+ 3 -> listFragment = BookmarkCategoriesFragment()
+ }
+ if (orderItem == 2) {
+ listFragment = BookmarkItemsFragment()
+ }
+
+ val featuredArguments = Bundle()
+ featuredArguments.putString("categoryName", title)
+ listFragment!!.setArguments(featuredArguments)
+ this.bookmarksPagerAdapter = bookmarksPagerAdapter
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ super.onCreate(savedInstanceState)
+ binding = FragmentFeaturedRootBinding.inflate(inflater, container, false)
+ return binding!!.getRoot()
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ if (savedInstanceState == null) {
+ setFragment(listFragment!!, mediaDetails)
+ }
+ }
+
+ fun setFragment(fragment: Fragment, otherFragment: Fragment?) {
+ if (fragment.isAdded() && otherFragment != null) {
+ getChildFragmentManager()
+ .beginTransaction()
+ .hide(otherFragment)
+ .show(fragment)
+ .addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
+ .commit()
+ getChildFragmentManager().executePendingTransactions()
+ } else if (fragment.isAdded() && otherFragment == null) {
+ getChildFragmentManager()
+ .beginTransaction()
+ .show(fragment)
+ .addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
+ .commit()
+ getChildFragmentManager().executePendingTransactions()
+ } else if (!fragment.isAdded() && otherFragment != null) {
+ getChildFragmentManager()
+ .beginTransaction()
+ .hide(otherFragment)
+ .add(R.id.explore_container, fragment)
+ .addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
+ .commit()
+ getChildFragmentManager().executePendingTransactions()
+ } else if (!fragment.isAdded()) {
+ getChildFragmentManager()
+ .beginTransaction()
+ .replace(R.id.explore_container, fragment)
+ .addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
+ .commit()
+ getChildFragmentManager().executePendingTransactions()
+ }
+ }
+
+ fun removeFragment(fragment: Fragment) {
+ getChildFragmentManager()
+ .beginTransaction()
+ .remove(fragment)
+ .commit()
+ getChildFragmentManager().executePendingTransactions()
+ }
+
+ override fun onMediaClicked(position: Int) {
+ Timber.d("on media clicked")
+ /*container.setVisibility(View.VISIBLE);
+ ((BookmarkFragment)getParentFragment()).tabLayout.setVisibility(View.GONE);
+ mediaDetails = new MediaDetailPagerFragment(false, true, position);
+ setFragment(mediaDetails, bookmarkPicturesFragment);*/
+ }
+
+ /**
+ * This method is called mediaDetailPagerFragment. It returns the Media Object at that Index
+ *
+ * @param i It is the index of which media object is to be returned which is same as current
+ * index of viewPager.
+ * @return Media Object
+ */
+ override fun getMediaAtPosition(i: Int): Media? =
+ bookmarksPagerAdapter!!.mediaAdapter?.getItem(i) as Media?
+
+ /**
+ * This method is called on from getCount of MediaDetailPagerFragment The viewpager will contain
+ * same number of media items as that of media elements in adapter.
+ *
+ * @return Total Media count in the adapter
+ */
+ override fun getTotalMediaCount(): Int =
+ bookmarksPagerAdapter!!.mediaAdapter?.count ?: 0
+
+ override fun getContributionStateAt(position: Int): Int? {
+ return null
+ }
+
+ /**
+ * Reload media detail fragment once media is nominated
+ *
+ * @param index item position that has been nominated
+ */
+ override fun refreshNominatedMedia(index: Int) {
+ if (mediaDetails != null && !listFragment!!.isVisible()) {
+ removeFragment(mediaDetails!!)
+ mediaDetails = newInstance(false, true)
+ (parentFragment as BookmarkFragment).setScroll(false)
+ setFragment(mediaDetails!!, listFragment)
+ mediaDetails!!.showImage(index)
+ }
+ }
+
+ /**
+ * This method is called on success of API call for featured images or mobile uploads. The
+ * viewpager will notified that number of items have changed.
+ */
+ override fun viewPagerNotifyDataSetChanged() {
+ if (mediaDetails != null) {
+ mediaDetails!!.notifyDataSetChanged()
+ }
+ }
+
+ fun backPressed(): Boolean {
+ //check mediaDetailPage fragment is not null then we check mediaDetail.is Visible or not to avoid NullPointerException
+ if (mediaDetails != null) {
+ if (mediaDetails!!.isVisible()) {
+ // todo add get list fragment
+ (parentFragment as BookmarkFragment).setupTabLayout()
+ val removed: ArrayList = mediaDetails!!.removedItems
+ removeFragment(mediaDetails!!)
+ (parentFragment as BookmarkFragment).setScroll(true)
+ setFragment(listFragment!!, mediaDetails)
+ (requireActivity() as MainActivity).showTabs()
+ if (listFragment is BookmarkPicturesFragment) {
+ val adapter = ((listFragment as BookmarkPicturesFragment)
+ .getAdapter() as GridViewAdapter?)
+ val i: MutableIterator<*> = removed.iterator()
+ while (i.hasNext()) {
+ adapter!!.remove(adapter.getItem(i.next() as Int))
+ }
+ mediaDetails!!.clearRemoved()
+ }
+ } else {
+ moveToContributionsFragment()
+ }
+ } else {
+ moveToContributionsFragment()
+ }
+ // notify mediaDetails did not handled the backPressed further actions required.
+ return false
+ }
+
+ fun moveToContributionsFragment() {
+ (requireActivity() as MainActivity).setSelectedItemId(NavTab.CONTRIBUTIONS.code())
+ (requireActivity() as MainActivity).showTabs()
+ }
+
+ override fun onItemClick(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
+ Timber.d("on media clicked")
+ binding!!.exploreContainer.visibility = View.VISIBLE
+ (parentFragment as BookmarkFragment).binding!!.tabLayout.setVisibility(View.GONE)
+ mediaDetails = newInstance(false, true)
+ (parentFragment as BookmarkFragment).setScroll(false)
+ setFragment(mediaDetails!!, listFragment)
+ mediaDetails!!.showImage(position)
+ }
+
+ override fun onBackStackChanged() = Unit
+
+ override fun onDestroy() {
+ super.onDestroy()
+ binding = null
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarksPagerAdapter.java b/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarksPagerAdapter.java
deleted file mode 100644
index f0620032a..000000000
--- a/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarksPagerAdapter.java
+++ /dev/null
@@ -1,94 +0,0 @@
-package fr.free.nrw.commons.bookmarks;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.widget.ListAdapter;
-
-import androidx.annotation.Nullable;
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentManager;
-import androidx.fragment.app.FragmentPagerAdapter;
-
-import java.util.ArrayList;
-
-import fr.free.nrw.commons.R;
-import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesFragment;
-
-public class BookmarksPagerAdapter extends FragmentPagerAdapter {
-
- private ArrayList pages;
-
- /**
- * Default Constructor
- * @param fm
- * @param context
- * @param onlyPictures is true if the fragment requires only BookmarkPictureFragment
- * (i.e. when no user is logged in).
- */
- BookmarksPagerAdapter(FragmentManager fm, Context context,boolean onlyPictures) {
- super(fm);
- pages = new ArrayList<>();
- Bundle picturesBundle = new Bundle();
- picturesBundle.putString("categoryName", context.getString(R.string.title_page_bookmarks_pictures));
- picturesBundle.putInt("order", 0);
- pages.add(new BookmarkPages(
- new BookmarkListRootFragment(picturesBundle, this),
- context.getString(R.string.title_page_bookmarks_pictures)));
- if (!onlyPictures) {
- // if onlyPictures is false we also add the location fragment.
- Bundle locationBundle = new Bundle();
- locationBundle.putString("categoryName",
- context.getString(R.string.title_page_bookmarks_locations));
- locationBundle.putInt("order", 1);
- pages.add(new BookmarkPages(
- new BookmarkListRootFragment(locationBundle, this),
- context.getString(R.string.title_page_bookmarks_locations)));
-
- locationBundle.putInt("orderItem", 2);
- pages.add(new BookmarkPages(
- new BookmarkListRootFragment(locationBundle, this),
- context.getString(R.string.title_page_bookmarks_items)));
- }
- final Bundle categoriesBundle = new Bundle();
- categoriesBundle.putString("categoryName",
- context.getString(R.string.title_page_bookmarks_categories));
- categoriesBundle.putInt("order", 3);
- pages.add(new BookmarkPages(
- new BookmarkListRootFragment(categoriesBundle, this),
- context.getString(R.string.title_page_bookmarks_categories)));
- notifyDataSetChanged();
- }
-
- @Override
- public Fragment getItem(int position) {
- return pages.get(position).getPage();
- }
-
- @Override
- public int getCount() {
- return pages.size();
- }
-
- @Nullable
- @Override
- public CharSequence getPageTitle(int position) {
- return pages.get(position).getTitle();
- }
-
- /**
- * Return the Adapter used to display the picture gridview
- * @return adapter
- */
- public ListAdapter getMediaAdapter() {
- BookmarkPicturesFragment fragment = (BookmarkPicturesFragment)(((BookmarkListRootFragment)pages.get(0).getPage()).listFragment);
- return fragment.getAdapter();
- }
-
- /**
- * Update the pictures list for the bookmark fragment
- */
- public void requestPictureListUpdate() {
- BookmarkPicturesFragment fragment = (BookmarkPicturesFragment)(((BookmarkListRootFragment)pages.get(0).getPage()).listFragment);
- fragment.onResume();
- }
-}
diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarksPagerAdapter.kt b/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarksPagerAdapter.kt
new file mode 100644
index 000000000..a7cbf0e68
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarksPagerAdapter.kt
@@ -0,0 +1,82 @@
+package fr.free.nrw.commons.bookmarks
+
+import android.content.Context
+import android.widget.ListAdapter
+import androidx.core.os.bundleOf
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentManager
+import androidx.fragment.app.FragmentPagerAdapter
+import fr.free.nrw.commons.R
+import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesFragment
+
+class BookmarksPagerAdapter internal constructor(
+ fm: FragmentManager, context: Context, onlyPictures: Boolean
+) : FragmentPagerAdapter(fm) {
+ private val pages = mutableListOf()
+
+ /**
+ * Default Constructor
+ * @param fm
+ * @param context
+ * @param onlyPictures is true if the fragment requires only BookmarkPictureFragment
+ * (i.e. when no user is logged in).
+ */
+ init {
+ pages.add(
+ BookmarkPages(
+ BookmarkListRootFragment(
+ bundleOf(
+ "categoryName" to context.getString(R.string.title_page_bookmarks_pictures),
+ "order" to 0
+ ), this
+ ), context.getString(R.string.title_page_bookmarks_pictures)
+ )
+ )
+ if (!onlyPictures) {
+ // if onlyPictures is false we also add the location fragment.
+ val locationBundle = bundleOf(
+ "categoryName" to context.getString(R.string.title_page_bookmarks_locations),
+ "order" to 1
+ )
+
+ pages.add(
+ BookmarkPages(
+ BookmarkListRootFragment(locationBundle, this),
+ context.getString(R.string.title_page_bookmarks_locations)
+ )
+ )
+
+ locationBundle.putInt("orderItem", 2)
+ pages.add(
+ BookmarkPages(
+ BookmarkListRootFragment(locationBundle, this),
+ context.getString(R.string.title_page_bookmarks_items)
+ )
+ )
+ }
+ pages.add(
+ BookmarkPages(
+ BookmarkListRootFragment(
+ bundleOf(
+ "categoryName" to context.getString(R.string.title_page_bookmarks_categories),
+ "order" to 3
+ ), this),
+ context.getString(R.string.title_page_bookmarks_categories)
+ )
+ )
+ notifyDataSetChanged()
+ }
+
+ override fun getItem(position: Int): Fragment = pages[position].page!!
+
+ override fun getCount(): Int = pages.size
+
+ override fun getPageTitle(position: Int): CharSequence? = pages[position].title
+
+ /**
+ * Return the Adapter used to display the picture gridview
+ * @return adapter
+ */
+ val mediaAdapter: ListAdapter?
+ get() = (((pages[0].page as BookmarkListRootFragment).listFragment) as BookmarkPicturesFragment).getAdapter()
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsContentProvider.java b/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsContentProvider.java
deleted file mode 100644
index 3a85ec159..000000000
--- a/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsContentProvider.java
+++ /dev/null
@@ -1,129 +0,0 @@
-package fr.free.nrw.commons.bookmarks.items;
-
-import static fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao.Table.COLUMN_ID;
-import static fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao.Table.TABLE_NAME;
-
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteQueryBuilder;
-import android.net.Uri;
-import android.text.TextUtils;
-import androidx.annotation.NonNull;
-import fr.free.nrw.commons.BuildConfig;
-import fr.free.nrw.commons.data.DBOpenHelper;
-import fr.free.nrw.commons.di.CommonsDaggerContentProvider;
-import javax.inject.Inject;
-import timber.log.Timber;
-
-/**
- * Handles private storage for bookmarked items
- */
-public class BookmarkItemsContentProvider extends CommonsDaggerContentProvider {
-
- private static final String BASE_PATH = "bookmarksItems";
- public static final Uri BASE_URI =
- Uri.parse("content://" + BuildConfig.BOOKMARK_ITEMS_AUTHORITY + "/" + BASE_PATH);
-
-
- /**
- * Append bookmark items ID to the base uri
- */
- public static Uri uriForName(final String id) {
- return Uri.parse(BASE_URI + "/" + id);
- }
-
- @Inject
- DBOpenHelper dbOpenHelper;
-
- @Override
- public String getType(@NonNull final Uri uri) {
- return null;
- }
-
- /**
- * Queries the SQLite database for the bookmark items
- * @param uri : contains the uri for bookmark items
- * @param projection : contains the all fields of the table
- * @param selection : handles Where
- * @param selectionArgs : the condition of Where clause
- * @param sortOrder : ascending or descending
- */
- @SuppressWarnings("ConstantConditions")
- @Override
- public Cursor query(@NonNull final Uri uri, final String[] projection, final String selection,
- final String[] selectionArgs, final String sortOrder) {
- final SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
- queryBuilder.setTables(TABLE_NAME);
- final SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
- final Cursor cursor = queryBuilder.query(db, projection, selection,
- selectionArgs, null, null, sortOrder);
- cursor.setNotificationUri(getContext().getContentResolver(), uri);
- return cursor;
- }
-
- /**
- * Handles the update query of local SQLite Database
- * @param uri : contains the uri for bookmark items
- * @param contentValues : new values to be entered to db
- * @param selection : handles Where
- * @param selectionArgs : the condition of Where clause
- */
- @SuppressWarnings("ConstantConditions")
- @Override
- public int update(@NonNull final Uri uri, final ContentValues contentValues,
- final String selection, final String[] selectionArgs) {
- final SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
- final int rowsUpdated;
- if (TextUtils.isEmpty(selection)) {
- final int id = Integer.parseInt(uri.getLastPathSegment());
- rowsUpdated = sqlDB.update(TABLE_NAME,
- contentValues,
- COLUMN_ID + " = ?",
- new String[]{String.valueOf(id)});
- } else {
- throw new IllegalArgumentException(
- "Parameter `selection` should be empty when updating an ID");
- }
-
- getContext().getContentResolver().notifyChange(uri, null);
- return rowsUpdated;
- }
-
- /**
- * Handles the insertion of new bookmark items record to local SQLite Database
- * @param uri
- * @param contentValues
- * @return
- */
- @SuppressWarnings("ConstantConditions")
- @Override
- public Uri insert(@NonNull final Uri uri, final ContentValues contentValues) {
- final SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
- final long id = sqlDB.insert(TABLE_NAME, null, contentValues);
- getContext().getContentResolver().notifyChange(uri, null);
- return Uri.parse(BASE_URI + "/" + id);
- }
-
- /**
- * Handles the deletion of new bookmark items record to local SQLite Database
- * @param uri
- * @param s
- * @param strings
- * @return
- */
- @SuppressWarnings("ConstantConditions")
- @Override
- public int delete(@NonNull final Uri uri, final String s, final String[] strings) {
- final int rows;
- final SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
- Timber.d("Deleting bookmark name %s", uri.getLastPathSegment());
- rows = db.delete(
- TABLE_NAME,
- "item_id = ?",
- new String[]{uri.getLastPathSegment()}
- );
- getContext().getContentResolver().notifyChange(uri, null);
- return rows;
- }
-}
diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsContentProvider.kt b/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsContentProvider.kt
new file mode 100644
index 000000000..c532ed3cc
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsContentProvider.kt
@@ -0,0 +1,101 @@
+package fr.free.nrw.commons.bookmarks.items
+
+import android.content.ContentValues
+import android.database.Cursor
+import android.database.sqlite.SQLiteQueryBuilder
+import android.net.Uri
+import fr.free.nrw.commons.BuildConfig
+import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.TABLE_NAME
+import fr.free.nrw.commons.di.CommonsDaggerContentProvider
+import androidx.core.net.toUri
+import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.COLUMN_ID
+
+/**
+ * Handles private storage for bookmarked items
+ */
+class BookmarkItemsContentProvider : CommonsDaggerContentProvider() {
+ override fun getType(uri: Uri): String? = null
+
+ /**
+ * Queries the SQLite database for the bookmark items
+ * @param uri : contains the uri for bookmark items
+ * @param projection : contains the all fields of the table
+ * @param selection : handles Where
+ * @param selectionArgs : the condition of Where clause
+ * @param sortOrder : ascending or descending
+ */
+ override fun query(
+ uri: Uri, projection: Array?, selection: String?,
+ selectionArgs: Array?, sortOrder: String?
+ ): Cursor {
+ val queryBuilder = SQLiteQueryBuilder().apply {
+ tables = TABLE_NAME
+ }
+
+ return queryBuilder.query(
+ requireDb(), projection, selection,
+ selectionArgs, null, null, sortOrder
+ ).apply {
+ setNotificationUri(context?.contentResolver, uri)
+ }
+ }
+
+ /**
+ * Handles the update query of local SQLite Database
+ * @param uri : contains the uri for bookmark items
+ * @param contentValues : new values to be entered to db
+ * @param selection : handles Where
+ * @param selectionArgs : the condition of Where clause
+ */
+ override fun update(
+ uri: Uri, contentValues: ContentValues?,
+ selection: String?, selectionArgs: Array?
+ ): Int {
+ val rowsUpdated: Int
+ if (selection.isNullOrEmpty()) {
+ val id = uri.lastPathSegment!!.toInt()
+ rowsUpdated = requireDb().update(
+ TABLE_NAME,
+ contentValues,
+ "$COLUMN_ID = ?",
+ arrayOf(id.toString())
+ )
+ } else {
+ throw IllegalArgumentException(
+ "Parameter `selection` should be empty when updating an ID"
+ )
+ }
+
+ context?.contentResolver?.notifyChange(uri, null)
+ return rowsUpdated
+ }
+
+ /**
+ * Handles the insertion of new bookmark items record to local SQLite Database
+ */
+ override fun insert(uri: Uri, contentValues: ContentValues?): Uri? {
+ val id = requireDb().insert(TABLE_NAME, null, contentValues)
+ context?.contentResolver?.notifyChange(uri, null)
+ return "$BASE_URI/$id".toUri()
+ }
+
+
+ /**
+ * Handles the deletion of new bookmark items record to local SQLite Database
+ */
+ override fun delete(uri: Uri, s: String?, strings: Array?): Int {
+ val rows: Int = requireDb().delete(
+ TABLE_NAME,
+ "$COLUMN_ID = ?",
+ arrayOf(uri.lastPathSegment)
+ )
+ context?.contentResolver?.notifyChange(uri, null)
+ return rows
+ }
+
+ companion object {
+ private const val BASE_PATH = "bookmarksItems"
+ val BASE_URI: Uri = "content://${BuildConfig.BOOKMARK_ITEMS_AUTHORITY}/$BASE_PATH".toUri()
+ fun uriForName(id: String) = "$BASE_URI/$id".toUri()
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsController.java b/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsController.java
deleted file mode 100644
index d059e4cc4..000000000
--- a/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsController.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package fr.free.nrw.commons.bookmarks.items;
-
-import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
-import java.util.List;
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-/**
- * Handles loading bookmarked items from Database
- */
-@Singleton
-public class BookmarkItemsController {
-
- @Inject
- BookmarkItemsDao bookmarkItemsDao;
-
- @Inject
- public BookmarkItemsController() {}
-
- /**
- * Load from DB the bookmarked items
- * @return a list of DepictedItem objects.
- */
- public List loadFavoritesItems() {
- return bookmarkItemsDao.getAllBookmarksItems();
- }
-}
diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsController.kt b/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsController.kt
new file mode 100644
index 000000000..d1a9ef785
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsController.kt
@@ -0,0 +1,23 @@
+package fr.free.nrw.commons.bookmarks.items
+
+import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
+import javax.inject.Inject
+import javax.inject.Singleton
+
+/**
+ * Handles loading bookmarked items from Database
+ */
+@Singleton
+class BookmarkItemsController @Inject constructor() {
+ @JvmField
+ @Inject
+ var bookmarkItemsDao: BookmarkItemsDao? = null
+
+ /**
+ * Load from DB the bookmarked items
+ * @return a list of DepictedItem objects.
+ */
+ fun loadFavoritesItems(): List {
+ return bookmarkItemsDao?.getAllBookmarksItems() ?: emptyList()
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsDao.java b/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsDao.java
deleted file mode 100644
index 6788a8290..000000000
--- a/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsDao.java
+++ /dev/null
@@ -1,329 +0,0 @@
-package fr.free.nrw.commons.bookmarks.items;
-
-import android.annotation.SuppressLint;
-import android.content.ContentProviderClient;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.os.RemoteException;
-import fr.free.nrw.commons.category.CategoryItem;
-import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import javax.inject.Inject;
-import javax.inject.Named;
-import javax.inject.Provider;
-import javax.inject.Singleton;
-import org.apache.commons.lang3.StringUtils;
-
-/**
- * Handles database operations for bookmarked items
- */
-@Singleton
-public class BookmarkItemsDao {
-
- private final Provider clientProvider;
-
- @Inject
- public BookmarkItemsDao(
- @Named("bookmarksItem") final Provider clientProvider) {
- this.clientProvider = clientProvider;
- }
-
-
- /**
- * Find all persisted items bookmarks on database
- * @return list of bookmarks
- */
- public List getAllBookmarksItems() {
- final List items = new ArrayList<>();
- final ContentProviderClient db = clientProvider.get();
- try (final Cursor cursor = db.query(
- BookmarkItemsContentProvider.BASE_URI,
- Table.ALL_FIELDS,
- null,
- new String[]{},
- null)) {
- while (cursor != null && cursor.moveToNext()) {
- items.add(fromCursor(cursor));
- }
- } catch (final RemoteException e) {
- throw new RuntimeException(e);
- } finally {
- db.release();
- }
- return items;
- }
-
-
- /**
- * Look for a bookmark in database and in order to insert or delete it
- * @param depictedItem : Bookmark object
- * @return boolean : is bookmark now favorite ?
- */
- public boolean updateBookmarkItem(final DepictedItem depictedItem) {
- final boolean bookmarkExists = findBookmarkItem(depictedItem.getId());
- if (bookmarkExists) {
- deleteBookmarkItem(depictedItem);
- } else {
- addBookmarkItem(depictedItem);
- }
- return !bookmarkExists;
- }
-
- /**
- * Add a Bookmark to database
- * @param depictedItem : Bookmark to add
- */
- private void addBookmarkItem(final DepictedItem depictedItem) {
- final ContentProviderClient db = clientProvider.get();
- try {
- db.insert(BookmarkItemsContentProvider.BASE_URI, toContentValues(depictedItem));
- } catch (final RemoteException e) {
- throw new RuntimeException(e);
- } finally {
- db.release();
- }
- }
-
- /**
- * Delete a bookmark from database
- * @param depictedItem : Bookmark to delete
- */
- private void deleteBookmarkItem(final DepictedItem depictedItem) {
- final ContentProviderClient db = clientProvider.get();
- try {
- db.delete(BookmarkItemsContentProvider.uriForName(depictedItem.getId()), null, null);
- } catch (final RemoteException e) {
- throw new RuntimeException(e);
- } finally {
- db.release();
- }
- }
-
- /**
- * Find a bookmark from database based on its name
- * @param depictedItemID : Bookmark to find
- * @return boolean : is bookmark in database ?
- */
- public boolean findBookmarkItem(final String depictedItemID) {
- if (depictedItemID == null) { //Avoiding NPE's
- return false;
- }
- final ContentProviderClient db = clientProvider.get();
- try (final Cursor cursor = db.query(
- BookmarkItemsContentProvider.BASE_URI,
- Table.ALL_FIELDS,
- Table.COLUMN_ID + "=?",
- new String[]{depictedItemID},
- null
- )) {
- if (cursor != null && cursor.moveToFirst()) {
- return true;
- }
- } catch (final RemoteException e) {
- throw new RuntimeException(e);
- } finally {
- db.release();
- }
- return false;
- }
-
- /**
- * Recives real data from cursor
- * @param cursor : Object for storing database data
- * @return DepictedItem
- */
- @SuppressLint("Range")
- DepictedItem fromCursor(final Cursor cursor) {
- final String fileName = cursor.getString(cursor.getColumnIndex(Table.COLUMN_NAME));
- final String description
- = cursor.getString(cursor.getColumnIndex(Table.COLUMN_DESCRIPTION));
- final String imageUrl = cursor.getString(cursor.getColumnIndex(Table.COLUMN_IMAGE));
- final String instanceListString
- = cursor.getString(cursor.getColumnIndex(Table.COLUMN_INSTANCE_LIST));
- final List instanceList = StringToArray(instanceListString);
- final String categoryNameListString = cursor.getString(cursor
- .getColumnIndex(Table.COLUMN_CATEGORIES_NAME_LIST));
- final List categoryNameList = StringToArray(categoryNameListString);
- final String categoryDescriptionListString = cursor.getString(cursor
- .getColumnIndex(Table.COLUMN_CATEGORIES_DESCRIPTION_LIST));
- final List categoryDescriptionList = StringToArray(categoryDescriptionListString);
- final String categoryThumbnailListString = cursor.getString(cursor
- .getColumnIndex(Table.COLUMN_CATEGORIES_THUMBNAIL_LIST));
- final List categoryThumbnailList = StringToArray(categoryThumbnailListString);
- final List categoryList = convertToCategoryItems(categoryNameList,
- categoryDescriptionList, categoryThumbnailList);
- final boolean isSelected
- = Boolean.parseBoolean(cursor.getString(cursor
- .getColumnIndex(Table.COLUMN_IS_SELECTED)));
- final String id = cursor.getString(cursor.getColumnIndex(Table.COLUMN_ID));
-
- return new DepictedItem(
- fileName,
- description,
- imageUrl,
- instanceList,
- categoryList,
- isSelected,
- id
- );
- }
-
- private List convertToCategoryItems(List categoryNameList,
- List categoryDescriptionList, List categoryThumbnailList) {
- List categoryItems = new ArrayList<>();
- for(int i=0; i StringToArray(final String listString) {
- final String[] elements = listString.split(",");
- return Arrays.asList(elements);
- }
-
- /**
- * Converts string to List
- * @param list list of items
- * @return string comma separated single string of items
- */
- private String ArrayToString(final List list) {
- if (list != null) {
- return StringUtils.join(list, ',');
- }
- return null;
- }
-
- /**
- * Takes data from DepictedItem and create a content value object
- * @param depictedItem depicted item
- * @return ContentValues
- */
- private ContentValues toContentValues(final DepictedItem depictedItem) {
-
- final List namesOfCommonsCategories = new ArrayList<>();
- for (final CategoryItem category :
- depictedItem.getCommonsCategories()) {
- namesOfCommonsCategories.add(category.getName());
- }
-
- final List descriptionsOfCommonsCategories = new ArrayList<>();
- for (final CategoryItem category :
- depictedItem.getCommonsCategories()) {
- descriptionsOfCommonsCategories.add(category.getDescription());
- }
-
- final List thumbnailsOfCommonsCategories = new ArrayList<>();
- for (final CategoryItem category :
- depictedItem.getCommonsCategories()) {
- thumbnailsOfCommonsCategories.add(category.getThumbnail());
- }
-
- final ContentValues cv = new ContentValues();
- cv.put(Table.COLUMN_NAME, depictedItem.getName());
- cv.put(Table.COLUMN_DESCRIPTION, depictedItem.getDescription());
- cv.put(Table.COLUMN_IMAGE, depictedItem.getImageUrl());
- cv.put(Table.COLUMN_INSTANCE_LIST, ArrayToString(depictedItem.getInstanceOfs()));
- cv.put(Table.COLUMN_CATEGORIES_NAME_LIST, ArrayToString(namesOfCommonsCategories));
- cv.put(Table.COLUMN_CATEGORIES_DESCRIPTION_LIST,
- ArrayToString(descriptionsOfCommonsCategories));
- cv.put(Table.COLUMN_CATEGORIES_THUMBNAIL_LIST,
- ArrayToString(thumbnailsOfCommonsCategories));
- cv.put(Table.COLUMN_IS_SELECTED, depictedItem.isSelected());
- cv.put(Table.COLUMN_ID, depictedItem.getId());
- return cv;
- }
-
- /**
- * Table of bookmarksItems data
- */
- public static final class Table {
- public static final String TABLE_NAME = "bookmarksItems";
- public static final String COLUMN_NAME = "item_name";
- public static final String COLUMN_DESCRIPTION = "item_description";
- public static final String COLUMN_IMAGE = "item_image_url";
- public static final String COLUMN_INSTANCE_LIST = "item_instance_of";
- public static final String COLUMN_CATEGORIES_NAME_LIST = "item_name_categories";
- public static final String COLUMN_CATEGORIES_DESCRIPTION_LIST = "item_description_categories";
- public static final String COLUMN_CATEGORIES_THUMBNAIL_LIST = "item_thumbnail_categories";
- public static final String COLUMN_IS_SELECTED = "item_is_selected";
- public static final String COLUMN_ID = "item_id";
-
- public static final String[] ALL_FIELDS = {
- COLUMN_NAME,
- COLUMN_DESCRIPTION,
- COLUMN_IMAGE,
- COLUMN_INSTANCE_LIST,
- COLUMN_CATEGORIES_NAME_LIST,
- COLUMN_CATEGORIES_DESCRIPTION_LIST,
- COLUMN_CATEGORIES_THUMBNAIL_LIST,
- COLUMN_IS_SELECTED,
- COLUMN_ID
- };
-
- static final String DROP_TABLE_STATEMENT = "DROP TABLE IF EXISTS " + TABLE_NAME;
- static final String CREATE_TABLE_STATEMENT = "CREATE TABLE " + TABLE_NAME + " ("
- + COLUMN_NAME + " STRING,"
- + COLUMN_DESCRIPTION + " STRING,"
- + COLUMN_IMAGE + " STRING,"
- + COLUMN_INSTANCE_LIST + " STRING,"
- + COLUMN_CATEGORIES_NAME_LIST + " STRING,"
- + COLUMN_CATEGORIES_DESCRIPTION_LIST + " STRING,"
- + COLUMN_CATEGORIES_THUMBNAIL_LIST + " STRING,"
- + COLUMN_IS_SELECTED + " STRING,"
- + COLUMN_ID + " STRING PRIMARY KEY"
- + ");";
-
- /**
- * Creates table
- * @param db SQLiteDatabase
- */
- public static void onCreate(final SQLiteDatabase db) {
- db.execSQL(CREATE_TABLE_STATEMENT);
- }
-
- /**
- * Deletes database
- * @param db SQLiteDatabase
- */
- public static void onDelete(final SQLiteDatabase db) {
- db.execSQL(DROP_TABLE_STATEMENT);
- onCreate(db);
- }
-
- /**
- * Updates database
- * @param db SQLiteDatabase
- * @param from starting
- * @param to end
- */
- public static void onUpdate(final SQLiteDatabase db, int from, final int to) {
- if (from == to) {
- return;
- }
- if (from < 18) {
- // doesn't exist yet
- from++;
- onUpdate(db, from, to);
- return;
- }
-
- if (from == 18) {
- // table added in version 19
- onCreate(db);
- from++;
- onUpdate(db, from, to);
- }
- }
- }
-}
diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsDao.kt b/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsDao.kt
new file mode 100644
index 000000000..e21e1ac8f
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsDao.kt
@@ -0,0 +1,203 @@
+package fr.free.nrw.commons.bookmarks.items
+
+import android.annotation.SuppressLint
+import android.content.ContentProviderClient
+import android.content.ContentValues
+import android.database.Cursor
+import android.os.RemoteException
+import androidx.core.content.contentValuesOf
+import fr.free.nrw.commons.bookmarks.items.BookmarkItemsContentProvider.Companion.BASE_URI
+import fr.free.nrw.commons.bookmarks.items.BookmarkItemsContentProvider.Companion.uriForName
+import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.COLUMN_CATEGORIES_DESCRIPTION_LIST
+import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.COLUMN_CATEGORIES_NAME_LIST
+import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.COLUMN_CATEGORIES_THUMBNAIL_LIST
+import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.COLUMN_DESCRIPTION
+import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.COLUMN_ID
+import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.COLUMN_IMAGE
+import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.COLUMN_INSTANCE_LIST
+import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.COLUMN_IS_SELECTED
+import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.COLUMN_NAME
+import fr.free.nrw.commons.category.CategoryItem
+import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
+import fr.free.nrw.commons.utils.arrayToString
+import fr.free.nrw.commons.utils.getString
+import fr.free.nrw.commons.utils.getStringArray
+import javax.inject.Inject
+import javax.inject.Named
+import javax.inject.Provider
+import javax.inject.Singleton
+
+/**
+ * Handles database operations for bookmarked items
+ */
+@Singleton
+class BookmarkItemsDao @Inject constructor(
+ @param:Named("bookmarksItem") private val clientProvider: Provider
+) {
+ /**
+ * Find all persisted items bookmarks on database
+ * @return list of bookmarks
+ */
+ fun getAllBookmarksItems(): List {
+ val items: MutableList = mutableListOf()
+ val db = clientProvider.get()
+ try {
+ db.query(
+ BASE_URI,
+ BookmarkItemsTable.ALL_FIELDS,
+ null,
+ arrayOf(),
+ null
+ ).use { cursor ->
+ while (cursor != null && cursor.moveToNext()) {
+ items.add(fromCursor(cursor))
+ }
+ }
+ } catch (e: RemoteException) {
+ throw RuntimeException(e)
+ } finally {
+ db.release()
+ }
+ return items
+ }
+
+
+ /**
+ * Look for a bookmark in database and in order to insert or delete it
+ * @param depictedItem : Bookmark object
+ * @return boolean : is bookmark now favorite ?
+ */
+ fun updateBookmarkItem(depictedItem: DepictedItem): Boolean {
+ val bookmarkExists = findBookmarkItem(depictedItem.id)
+ if (bookmarkExists) {
+ deleteBookmarkItem(depictedItem)
+ } else {
+ addBookmarkItem(depictedItem)
+ }
+ return !bookmarkExists
+ }
+
+ /**
+ * Add a Bookmark to database
+ * @param depictedItem : Bookmark to add
+ */
+ private fun addBookmarkItem(depictedItem: DepictedItem) {
+ val db = clientProvider.get()
+ try {
+ db.insert(BASE_URI, toContentValues(depictedItem))
+ } catch (e: RemoteException) {
+ throw RuntimeException(e)
+ } finally {
+ db.release()
+ }
+ }
+
+ /**
+ * Delete a bookmark from database
+ * @param depictedItem : Bookmark to delete
+ */
+ private fun deleteBookmarkItem(depictedItem: DepictedItem) {
+ val db = clientProvider.get()
+ try {
+ db.delete(uriForName(depictedItem.id), null, null)
+ } catch (e: RemoteException) {
+ throw RuntimeException(e)
+ } finally {
+ db.release()
+ }
+ }
+
+ /**
+ * Find a bookmark from database based on its name
+ * @param depictedItemID : Bookmark to find
+ * @return boolean : is bookmark in database ?
+ */
+ fun findBookmarkItem(depictedItemID: String?): Boolean {
+ if (depictedItemID == null) { //Avoiding NPE's
+ return false
+ }
+ val db = clientProvider.get()
+ try {
+ db.query(
+ BASE_URI,
+ BookmarkItemsTable.ALL_FIELDS,
+ COLUMN_ID + "=?",
+ arrayOf(depictedItemID),
+ null
+ ).use { cursor ->
+ if (cursor != null && cursor.moveToFirst()) {
+ return true
+ }
+ }
+ } catch (e: RemoteException) {
+ throw RuntimeException(e)
+ } finally {
+ db.release()
+ }
+ return false
+ }
+
+ /**
+ * Recives real data from cursor
+ * @param cursor : Object for storing database data
+ * @return DepictedItem
+ */
+ @SuppressLint("Range")
+ fun fromCursor(cursor: Cursor) = with(cursor) {
+ var name = getString(COLUMN_NAME)
+ if (name == null) {
+ name = ""
+ }
+
+ var id = getString(COLUMN_ID)
+ if (id == null) {
+ id = ""
+ }
+
+ DepictedItem(
+ name,
+ getString(COLUMN_DESCRIPTION),
+ getString(COLUMN_IMAGE),
+ getStringArray(COLUMN_INSTANCE_LIST),
+ convertToCategoryItems(
+ getStringArray(COLUMN_CATEGORIES_NAME_LIST),
+ getStringArray(COLUMN_CATEGORIES_DESCRIPTION_LIST),
+ getStringArray(COLUMN_CATEGORIES_THUMBNAIL_LIST)
+ ),
+ getString(COLUMN_IS_SELECTED).toBoolean(),
+ id
+ )
+ }
+
+ private fun convertToCategoryItems(
+ categoryNameList: List,
+ categoryDescriptionList: List,
+ categoryThumbnailList: List
+ ): List = categoryNameList.mapIndexed { index, name ->
+ CategoryItem(
+ name = name,
+ description = categoryDescriptionList.getOrNull(index),
+ thumbnail = categoryThumbnailList.getOrNull(index),
+ isSelected = false
+ )
+ }
+
+ /**
+ * Takes data from DepictedItem and create a content value object
+ * @param depictedItem depicted item
+ * @return ContentValues
+ */
+ private fun toContentValues(depictedItem: DepictedItem): ContentValues {
+ return contentValuesOf(
+ COLUMN_NAME to depictedItem.name,
+ COLUMN_DESCRIPTION to depictedItem.description,
+ COLUMN_IMAGE to depictedItem.imageUrl,
+ COLUMN_INSTANCE_LIST to arrayToString(depictedItem.instanceOfs),
+ COLUMN_CATEGORIES_NAME_LIST to arrayToString(depictedItem.commonsCategories.map { it.name }),
+ COLUMN_CATEGORIES_DESCRIPTION_LIST to arrayToString(depictedItem.commonsCategories.map { it.description }),
+ COLUMN_CATEGORIES_THUMBNAIL_LIST to arrayToString(depictedItem.commonsCategories.map { it.thumbnail }),
+ COLUMN_IS_SELECTED to depictedItem.isSelected,
+ COLUMN_ID to depictedItem.id,
+ )
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsFragment.java b/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsFragment.java
deleted file mode 100644
index 75a0fa7a4..000000000
--- a/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsFragment.java
+++ /dev/null
@@ -1,81 +0,0 @@
-package fr.free.nrw.commons.bookmarks.items;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ProgressBar;
-import android.widget.RelativeLayout;
-import android.widget.TextView;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-import dagger.android.support.DaggerFragment;
-import fr.free.nrw.commons.R;
-import fr.free.nrw.commons.databinding.FragmentBookmarksItemsBinding;
-import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
-import java.util.List;
-import javax.inject.Inject;
-import org.jetbrains.annotations.NotNull;
-
-/**
- * Tab fragment to show list of bookmarked Wikidata Items
- */
-public class BookmarkItemsFragment extends DaggerFragment {
-
- private FragmentBookmarksItemsBinding binding;
-
- @Inject
- BookmarkItemsController controller;
-
- public static BookmarkItemsFragment newInstance() {
- return new BookmarkItemsFragment();
- }
-
- @Override
- public View onCreateView(
- @NonNull final LayoutInflater inflater,
- final ViewGroup container,
- final Bundle savedInstanceState
- ) {
- binding = FragmentBookmarksItemsBinding.inflate(inflater, container, false);
- return binding.getRoot();
- }
-
- @Override
- public void onViewCreated(final @NotNull View view, @Nullable final Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
- initList(requireContext());
- }
-
- @Override
- public void onResume() {
- super.onResume();
- initList(requireContext());
- }
-
- /**
- * Get list of DepictedItem and sets to the adapter
- * @param context context
- */
- private void initList(final Context context) {
- final List depictItems = controller.loadFavoritesItems();
- final BookmarkItemsAdapter adapter = new BookmarkItemsAdapter(depictItems, context);
- binding.listView.setAdapter(adapter);
- binding.loadingImagesProgressBar.setVisibility(View.GONE);
- if (depictItems.isEmpty()) {
- binding.statusMessage.setText(R.string.bookmark_empty);
- binding.statusMessage.setVisibility(View.VISIBLE);
- } else {
- binding.statusMessage.setVisibility(View.GONE);
- }
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- binding = null;
- }
-}
diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsFragment.kt b/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsFragment.kt
new file mode 100644
index 000000000..aa9dcccc0
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsFragment.kt
@@ -0,0 +1,62 @@
+package fr.free.nrw.commons.bookmarks.items
+
+import android.content.Context
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import dagger.android.support.DaggerFragment
+import fr.free.nrw.commons.R
+import fr.free.nrw.commons.databinding.FragmentBookmarksItemsBinding
+import javax.inject.Inject
+
+/**
+ * Tab fragment to show list of bookmarked Wikidata Items
+ */
+class BookmarkItemsFragment : DaggerFragment() {
+ private var binding: FragmentBookmarksItemsBinding? = null
+
+ @JvmField
+ @Inject
+ var controller: BookmarkItemsController? = null
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ binding = FragmentBookmarksItemsBinding.inflate(inflater, container, false)
+ return binding!!.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ initList(requireContext())
+ }
+
+ override fun onResume() {
+ super.onResume()
+ initList(requireContext())
+ }
+
+ /**
+ * Get list of DepictedItem and sets to the adapter
+ * @param context context
+ */
+ private fun initList(context: Context) {
+ val depictItems = controller!!.loadFavoritesItems()
+ binding!!.listView.adapter = BookmarkItemsAdapter(depictItems, context)
+ binding!!.loadingImagesProgressBar.visibility = View.GONE
+ if (depictItems.isEmpty()) {
+ binding!!.statusMessage.setText(R.string.bookmark_empty)
+ binding!!.statusMessage.visibility = View.VISIBLE
+ } else {
+ binding!!.statusMessage.visibility = View.GONE
+ }
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ binding = null
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsTable.kt b/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsTable.kt
new file mode 100644
index 000000000..b1b03c71b
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsTable.kt
@@ -0,0 +1,90 @@
+package fr.free.nrw.commons.bookmarks.items
+
+import android.database.sqlite.SQLiteDatabase
+
+/**
+ * Table of bookmarksItems data
+ */
+object BookmarkItemsTable {
+ const val TABLE_NAME = "bookmarksItems"
+ const val COLUMN_NAME = "item_name"
+ const val COLUMN_DESCRIPTION = "item_description"
+ const val COLUMN_IMAGE = "item_image_url"
+ const val COLUMN_INSTANCE_LIST = "item_instance_of"
+ const val COLUMN_CATEGORIES_NAME_LIST = "item_name_categories"
+ const val COLUMN_CATEGORIES_DESCRIPTION_LIST = "item_description_categories"
+ const val COLUMN_CATEGORIES_THUMBNAIL_LIST = "item_thumbnail_categories"
+ const val COLUMN_IS_SELECTED = "item_is_selected"
+ const val COLUMN_ID = "item_id"
+
+ val ALL_FIELDS = arrayOf(
+ COLUMN_NAME,
+ COLUMN_DESCRIPTION,
+ COLUMN_IMAGE,
+ COLUMN_INSTANCE_LIST,
+ COLUMN_CATEGORIES_NAME_LIST,
+ COLUMN_CATEGORIES_DESCRIPTION_LIST,
+ COLUMN_CATEGORIES_THUMBNAIL_LIST,
+ COLUMN_IS_SELECTED,
+ COLUMN_ID
+ )
+
+ const val DROP_TABLE_STATEMENT = "DROP TABLE IF EXISTS $TABLE_NAME"
+
+ val CREATE_TABLE_STATEMENT =
+ """CREATE TABLE $TABLE_NAME (
+ $COLUMN_NAME STRING,
+ $COLUMN_DESCRIPTION STRING,
+ $COLUMN_IMAGE STRING,
+ $COLUMN_INSTANCE_LIST STRING,
+ $COLUMN_CATEGORIES_NAME_LIST STRING,
+ $COLUMN_CATEGORIES_DESCRIPTION_LIST STRING,
+ $COLUMN_CATEGORIES_THUMBNAIL_LIST STRING,
+ $COLUMN_IS_SELECTED STRING,
+ $COLUMN_ID STRING PRIMARY KEY
+ );""".trimIndent()
+
+ /**
+ * Creates table
+ *
+ * @param db SQLiteDatabase
+ */
+ fun onCreate(db: SQLiteDatabase) {
+ db.execSQL(CREATE_TABLE_STATEMENT)
+ }
+
+ /**
+ * Deletes database
+ *
+ * @param db SQLiteDatabase
+ */
+ fun onDelete(db: SQLiteDatabase) {
+ db.execSQL(DROP_TABLE_STATEMENT)
+ onCreate(db)
+ }
+
+ /**
+ * Updates database
+ *
+ * @param db SQLiteDatabase
+ * @param from starting
+ * @param to end
+ */
+ fun onUpdate(db: SQLiteDatabase, from: Int, to: Int) {
+ if (from == to) {
+ return
+ }
+
+ if (from < 18) {
+ // doesn't exist yet
+ onUpdate(db, from + 1, to)
+ return
+ }
+
+ if (from == 18) {
+ // table added in version 19
+ onCreate(db)
+ onUpdate(db, from + 1, to)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesContentProvider.java b/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesContentProvider.java
deleted file mode 100644
index 2aac07902..000000000
--- a/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesContentProvider.java
+++ /dev/null
@@ -1,120 +0,0 @@
-package fr.free.nrw.commons.bookmarks.pictures;
-
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteQueryBuilder;
-// We can get uri using java.Net.Uri, but andoid implimentation is faster (but it's forgiving with handling exceptions though)
-import android.net.Uri;
-import android.text.TextUtils;
-
-import androidx.annotation.NonNull;
-
-import javax.inject.Inject;
-
-import fr.free.nrw.commons.BuildConfig;
-import fr.free.nrw.commons.data.DBOpenHelper;
-import fr.free.nrw.commons.di.CommonsDaggerContentProvider;
-import timber.log.Timber;
-
-import static fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao.Table.COLUMN_MEDIA_NAME;
-import static fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao.Table.TABLE_NAME;
-
-/**
- * Handles private storage for Bookmark pictures
- */
-public class BookmarkPicturesContentProvider extends CommonsDaggerContentProvider {
-
- private static final String BASE_PATH = "bookmarks";
- public static final Uri BASE_URI = Uri.parse("content://" + BuildConfig.BOOKMARK_AUTHORITY + "/" + BASE_PATH);
-
- /**
- * Append bookmark pictures name to the base uri
- */
- public static Uri uriForName(String name) {
- return Uri.parse(BASE_URI.toString() + "/" + name);
- }
-
- @Inject
- DBOpenHelper dbOpenHelper;
-
- @Override
- public String getType(@NonNull Uri uri) {
- return null;
- }
-
- /**
- * Queries the SQLite database for the bookmark pictures
- * @param uri : contains the uri for bookmark pictures
- * @param projection
- * @param selection : handles Where
- * @param selectionArgs : the condition of Where clause
- * @param sortOrder : ascending or descending
- */
- @SuppressWarnings("ConstantConditions")
- @Override
- public Cursor query(@NonNull Uri uri, String[] projection, String selection,
- String[] selectionArgs, String sortOrder) {
- SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
- queryBuilder.setTables(TABLE_NAME);
-
- SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
- Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder);
- cursor.setNotificationUri(getContext().getContentResolver(), uri);
-
- return cursor;
- }
-
- /**
- * Handles the update query of local SQLite Database
- * @param uri : contains the uri for bookmark pictures
- * @param contentValues : new values to be entered to db
- * @param selection : handles Where
- * @param selectionArgs : the condition of Where clause
- */
- @SuppressWarnings("ConstantConditions")
- @Override
- public int update(@NonNull Uri uri, ContentValues contentValues, String selection,
- String[] selectionArgs) {
- SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
- int rowsUpdated;
- if (TextUtils.isEmpty(selection)) {
- int id = Integer.valueOf(uri.getLastPathSegment());
- rowsUpdated = sqlDB.update(TABLE_NAME,
- contentValues,
- COLUMN_MEDIA_NAME + " = ?",
- new String[]{String.valueOf(id)});
- } else {
- throw new IllegalArgumentException(
- "Parameter `selection` should be empty when updating an ID");
- }
- getContext().getContentResolver().notifyChange(uri, null);
- return rowsUpdated;
- }
-
- /**
- * Handles the insertion of new bookmark pictures record to local SQLite Database
- */
- @SuppressWarnings("ConstantConditions")
- @Override
- public Uri insert(@NonNull Uri uri, ContentValues contentValues) {
- SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
- long id = sqlDB.insert(BookmarkPicturesDao.Table.TABLE_NAME, null, contentValues);
- getContext().getContentResolver().notifyChange(uri, null);
- return Uri.parse(BASE_URI + "/" + id);
- }
-
- @SuppressWarnings("ConstantConditions")
- @Override
- public int delete(@NonNull Uri uri, String s, String[] strings) {
- int rows;
- SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
- Timber.d("Deleting bookmark name %s", uri.getLastPathSegment());
- rows = db.delete(TABLE_NAME,
- "media_name = ?",
- new String[]{uri.getLastPathSegment()}
- );
- getContext().getContentResolver().notifyChange(uri, null);
- return rows;
- }
-}
diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesContentProvider.kt b/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesContentProvider.kt
new file mode 100644
index 000000000..a47eed8ca
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesContentProvider.kt
@@ -0,0 +1,100 @@
+package fr.free.nrw.commons.bookmarks.pictures
+
+import android.content.ContentValues
+import android.database.Cursor
+import android.database.sqlite.SQLiteQueryBuilder
+import android.net.Uri
+import fr.free.nrw.commons.BuildConfig
+import fr.free.nrw.commons.di.CommonsDaggerContentProvider
+import androidx.core.net.toUri
+import fr.free.nrw.commons.bookmarks.pictures.BookmarksTable.COLUMN_MEDIA_NAME
+import fr.free.nrw.commons.bookmarks.pictures.BookmarksTable.TABLE_NAME
+
+/**
+ * Handles private storage for Bookmark pictures
+ */
+class BookmarkPicturesContentProvider : CommonsDaggerContentProvider() {
+ override fun getType(uri: Uri): String? = null
+
+ /**
+ * Queries the SQLite database for the bookmark pictures
+ * @param uri : contains the uri for bookmark pictures
+ * @param projection
+ * @param selection : handles Where
+ * @param selectionArgs : the condition of Where clause
+ * @param sortOrder : ascending or descending
+ */
+ override fun query(
+ uri: Uri, projection: Array?, selection: String?,
+ selectionArgs: Array?, sortOrder: String?
+ ): Cursor {
+ val queryBuilder = SQLiteQueryBuilder().apply {
+ tables = TABLE_NAME
+ }
+
+ val cursor = queryBuilder.query(
+ requireDb(), projection, selection,
+ selectionArgs, null, null, sortOrder
+ )
+ cursor.setNotificationUri(context?.contentResolver, uri)
+
+ return cursor
+ }
+
+ /**
+ * Handles the update query of local SQLite Database
+ * @param uri : contains the uri for bookmark pictures
+ * @param contentValues : new values to be entered to db
+ * @param selection : handles Where
+ * @param selectionArgs : the condition of Where clause
+ */
+ override fun update(
+ uri: Uri, contentValues: ContentValues?, selection: String?,
+ selectionArgs: Array?
+ ): Int {
+ val rowsUpdated: Int
+ if (selection.isNullOrEmpty()) {
+ val id = uri.lastPathSegment!!.toInt()
+ rowsUpdated = requireDb().update(
+ TABLE_NAME,
+ contentValues,
+ "$COLUMN_MEDIA_NAME = ?",
+ arrayOf(id.toString())
+ )
+ } else {
+ throw IllegalArgumentException(
+ "Parameter `selection` should be empty when updating an ID"
+ )
+ }
+ context?.contentResolver?.notifyChange(uri, null)
+ return rowsUpdated
+ }
+
+ /**
+ * Handles the insertion of new bookmark pictures record to local SQLite Database
+ */
+ override fun insert(uri: Uri, contentValues: ContentValues?): Uri {
+ val id = requireDb().insert(TABLE_NAME, null, contentValues)
+ context?.contentResolver?.notifyChange(uri, null)
+ return "$BASE_URI/$id".toUri()
+ }
+
+ override fun delete(uri: Uri, s: String?, strings: Array?): Int {
+ val rows: Int = requireDb().delete(
+ TABLE_NAME,
+ "media_name = ?",
+ arrayOf(uri.lastPathSegment)
+ )
+ context?.contentResolver?.notifyChange(uri, null)
+ return rows
+ }
+
+ companion object {
+ private const val BASE_PATH = "bookmarks"
+ @JvmField
+ val BASE_URI: Uri = "content://${BuildConfig.BOOKMARK_AUTHORITY}/$BASE_PATH".toUri()
+
+ @JvmStatic
+ fun uriForName(name: String): Uri = "$BASE_URI/$name".toUri()
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesController.java b/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesController.java
deleted file mode 100644
index 7b644586c..000000000
--- a/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesController.java
+++ /dev/null
@@ -1,63 +0,0 @@
-package fr.free.nrw.commons.bookmarks.pictures;
-
-import fr.free.nrw.commons.Media;
-import fr.free.nrw.commons.bookmarks.models.Bookmark;
-import fr.free.nrw.commons.media.MediaClient;
-import io.reactivex.Observable;
-import io.reactivex.ObservableSource;
-import io.reactivex.Single;
-import io.reactivex.functions.Function;
-import java.util.ArrayList;
-import java.util.List;
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-@Singleton
-public class BookmarkPicturesController {
-
- private final MediaClient mediaClient;
- private final BookmarkPicturesDao bookmarkDao;
-
- private List currentBookmarks;
-
- @Inject
- public BookmarkPicturesController(MediaClient mediaClient, BookmarkPicturesDao bookmarkDao) {
- this.mediaClient = mediaClient;
- this.bookmarkDao = bookmarkDao;
- currentBookmarks = new ArrayList<>();
- }
-
- /**
- * Loads the Media objects from the raw data stored in DB and the API.
- * @return a list of bookmarked Media object
- */
- Single> loadBookmarkedPictures() {
- List bookmarks = bookmarkDao.getAllBookmarks();
- currentBookmarks = bookmarks;
- return Observable.fromIterable(bookmarks)
- .flatMap((Function>) this::getMediaFromBookmark)
- .toList();
- }
-
- private Observable getMediaFromBookmark(Bookmark bookmark) {
- return mediaClient.getMedia(bookmark.getMediaName())
- .toObservable()
- .onErrorResumeNext(Observable.empty());
- }
-
- /**
- * Loads the Media objects from the raw data stored in DB and the API.
- * @return a list of bookmarked Media object
- */
- boolean needRefreshBookmarkedPictures() {
- List bookmarks = bookmarkDao.getAllBookmarks();
- return bookmarks.size() != currentBookmarks.size();
- }
-
- /**
- * Cancels the requests to the API and the DB
- */
- void stop() {
- //noop
- }
-}
diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesController.kt b/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesController.kt
new file mode 100644
index 000000000..5ee88d973
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesController.kt
@@ -0,0 +1,38 @@
+package fr.free.nrw.commons.bookmarks.pictures
+
+import fr.free.nrw.commons.Media
+import fr.free.nrw.commons.bookmarks.models.Bookmark
+import fr.free.nrw.commons.media.MediaClient
+import io.reactivex.Observable
+import io.reactivex.Single
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class BookmarkPicturesController @Inject constructor(
+ private val mediaClient: MediaClient,
+ private val bookmarkDao: BookmarkPicturesDao
+) {
+ private var currentBookmarks: List = listOf()
+
+ /**
+ * Loads the Media objects from the raw data stored in DB and the API.
+ * @return a list of bookmarked Media object
+ */
+ fun loadBookmarkedPictures(): Single> {
+ val bookmarks = bookmarkDao.getAllBookmarks()
+ currentBookmarks = bookmarks
+ return Observable.fromIterable(bookmarks).flatMap {
+ mediaClient.getMedia(it.mediaName)
+ .toObservable()
+ .onErrorResumeNext(Observable.empty())
+ }.toList()
+ }
+
+ fun needRefreshBookmarkedPictures(): Boolean {
+ val bookmarks = bookmarkDao.getAllBookmarks()
+ return bookmarks.size != currentBookmarks.size
+ }
+
+ fun stop() = Unit
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesDao.java b/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesDao.java
deleted file mode 100644
index c214ae996..000000000
--- a/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesDao.java
+++ /dev/null
@@ -1,227 +0,0 @@
-package fr.free.nrw.commons.bookmarks.pictures;
-
-import android.annotation.SuppressLint;
-import android.content.ContentProviderClient;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.os.RemoteException;
-
-import androidx.annotation.NonNull;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-import javax.inject.Provider;
-import javax.inject.Singleton;
-
-import fr.free.nrw.commons.bookmarks.models.Bookmark;
-
-import static fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesContentProvider.BASE_URI;
-
-@Singleton
-public class BookmarkPicturesDao {
-
- private final Provider clientProvider;
-
- @Inject
- public BookmarkPicturesDao(@Named("bookmarks") Provider clientProvider) {
- this.clientProvider = clientProvider;
- }
-
-
- /**
- * Find all persisted pictures bookmarks on database
- *
- * @return list of bookmarks
- */
- @NonNull
- public List getAllBookmarks() {
- List items = new ArrayList<>();
- Cursor cursor = null;
- ContentProviderClient db = clientProvider.get();
- try {
- cursor = db.query(
- BookmarkPicturesContentProvider.BASE_URI,
- Table.ALL_FIELDS,
- null,
- new String[]{},
- null);
- while (cursor != null && cursor.moveToNext()) {
- items.add(fromCursor(cursor));
- }
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- db.release();
- }
- return items;
- }
-
-
- /**
- * Look for a bookmark in database and in order to insert or delete it
- *
- * @param bookmark : Bookmark object
- * @return boolean : is bookmark now fav ?
- */
- public boolean updateBookmark(Bookmark bookmark) {
- boolean bookmarkExists = findBookmark(bookmark);
- if (bookmarkExists) {
- deleteBookmark(bookmark);
- } else {
- addBookmark(bookmark);
- }
- return !bookmarkExists;
- }
-
- /**
- * Add a Bookmark to database
- *
- * @param bookmark : Bookmark to add
- */
- private void addBookmark(Bookmark bookmark) {
- ContentProviderClient db = clientProvider.get();
- try {
- db.insert(BASE_URI, toContentValues(bookmark));
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- } finally {
- db.release();
- }
- }
-
- /**
- * Delete a bookmark from database
- *
- * @param bookmark : Bookmark to delete
- */
- private void deleteBookmark(Bookmark bookmark) {
- ContentProviderClient db = clientProvider.get();
- try {
- if (bookmark.getContentUri() == null) {
- throw new RuntimeException("tried to delete item with no content URI");
- } else {
- db.delete(bookmark.getContentUri(), null, null);
- }
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- } finally {
- db.release();
- }
- }
-
- /**
- * Find a bookmark from database based on its name
- *
- * @param bookmark : Bookmark to find
- * @return boolean : is bookmark in database ?
- */
- public boolean findBookmark(Bookmark bookmark) {
- if (bookmark == null) {//Avoiding NPE's
- return false;
- }
-
- Cursor cursor = null;
- ContentProviderClient db = clientProvider.get();
- try {
- cursor = db.query(
- BookmarkPicturesContentProvider.BASE_URI,
- Table.ALL_FIELDS,
- Table.COLUMN_MEDIA_NAME + "=?",
- new String[]{bookmark.getMediaName()},
- null);
- if (cursor != null && cursor.moveToFirst()) {
- return true;
- }
- } catch (RemoteException e) {
- // This feels lazy, but to hell with checked exceptions. :)
- throw new RuntimeException(e);
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- db.release();
- }
- return false;
- }
-
- @SuppressLint("Range")
- @NonNull
- Bookmark fromCursor(Cursor cursor) {
- String fileName = cursor.getString(cursor.getColumnIndex(Table.COLUMN_MEDIA_NAME));
- return new Bookmark(
- fileName,
- cursor.getString(cursor.getColumnIndex(Table.COLUMN_CREATOR)),
- BookmarkPicturesContentProvider.uriForName(fileName)
- );
- }
-
- private ContentValues toContentValues(Bookmark bookmark) {
- ContentValues cv = new ContentValues();
- cv.put(BookmarkPicturesDao.Table.COLUMN_MEDIA_NAME, bookmark.getMediaName());
- cv.put(BookmarkPicturesDao.Table.COLUMN_CREATOR, bookmark.getMediaCreator());
- return cv;
- }
-
-
- public static class Table {
- public static final String TABLE_NAME = "bookmarks";
-
- public static final String COLUMN_MEDIA_NAME = "media_name";
- public static final String COLUMN_CREATOR = "media_creator";
-
- // NOTE! KEEP IN SAME ORDER AS THEY ARE DEFINED UP THERE. HELPS HARD CODE COLUMN INDICES.
- public static final String[] ALL_FIELDS = {
- COLUMN_MEDIA_NAME,
- COLUMN_CREATOR
- };
-
- public static final String DROP_TABLE_STATEMENT = "DROP TABLE IF EXISTS " + TABLE_NAME;
-
- public static final String CREATE_TABLE_STATEMENT = "CREATE TABLE " + TABLE_NAME + " ("
- + COLUMN_MEDIA_NAME + " STRING PRIMARY KEY,"
- + COLUMN_CREATOR + " STRING"
- + ");";
-
- public static void onCreate(SQLiteDatabase db) {
- db.execSQL(CREATE_TABLE_STATEMENT);
- }
-
- public static void onDelete(SQLiteDatabase db) {
- db.execSQL(DROP_TABLE_STATEMENT);
- onCreate(db);
- }
-
- public static void onUpdate(SQLiteDatabase db, int from, int to) {
- if (from == to) {
- return;
- }
- if (from < 7) {
- // doesn't exist yet
- from++;
- onUpdate(db, from, to);
- return;
- }
-
- if (from == 7) {
- // table added in version 8
- onCreate(db);
- from++;
- onUpdate(db, from, to);
- return;
- }
-
- if (from == 8) {
- from++;
- onUpdate(db, from, to);
- return;
- }
- }
- }
-}
diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesDao.kt b/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesDao.kt
new file mode 100644
index 000000000..00c8e3228
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesDao.kt
@@ -0,0 +1,144 @@
+package fr.free.nrw.commons.bookmarks.pictures
+
+import android.content.ContentProviderClient
+import android.content.ContentValues
+import android.database.Cursor
+import android.os.RemoteException
+import androidx.core.content.contentValuesOf
+import fr.free.nrw.commons.bookmarks.models.Bookmark
+import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesContentProvider.Companion.BASE_URI
+import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesContentProvider.Companion.uriForName
+import fr.free.nrw.commons.bookmarks.pictures.BookmarksTable.ALL_FIELDS
+import fr.free.nrw.commons.bookmarks.pictures.BookmarksTable.COLUMN_CREATOR
+import fr.free.nrw.commons.bookmarks.pictures.BookmarksTable.COLUMN_MEDIA_NAME
+import fr.free.nrw.commons.utils.getString
+import javax.inject.Inject
+import javax.inject.Named
+import javax.inject.Provider
+import javax.inject.Singleton
+
+@Singleton
+class BookmarkPicturesDao @Inject constructor(
+ @param:Named("bookmarks") private val clientProvider: Provider
+) {
+ /**
+ * Find all persisted pictures bookmarks on database
+ *
+ * @return list of bookmarks
+ */
+ fun getAllBookmarks(): List {
+ val items: MutableList = mutableListOf()
+ var cursor: Cursor? = null
+ val db = clientProvider.get()
+ try {
+ cursor = db.query(
+ BASE_URI, ALL_FIELDS, null, arrayOf(), null
+ )
+ while (cursor != null && cursor.moveToNext()) {
+ items.add(fromCursor(cursor))
+ }
+ } catch (e: RemoteException) {
+ throw RuntimeException(e)
+ } finally {
+ cursor?.close()
+ db.release()
+ }
+ return items
+ }
+
+ /**
+ * Look for a bookmark in database and in order to insert or delete it
+ *
+ * @param bookmark : Bookmark object
+ * @return boolean : is bookmark now fav ?
+ */
+ fun updateBookmark(bookmark: Bookmark): Boolean {
+ val bookmarkExists = findBookmark(bookmark)
+ if (bookmarkExists) {
+ deleteBookmark(bookmark)
+ } else {
+ addBookmark(bookmark)
+ }
+ return !bookmarkExists
+ }
+
+ /**
+ * Add a Bookmark to database
+ *
+ * @param bookmark : Bookmark to add
+ */
+ private fun addBookmark(bookmark: Bookmark) {
+ val db = clientProvider.get()
+ try {
+ db.insert(BASE_URI, toContentValues(bookmark))
+ } catch (e: RemoteException) {
+ throw RuntimeException(e)
+ } finally {
+ db.release()
+ }
+ }
+
+ /**
+ * Delete a bookmark from database
+ *
+ * @param bookmark : Bookmark to delete
+ */
+ private fun deleteBookmark(bookmark: Bookmark) {
+ val db = clientProvider.get()
+ try {
+ if (bookmark.contentUri == null) {
+ throw RuntimeException("tried to delete item with no content URI")
+ } else {
+ db.delete(bookmark.contentUri!!, null, null)
+ }
+ } catch (e: RemoteException) {
+ throw RuntimeException(e)
+ } finally {
+ db.release()
+ }
+ }
+
+ /**
+ * Find a bookmark from database based on its name
+ *
+ * @param bookmark : Bookmark to find
+ * @return boolean : is bookmark in database ?
+ */
+ fun findBookmark(bookmark: Bookmark?): Boolean {
+ if (bookmark == null) {
+ return false
+ }
+
+ var cursor: Cursor? = null
+ val db = clientProvider.get()
+ try {
+ cursor = db.query(
+ BASE_URI, ALL_FIELDS, "$COLUMN_MEDIA_NAME=?", arrayOf(bookmark.mediaName), null
+ )
+ if (cursor != null && cursor.moveToFirst()) {
+ return true
+ }
+ } catch (e: RemoteException) {
+ throw RuntimeException(e)
+ } finally {
+ cursor?.close()
+ db.release()
+ }
+ return false
+ }
+
+ fun fromCursor(cursor: Cursor): Bookmark {
+ var fileName = cursor.getString(COLUMN_MEDIA_NAME)
+ if (fileName == null) {
+ fileName = ""
+ }
+ return Bookmark(
+ fileName, cursor.getString(COLUMN_CREATOR), uriForName(fileName)
+ )
+ }
+
+ private fun toContentValues(bookmark: Bookmark): ContentValues = contentValuesOf(
+ COLUMN_MEDIA_NAME to bookmark.mediaName,
+ COLUMN_CREATOR to bookmark.mediaCreator
+ )
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesFragment.java b/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesFragment.java
deleted file mode 100644
index 9f02e4631..000000000
--- a/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesFragment.java
+++ /dev/null
@@ -1,218 +0,0 @@
-package fr.free.nrw.commons.bookmarks.pictures;
-
-import static android.view.View.GONE;
-import static android.view.View.VISIBLE;
-
-import android.annotation.SuppressLint;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.ListAdapter;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import dagger.android.support.DaggerFragment;
-import fr.free.nrw.commons.Media;
-import fr.free.nrw.commons.R;
-import fr.free.nrw.commons.bookmarks.BookmarkListRootFragment;
-import fr.free.nrw.commons.category.GridViewAdapter;
-import fr.free.nrw.commons.databinding.FragmentBookmarksPicturesBinding;
-import fr.free.nrw.commons.utils.NetworkUtils;
-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.util.List;
-import javax.inject.Inject;
-import timber.log.Timber;
-
-public class BookmarkPicturesFragment extends DaggerFragment {
-
- private GridViewAdapter gridAdapter;
- private CompositeDisposable compositeDisposable = new CompositeDisposable();
-
- private FragmentBookmarksPicturesBinding binding;
- @Inject
- BookmarkPicturesController controller;
-
- /**
- * Create an instance of the fragment with the right bundle parameters
- * @return an instance of the fragment
- */
- public static BookmarkPicturesFragment newInstance() {
- return new BookmarkPicturesFragment();
- }
-
- @Override
- public View onCreateView(
- @NonNull LayoutInflater inflater,
- ViewGroup container,
- Bundle savedInstanceState
- ) {
- binding = FragmentBookmarksPicturesBinding.inflate(inflater, container, false);
- return binding.getRoot();
- }
-
- @Override
- public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
- binding.bookmarkedPicturesList.setOnItemClickListener((AdapterView.OnItemClickListener) getParentFragment());
- initList();
- }
-
- @Override
- public void onStop() {
- super.onStop();
- controller.stop();
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- compositeDisposable.clear();
- binding = null;
- }
-
- @Override
- public void onResume() {
- super.onResume();
- if (controller.needRefreshBookmarkedPictures()) {
- binding.bookmarkedPicturesList.setVisibility(GONE);
- if (gridAdapter != null) {
- gridAdapter.clear();
- ((BookmarkListRootFragment)getParentFragment()).viewPagerNotifyDataSetChanged();
- }
- initList();
- }
- }
-
- /**
- * Checks for internet connection and then initializes
- * the recycler view with bookmarked pictures
- */
- @SuppressLint("CheckResult")
- private void initList() {
- if (!NetworkUtils.isInternetConnectionEstablished(getContext())) {
- handleNoInternet();
- return;
- }
-
- binding.loadingImagesProgressBar.setVisibility(VISIBLE);
- binding.statusMessage.setVisibility(GONE);
-
- compositeDisposable.add(controller.loadBookmarkedPictures()
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(this::handleSuccess, this::handleError));
- }
-
- /**
- * Handles the UI updates for no internet scenario
- */
- private void handleNoInternet() {
- binding.loadingImagesProgressBar.setVisibility(GONE);
- if (gridAdapter == null || gridAdapter.isEmpty()) {
- binding.statusMessage.setVisibility(VISIBLE);
- binding.statusMessage.setText(getString(R.string.no_internet));
- } else {
- ViewUtil.showShortSnackbar(binding.parentLayout, R.string.no_internet);
- }
- }
-
- /**
- * Logs and handles API error scenario
- * @param throwable
- */
- private void handleError(Throwable throwable) {
- Timber.e(throwable, "Error occurred while loading images inside a category");
- try{
- ViewUtil.showShortSnackbar(binding.getRoot(), R.string.error_loading_images);
- initErrorView();
- }catch (Exception e){
- e.printStackTrace();
- }
- }
-
- /**
- * Handles the UI updates for a error scenario
- */
- private void initErrorView() {
- binding.loadingImagesProgressBar.setVisibility(GONE);
- if (gridAdapter == null || gridAdapter.isEmpty()) {
- binding.statusMessage.setVisibility(VISIBLE);
- binding.statusMessage.setText(getString(R.string.no_images_found));
- } else {
- binding.statusMessage.setVisibility(GONE);
- }
- }
-
- /**
- * Handles the UI updates when there is no bookmarks
- */
- private void initEmptyBookmarkListView() {
- binding.loadingImagesProgressBar.setVisibility(GONE);
- if (gridAdapter == null || gridAdapter.isEmpty()) {
- binding.statusMessage.setVisibility(VISIBLE);
- binding.statusMessage.setText(getString(R.string.bookmark_empty));
- } else {
- binding.statusMessage.setVisibility(GONE);
- }
- }
-
- /**
- * Handles the success scenario
- * On first load, it initializes the grid view. On subsequent loads, it adds items to the adapter
- * @param collection List of new Media to be displayed
- */
- private void handleSuccess(List collection) {
- if (collection == null) {
- initErrorView();
- return;
- }
- if (collection.isEmpty()) {
- initEmptyBookmarkListView();
- return;
- }
-
- if (gridAdapter == null) {
- setAdapter(collection);
- } else {
- if (gridAdapter.containsAll(collection)) {
- binding.loadingImagesProgressBar.setVisibility(GONE);
- binding.statusMessage.setVisibility(GONE);
- binding.bookmarkedPicturesList.setVisibility(VISIBLE);
- binding.bookmarkedPicturesList.setAdapter(gridAdapter);
- return;
- }
- gridAdapter.addItems(collection);
- ((BookmarkListRootFragment) getParentFragment()).viewPagerNotifyDataSetChanged();
- }
- binding.loadingImagesProgressBar.setVisibility(GONE);
- binding.statusMessage.setVisibility(GONE);
- binding.bookmarkedPicturesList.setVisibility(VISIBLE);
- }
-
- /**
- * Initializes the adapter with a list of Media objects
- * @param mediaList List of new Media to be displayed
- */
- private void setAdapter(List mediaList) {
- gridAdapter = new GridViewAdapter(
- this.getContext(),
- R.layout.layout_category_images,
- mediaList
- );
- binding.bookmarkedPicturesList.setAdapter(gridAdapter);
- }
-
- /**
- * It return an instance of gridView adapter which helps in extracting media details
- * used by the gridView
- * @return GridView Adapter
- */
- public ListAdapter getAdapter() {
- return binding.bookmarkedPicturesList.getAdapter();
- }
-
-}
diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesFragment.kt b/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesFragment.kt
new file mode 100644
index 000000000..e8c61371a
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesFragment.kt
@@ -0,0 +1,201 @@
+package fr.free.nrw.commons.bookmarks.pictures
+
+import android.annotation.SuppressLint
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.AdapterView.OnItemClickListener
+import android.widget.ListAdapter
+import dagger.android.support.DaggerFragment
+import fr.free.nrw.commons.Media
+import fr.free.nrw.commons.R
+import fr.free.nrw.commons.bookmarks.BookmarkListRootFragment
+import fr.free.nrw.commons.category.GridViewAdapter
+import fr.free.nrw.commons.databinding.FragmentBookmarksPicturesBinding
+import fr.free.nrw.commons.utils.NetworkUtils.isInternetConnectionEstablished
+import fr.free.nrw.commons.utils.ViewUtil.showShortSnackbar
+import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.disposables.CompositeDisposable
+import io.reactivex.functions.Consumer
+import io.reactivex.schedulers.Schedulers
+import timber.log.Timber
+import javax.inject.Inject
+
+class BookmarkPicturesFragment : DaggerFragment() {
+ private var gridAdapter: GridViewAdapter? = null
+ private val compositeDisposable = CompositeDisposable()
+
+ private var binding: FragmentBookmarksPicturesBinding? = null
+
+ @JvmField
+ @Inject
+ var controller: BookmarkPicturesController? = null
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ binding = FragmentBookmarksPicturesBinding.inflate(inflater, container, false)
+ return binding!!.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ binding!!.bookmarkedPicturesList.onItemClickListener =
+ parentFragment as OnItemClickListener?
+ initList()
+ }
+
+ override fun onStop() {
+ super.onStop()
+ controller!!.stop()
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ compositeDisposable.clear()
+ binding = null
+ }
+
+ override fun onResume() {
+ super.onResume()
+ if (controller!!.needRefreshBookmarkedPictures()) {
+ binding!!.bookmarkedPicturesList.visibility = View.GONE
+ gridAdapter?.let {
+ it.clear()
+ (parentFragment as BookmarkListRootFragment).viewPagerNotifyDataSetChanged()
+ }
+ initList()
+ }
+ }
+
+ /**
+ * Checks for internet connection and then initializes
+ * the recycler view with bookmarked pictures
+ */
+ @SuppressLint("CheckResult")
+ private fun initList() {
+ if (!isInternetConnectionEstablished(context)) {
+ handleNoInternet()
+ return
+ }
+
+ binding!!.loadingImagesProgressBar.visibility = View.VISIBLE
+ binding!!.statusMessage.visibility = View.GONE
+
+ compositeDisposable.add(
+ controller!!.loadBookmarkedPictures()
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(::handleSuccess, ::handleError)
+ )
+ }
+
+ /**
+ * Handles the UI updates for no internet scenario
+ */
+ private fun handleNoInternet() {
+ binding!!.loadingImagesProgressBar.visibility = View.GONE
+ if (gridAdapter == null || gridAdapter!!.isEmpty) {
+ binding!!.statusMessage.visibility = View.VISIBLE
+ binding!!.statusMessage.text = getString(R.string.no_internet)
+ } else {
+ showShortSnackbar(binding!!.parentLayout, R.string.no_internet)
+ }
+ }
+
+ /**
+ * Logs and handles API error scenario
+ * @param throwable
+ */
+ private fun handleError(throwable: Throwable) {
+ Timber.e(throwable, "Error occurred while loading images inside a category")
+ try {
+ showShortSnackbar(binding!!.root, R.string.error_loading_images)
+ initErrorView()
+ } catch (e: Exception) {
+ Timber.e(e)
+ }
+ }
+
+ /**
+ * Handles the UI updates for a error scenario
+ */
+ private fun initErrorView() {
+ binding!!.loadingImagesProgressBar.visibility = View.GONE
+ if (gridAdapter == null || gridAdapter!!.isEmpty) {
+ binding!!.statusMessage.visibility = View.VISIBLE
+ binding!!.statusMessage.text = getString(R.string.no_images_found)
+ } else {
+ binding!!.statusMessage.visibility = View.GONE
+ }
+ }
+
+ /**
+ * Handles the UI updates when there is no bookmarks
+ */
+ private fun initEmptyBookmarkListView() {
+ binding!!.loadingImagesProgressBar.visibility = View.GONE
+ if (gridAdapter == null || gridAdapter!!.isEmpty) {
+ binding!!.statusMessage.visibility = View.VISIBLE
+ binding!!.statusMessage.text = getString(R.string.bookmark_empty)
+ } else {
+ binding!!.statusMessage.visibility = View.GONE
+ }
+ }
+
+ /**
+ * Handles the success scenario
+ * On first load, it initializes the grid view. On subsequent loads, it adds items to the adapter
+ * @param collection List of new Media to be displayed
+ */
+ private fun handleSuccess(collection: List?) {
+ if (collection == null) {
+ initErrorView()
+ return
+ }
+ if (collection.isEmpty()) {
+ initEmptyBookmarkListView()
+ return
+ }
+
+ if (gridAdapter == null) {
+ setAdapter(collection)
+ } else {
+ if (gridAdapter!!.containsAll(collection)) {
+ binding!!.loadingImagesProgressBar.visibility = View.GONE
+ binding!!.statusMessage.visibility = View.GONE
+ binding!!.bookmarkedPicturesList.visibility = View.VISIBLE
+ binding!!.bookmarkedPicturesList.adapter = gridAdapter
+ return
+ }
+ gridAdapter!!.addItems(collection)
+ (parentFragment as BookmarkListRootFragment).viewPagerNotifyDataSetChanged()
+ }
+ binding!!.loadingImagesProgressBar.visibility = View.GONE
+ binding!!.statusMessage.visibility = View.GONE
+ binding!!.bookmarkedPicturesList.visibility = View.VISIBLE
+ }
+
+ /**
+ * Initializes the adapter with a list of Media objects
+ * @param mediaList List of new Media to be displayed
+ */
+ private fun setAdapter(mediaList: List) {
+ gridAdapter = GridViewAdapter(
+ requireContext(),
+ R.layout.layout_category_images,
+ mediaList.toMutableList()
+ )
+ binding?.let { it.bookmarkedPicturesList.adapter = gridAdapter }
+ }
+
+ /**
+ * It return an instance of gridView adapter which helps in extracting media details
+ * used by the gridView
+ * @return GridView Adapter
+ */
+ fun getAdapter(): ListAdapter? = binding?.bookmarkedPicturesList?.adapter
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarksTable.kt b/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarksTable.kt
new file mode 100644
index 000000000..6a8f4d541
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarksTable.kt
@@ -0,0 +1,54 @@
+package fr.free.nrw.commons.bookmarks.pictures
+
+import android.database.sqlite.SQLiteDatabase
+
+object BookmarksTable {
+ const val TABLE_NAME: String = "bookmarks"
+ const val COLUMN_MEDIA_NAME: String = "media_name"
+ const val COLUMN_CREATOR: String = "media_creator"
+
+ // NOTE! KEEP IN SAME ORDER AS THEY ARE DEFINED UP THERE. HELPS HARD CODE COLUMN INDICES.
+ val ALL_FIELDS = arrayOf(
+ COLUMN_MEDIA_NAME,
+ COLUMN_CREATOR
+ )
+
+ const val DROP_TABLE_STATEMENT: String = "DROP TABLE IF EXISTS $TABLE_NAME"
+
+ const val CREATE_TABLE_STATEMENT: String = ("CREATE TABLE $TABLE_NAME (" +
+ "$COLUMN_MEDIA_NAME STRING PRIMARY KEY, " +
+ "$COLUMN_CREATOR STRING" +
+ ");")
+
+ fun onCreate(db: SQLiteDatabase) =
+ db.execSQL(CREATE_TABLE_STATEMENT)
+
+ fun onDelete(db: SQLiteDatabase) {
+ db.execSQL(DROP_TABLE_STATEMENT)
+ onCreate(db)
+ }
+
+ fun onUpdate(db: SQLiteDatabase, from: Int, to: Int) {
+ if (from == to) {
+ return
+ }
+
+ if (from < 7) {
+ // doesn't exist yet
+ onUpdate(db, from+1, to)
+ return
+ }
+
+ if (from == 7) {
+ // table added in version 8
+ onCreate(db)
+ onUpdate(db, from+1, to)
+ return
+ }
+
+ if (from == 8) {
+ onUpdate(db, from+1, to)
+ return
+ }
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/campaigns/CampaignConfig.kt b/app/src/main/java/fr/free/nrw/commons/campaigns/CampaignConfig.kt
index 6bf0bc0ed..9f94e8592 100644
--- a/app/src/main/java/fr/free/nrw/commons/campaigns/CampaignConfig.kt
+++ b/app/src/main/java/fr/free/nrw/commons/campaigns/CampaignConfig.kt
@@ -7,8 +7,8 @@ import com.google.gson.annotations.SerializedName
*/
class CampaignConfig {
@SerializedName("showOnlyLiveCampaigns")
- private val showOnlyLiveCampaigns = false
+ var showOnlyLiveCampaigns = false
@SerializedName("sortBy")
- private val sortBy: String? = null
-}
+ var sortBy: String? = null
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/campaigns/CampaignResponseDTO.kt b/app/src/main/java/fr/free/nrw/commons/campaigns/CampaignResponseDTO.kt
index 767732eb7..1656109e7 100644
--- a/app/src/main/java/fr/free/nrw/commons/campaigns/CampaignResponseDTO.kt
+++ b/app/src/main/java/fr/free/nrw/commons/campaigns/CampaignResponseDTO.kt
@@ -8,8 +8,8 @@ import fr.free.nrw.commons.campaigns.models.Campaign
*/
class CampaignResponseDTO {
@SerializedName("config")
- val campaignConfig: CampaignConfig? = null
+ var campaignConfig: CampaignConfig? = null
@SerializedName("campaigns")
- val campaigns: List? = null
-}
+ var campaigns: List? = null
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategoryContentProvider.kt b/app/src/main/java/fr/free/nrw/commons/category/CategoryContentProvider.kt
index ddd7f5ae4..f5cec0fce 100644
--- a/app/src/main/java/fr/free/nrw/commons/category/CategoryContentProvider.kt
+++ b/app/src/main/java/fr/free/nrw/commons/category/CategoryContentProvider.kt
@@ -9,12 +9,9 @@ import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteQueryBuilder
import android.net.Uri
import android.text.TextUtils
-import androidx.annotation.NonNull
import fr.free.nrw.commons.BuildConfig
-import fr.free.nrw.commons.data.DBOpenHelper
import fr.free.nrw.commons.di.CommonsDaggerContentProvider
-import timber.log.Timber
-import javax.inject.Inject
+import androidx.core.net.toUri
class CategoryContentProvider : CommonsDaggerContentProvider() {
@@ -23,9 +20,6 @@ class CategoryContentProvider : CommonsDaggerContentProvider() {
addURI(BuildConfig.CATEGORY_AUTHORITY, "${BASE_PATH}/#", CATEGORIES_ID)
}
- @Inject
- lateinit var dbOpenHelper: DBOpenHelper
-
@SuppressWarnings("ConstantConditions")
override fun query(uri: Uri, projection: Array?, selection: String?,
selectionArgs: Array?, sortOrder: String?): Cursor? {
@@ -34,7 +28,7 @@ class CategoryContentProvider : CommonsDaggerContentProvider() {
}
val uriType = uriMatcher.match(uri)
- val db = dbOpenHelper.readableDatabase
+ val db = requireDb()
val cursor: Cursor? = when (uriType) {
CATEGORIES -> queryBuilder.query(
@@ -58,45 +52,37 @@ class CategoryContentProvider : CommonsDaggerContentProvider() {
else -> throw IllegalArgumentException("Unknown URI $uri")
}
- cursor?.setNotificationUri(context?.contentResolver, uri)
+ cursor?.setNotificationUri(requireContext().contentResolver, uri)
return cursor
}
- override fun getType(uri: Uri): String? {
- return null
- }
+ override fun getType(uri: Uri): String? = null
@SuppressWarnings("ConstantConditions")
- override fun insert(uri: Uri, contentValues: ContentValues?): Uri? {
+ override fun insert(uri: Uri, contentValues: ContentValues?): Uri {
val uriType = uriMatcher.match(uri)
- val sqlDB = dbOpenHelper.writableDatabase
val id: Long
when (uriType) {
CATEGORIES -> {
- id = sqlDB.insert(TABLE_NAME, null, contentValues)
+ id = requireDb().insert(TABLE_NAME, null, contentValues)
}
else -> throw IllegalArgumentException("Unknown URI: $uri")
}
- context?.contentResolver?.notifyChange(uri, null)
- return Uri.parse("${Companion.BASE_URI}/$id")
+ requireContext().contentResolver?.notifyChange(uri, null)
+ return "${BASE_URI}/$id".toUri()
}
@SuppressWarnings("ConstantConditions")
- override fun delete(uri: Uri, selection: String?, selectionArgs: Array?): Int {
- // Not implemented
- return 0
- }
+ override fun delete(uri: Uri, selection: String?, selectionArgs: Array?): Int = 0
@SuppressWarnings("ConstantConditions")
override fun bulkInsert(uri: Uri, values: Array): Int {
- Timber.d("Hello, bulk insert! (CategoryContentProvider)")
val uriType = uriMatcher.match(uri)
- val sqlDB = dbOpenHelper.writableDatabase
+ val sqlDB = requireDb()
sqlDB.beginTransaction()
when (uriType) {
CATEGORIES -> {
for (value in values) {
- Timber.d("Inserting! %s", value)
sqlDB.insert(TABLE_NAME, null, value)
}
sqlDB.setTransactionSuccessful()
@@ -104,7 +90,7 @@ class CategoryContentProvider : CommonsDaggerContentProvider() {
else -> throw IllegalArgumentException("Unknown URI: $uri")
}
sqlDB.endTransaction()
- context?.contentResolver?.notifyChange(uri, null)
+ requireContext().contentResolver?.notifyChange(uri, null)
return values.size
}
@@ -112,17 +98,18 @@ class CategoryContentProvider : CommonsDaggerContentProvider() {
override fun update(uri: Uri, contentValues: ContentValues?, selection: String?,
selectionArgs: Array?): Int {
val uriType = uriMatcher.match(uri)
- val sqlDB = dbOpenHelper.writableDatabase
val rowsUpdated: Int
when (uriType) {
CATEGORIES_ID -> {
if (TextUtils.isEmpty(selection)) {
val id = uri.lastPathSegment?.toInt()
?: throw IllegalArgumentException("Invalid ID")
- rowsUpdated = sqlDB.update(TABLE_NAME,
+ rowsUpdated = requireDb().update(
+ TABLE_NAME,
contentValues,
"$COLUMN_ID = ?",
- arrayOf(id.toString()))
+ arrayOf(id.toString())
+ )
} else {
throw IllegalArgumentException(
"Parameter `selection` should be empty when updating an ID")
@@ -130,7 +117,7 @@ class CategoryContentProvider : CommonsDaggerContentProvider() {
}
else -> throw IllegalArgumentException("Unknown URI: $uri with type $uriType")
}
- context?.contentResolver?.notifyChange(uri, null)
+ requireContext().contentResolver?.notifyChange(uri, null)
return rowsUpdated
}
@@ -165,13 +152,9 @@ class CategoryContentProvider : CommonsDaggerContentProvider() {
"$COLUMN_TIMES_USED INTEGER" +
");"
- fun uriForId(id: Int): Uri {
- return Uri.parse("${BASE_URI}/$id")
- }
+ fun uriForId(id: Int): Uri = Uri.parse("${BASE_URI}/$id")
- fun onCreate(db: SQLiteDatabase) {
- db.execSQL(CREATE_TABLE_STATEMENT)
- }
+ fun onCreate(db: SQLiteDatabase) = db.execSQL(CREATE_TABLE_STATEMENT)
fun onDelete(db: SQLiteDatabase) {
db.execSQL(DROP_TABLE_STATEMENT)
@@ -200,6 +183,6 @@ class CategoryContentProvider : CommonsDaggerContentProvider() {
private const val CATEGORIES = 1
private const val CATEGORIES_ID = 2
private const val BASE_PATH = "categories"
- val BASE_URI: Uri = Uri.parse("content://${BuildConfig.CATEGORY_AUTHORITY}/${Companion.BASE_PATH}")
+ val BASE_URI: Uri = "content://${BuildConfig.CATEGORY_AUTHORITY}/${BASE_PATH}".toUri()
}
}
diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategoryDetailsActivity.kt b/app/src/main/java/fr/free/nrw/commons/category/CategoryDetailsActivity.kt
index c998f96ac..fefe462a9 100644
--- a/app/src/main/java/fr/free/nrw/commons/category/CategoryDetailsActivity.kt
+++ b/app/src/main/java/fr/free/nrw/commons/category/CategoryDetailsActivity.kt
@@ -23,6 +23,7 @@ import fr.free.nrw.commons.explore.categories.sub.SubCategoriesFragment
import fr.free.nrw.commons.media.MediaDetailPagerFragment
import fr.free.nrw.commons.media.MediaDetailProvider
import fr.free.nrw.commons.theme.BaseActivity
+import fr.free.nrw.commons.utils.applyEdgeToEdgeAllInsets
import fr.free.nrw.commons.utils.handleWebUrl
import fr.free.nrw.commons.wikidata.model.WikiSite
import fr.free.nrw.commons.wikidata.model.page.PageTitle
@@ -57,6 +58,7 @@ class CategoryDetailsActivity : BaseActivity(),
binding = ActivityCategoryDetailsBinding.inflate(layoutInflater)
val view = binding.root
+ applyEdgeToEdgeAllInsets(view)
setContentView(view)
supportFragmentManager = getSupportFragmentManager()
viewPagerAdapter = ViewPagerAdapter(this, supportFragmentManager)
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.kt b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.kt
index fe6c8f68b..b0bc0e405 100644
--- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.kt
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.kt
@@ -180,8 +180,8 @@ class ContributionController @Inject constructor(@param:Named("default_preferenc
showAlertDialog(
activity, activity.getString(R.string.location_permission_title),
activity.getString(R.string.in_app_camera_location_permission_rationale),
- activity.getString(android.R.string.ok),
- activity.getString(android.R.string.cancel),
+ activity.getString(R.string.ok),
+ activity.getString(R.string.cancel),
{
createDialogsAndHandleLocationPermissions(
activity,
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.kt b/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.kt
index b9fa3e395..d481017b2 100644
--- a/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.kt
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.kt
@@ -34,6 +34,7 @@ import fr.free.nrw.commons.quiz.QuizChecker
import fr.free.nrw.commons.settings.SettingsFragment
import fr.free.nrw.commons.startWelcome
import fr.free.nrw.commons.theme.BaseActivity
+import fr.free.nrw.commons.utils.applyEdgeToEdgeAllInsets
import fr.free.nrw.commons.upload.UploadProgressActivity
import fr.free.nrw.commons.upload.worker.WorkRequestHelper.Companion.makeOneTimeWorkRequest
import fr.free.nrw.commons.utils.ViewUtilWrapper
@@ -112,6 +113,7 @@ class MainActivity : BaseActivity(), FragmentManager.OnBackStackChangedListener
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = MainBinding.inflate(layoutInflater)
+ applyEdgeToEdgeAllInsets(binding!!.root)
setContentView(binding!!.root)
setSupportActionBar(binding!!.toolbarBinding.toolbar)
tabLayout = binding!!.fragmentMainNavTabLayout
@@ -151,21 +153,7 @@ after opening the app.
}
}
setUpPager()
- /**
- * Ask the user for media location access just after login
- * so that location in the EXIF metadata of the images shared by the user
- * is retained on devices running Android 10 or above
- */
-// if (VERSION.SDK_INT >= VERSION_CODES.Q) {
-// ActivityCompat.requestPermissions(this,
-// new String[]{Manifest.permission.ACCESS_MEDIA_LOCATION}, 0);
-// PermissionUtils.checkPermissionsAndPerformAction(
-// this,
-// () -> {},
-// R.string.media_location_permission_denied,
-// R.string.add_location_manually,
-// permission.ACCESS_MEDIA_LOCATION);
-// }
+
checkAndResumeStuckUploads()
}
}
@@ -336,7 +324,7 @@ after opening the app.
)
.subscribeOn(Schedulers.io())
.blockingGet()
- Timber.d("Resuming " + stuckUploads.size + " uploads...")
+ Timber.d("Resuming %d uploads...", stuckUploads.size)
if (!stuckUploads.isEmpty()) {
for (contribution in stuckUploads) {
contribution.state = Contribution.STATE_QUEUED
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/SetWallpaperWorker.kt b/app/src/main/java/fr/free/nrw/commons/contributions/SetWallpaperWorker.kt
index 06c31fede..8e899fcba 100644
--- a/app/src/main/java/fr/free/nrw/commons/contributions/SetWallpaperWorker.kt
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/SetWallpaperWorker.kt
@@ -45,10 +45,10 @@ class SetWallpaperWorker(context: Context, params: WorkerParameters) :
}
}
- override fun onFailureImpl(dataSource: DataSource>?) {
+ override fun onFailureImpl(dataSource: DataSource?>) {
Timber.d("Error getting bitmap from image url %s", imageUrl.toString())
showNotification(context, "Setting Wallpaper Failed", "Failed to download image.")
- dataSource?.close()
+ dataSource.close()
}
}, CallerThreadExecutor.getInstance())
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/model/Folder.kt b/app/src/main/java/fr/free/nrw/commons/customselector/model/Folder.kt
index ec08f6f73..4bf295f4c 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/model/Folder.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/model/Folder.kt
@@ -39,4 +39,11 @@ data class Folder(
return true
}
+
+ override fun hashCode(): Int {
+ var result = bucketId.hashCode()
+ result = 31 * result + name.hashCode()
+ result = 31 * result + images.hashCode()
+ return result
+ }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/model/Image.kt b/app/src/main/java/fr/free/nrw/commons/customselector/model/Image.kt
index a2965fb5d..a172f28e2 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/model/Image.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/model/Image.kt
@@ -1,6 +1,7 @@
package fr.free.nrw.commons.customselector.model
import android.net.Uri
+import android.os.Build
import android.os.Parcel
import android.os.Parcelable
@@ -48,7 +49,12 @@ data class Image(
this(
parcel.readLong(),
parcel.readString()!!,
- parcel.readParcelable(Uri::class.java.classLoader)!!,
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ parcel.readParcelable(Uri::class.java.classLoader, Uri::class.java)!!
+ } else {
+ @Suppress("DEPRECATION")
+ parcel.readParcelable(Uri::class.java.classLoader)!!
+ },
parcel.readString()!!,
parcel.readLong(),
parcel.readString()!!,
@@ -121,4 +127,16 @@ data class Image(
override fun newArray(size: Int): Array = arrayOfNulls(size)
}
+
+ override fun hashCode(): Int {
+ var result = id.hashCode()
+ result = 31 * result + bucketId.hashCode()
+ result = 31 * result + name.hashCode()
+ result = 31 * result + uri.hashCode()
+ result = 31 * result + path.hashCode()
+ result = 31 * result + bucketName.hashCode()
+ result = 31 * result + sha1.hashCode()
+ result = 31 * result + date.hashCode()
+ return result
+ }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/ImageAdapter.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/ImageAdapter.kt
index 62a440ff4..c3ef4a784 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/ImageAdapter.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/ImageAdapter.kt
@@ -168,8 +168,7 @@ class ImageAdapter(
// Getting selected index when switch is off
} else if (actionableImagesMap.size > position) {
- ImageHelper
- .getIndex(selectedImages, ArrayList(actionableImagesMap.values)[position])
+ ImageHelper.getIndex(selectedImages, ArrayList(actionableImagesMap.values)[position])
// For any other case return -1
} else {
@@ -348,8 +347,14 @@ class ImageAdapter(
numberOfSelectedImagesMarkedAsNotForUpload--
}
notifyItemChanged(position, ImageUnselected())
+ // Notify listener of deselection to update UI
+ imageSelectListener.onSelectedImagesChanged(selectedImages, numberOfSelectedImagesMarkedAsNotForUpload)
} else {
- val image = images[position]
+ // Prevent adding the same image multiple times
+ val image = if (showAlreadyActionedImages) images[position] else ArrayList(actionableImagesMap.values)[position]
+ if (selectedImages.contains(image)) {
+ return // Image already selected, ignore additional clicks
+ }
scope.launch(ioDispatcher) {
val imageSHA1 = imageLoader.getSHA1(image, defaultDispatcher)
withContext(Dispatchers.Main) {
@@ -373,7 +378,6 @@ class ImageAdapter(
}
selectedImages.add(image)
notifyItemChanged(position, ImageSelectedOrUpdated())
-
imageSelectListener.onSelectedImagesChanged(selectedImages, numberOfSelectedImagesMarkedAsNotForUpload)
}
}
@@ -632,4 +636,4 @@ class ImageAdapter(
fun setSingleSelection(single: Boolean) {
singleSelection = single
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorActivity.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorActivity.kt
index 7e7d7e4cd..2534b4aeb 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorActivity.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorActivity.kt
@@ -40,6 +40,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat
+import androidx.core.view.ViewGroupCompat
import androidx.lifecycle.ViewModelProvider
import fr.free.nrw.commons.R
import fr.free.nrw.commons.customselector.database.NotForUploadStatus
@@ -56,6 +57,8 @@ import fr.free.nrw.commons.media.ZoomableActivity
import fr.free.nrw.commons.theme.BaseActivity
import fr.free.nrw.commons.upload.FileUtilsWrapper
import fr.free.nrw.commons.utils.CustomSelectorUtils
+import fr.free.nrw.commons.utils.applyEdgeToEdgeBottomPaddingInsets
+import fr.free.nrw.commons.utils.applyEdgeToEdgeTopInsets
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -198,6 +201,9 @@ class CustomSelectorActivity :
.fillMaxWidth(),
)
}
+ ViewGroupCompat.installCompatInsetsDispatch(binding.root)
+ applyEdgeToEdgeTopInsets(toolbarBinding.toolbarLayout)
+ bottomSheetBinding.bottomLayout.applyEdgeToEdgeBottomPaddingInsets()
val view = binding.root
setContentView(view)
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/FolderFragment.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/FolderFragment.kt
index 6ca2b06e4..0c3c5bdd0 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/FolderFragment.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/FolderFragment.kt
@@ -18,6 +18,7 @@ import fr.free.nrw.commons.databinding.FragmentCustomSelectorBinding
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment
import fr.free.nrw.commons.media.MediaClient
import fr.free.nrw.commons.upload.FileProcessor
+import fr.free.nrw.commons.utils.applyEdgeToEdgeBottomPaddingInsets
import javax.inject.Inject
/**
@@ -99,6 +100,7 @@ class FolderFragment : CommonsDaggerSupportFragment() {
selectorRV = binding?.selectorRv
loader = binding?.loader
with(binding?.selectorRv) {
+ this?.applyEdgeToEdgeBottomPaddingInsets()
this?.layoutManager = gridLayoutManager
this?.setHasFixedSize(true)
this?.adapter = folderAdapter
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageFragment.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageFragment.kt
index 6e08e30f1..a5182fe62 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageFragment.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageFragment.kt
@@ -9,7 +9,6 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ProgressBar
-import android.widget.Switch
import androidx.appcompat.app.AlertDialog
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.isVisible
@@ -20,6 +19,7 @@ import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
+import com.google.android.material.switchmaterial.SwitchMaterial
import fr.free.nrw.commons.contributions.Contribution
import fr.free.nrw.commons.contributions.ContributionDao
import fr.free.nrw.commons.customselector.database.NotForUploadStatusDao
@@ -41,11 +41,13 @@ import fr.free.nrw.commons.media.MediaClient
import fr.free.nrw.commons.theme.BaseActivity
import fr.free.nrw.commons.upload.FileProcessor
import fr.free.nrw.commons.upload.FileUtilsWrapper
+import fr.free.nrw.commons.utils.applyEdgeToEdgeBottomPaddingInsets
import io.reactivex.schedulers.Schedulers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
+import timber.log.Timber
import java.util.TreeMap
import javax.inject.Inject
import kotlin.collections.ArrayList
@@ -80,7 +82,7 @@ class ImageFragment :
*/
private var selectorRV: RecyclerView? = null
private var loader: ProgressBar? = null
- private var switch: Switch? = null
+ private var switch: SwitchMaterial? = null
lateinit var filteredImages: ArrayList
/**
@@ -210,13 +212,18 @@ class ImageFragment :
savedInstanceState: Bundle?,
): View? {
_binding = FragmentCustomSelectorBinding.inflate(inflater, container, false)
- imageAdapter =
- ImageAdapter(requireActivity(), activity as ImageSelectListener, imageLoader!!)
+
+ // ensures imageAdapter is initialized
+ if (!::imageAdapter.isInitialized) {
+ imageAdapter = ImageAdapter(requireActivity(), activity as ImageSelectListener, imageLoader!!)
+ Timber.d("Initialized imageAdapter in onCreateView")
+ }
// Set single selection mode if needed
val singleSelection = (activity as? CustomSelectorActivity)?.intent?.getBooleanExtra(CustomSelectorActivity.EXTRA_SINGLE_SELECTION, false) == true
imageAdapter.setSingleSelection(singleSelection)
gridLayoutManager = GridLayoutManager(context, getSpanCount())
with(binding?.selectorRv) {
+ this?.applyEdgeToEdgeBottomPaddingInsets()
this?.layoutManager = gridLayoutManager
this?.setHasFixedSize(true)
this?.adapter = imageAdapter
@@ -368,7 +375,12 @@ class ImageFragment :
* notifyDataSetChanged, rebuild the holder views to account for deleted images.
*/
override fun onResume() {
- imageAdapter.notifyDataSetChanged()
+ if (::imageAdapter.isInitialized) {
+ imageAdapter.notifyDataSetChanged()
+ Timber.d("Notified imageAdapter in onResume")
+ } else {
+ Timber.w("imageAdapter not initialized in onResume")
+ }
super.onResume()
}
@@ -378,14 +390,19 @@ class ImageFragment :
* Save the Image Fragment state.
*/
override fun onDestroy() {
- imageAdapter.cleanUp()
+ if (::imageAdapter.isInitialized) {
+ imageAdapter.cleanUp()
+ Timber.d("Cleaned up imageAdapter in onDestroy")
+ } else {
+ Timber.w("imageAdapter not initialized in onDestroy, skipping cleanup")
+ }
val position =
- (selectorRV?.layoutManager as GridLayoutManager)
- .findFirstVisibleItemPosition()
+ (selectorRV?.layoutManager as? GridLayoutManager)
+ ?.findFirstVisibleItemPosition() ?: -1
- // Check for empty RecyclerView.
- if (position != -1 && filteredImages.size > 0) {
+ // check for valid position and non-empty image list
+ if (position != -1 && filteredImages.isNotEmpty() && ::imageAdapter.isInitialized) {
context?.let { context ->
context
.getSharedPreferences(
@@ -394,34 +411,57 @@ class ImageFragment :
)?.let { prefs ->
prefs.edit()?.let { editor ->
editor.putLong("ItemId", imageAdapter.getImageIdAt(position))?.apply()
+ Timber.d("Saved last visible item ID: %d", imageAdapter.getImageIdAt(position))
}
}
}
+ } else {
+ Timber.d("Skipped saving item ID: position=%d, filteredImages.size=%d, imageAdapter initialized=%b",
+ position, filteredImages.size, ::imageAdapter.isInitialized)
}
super.onDestroy()
}
override fun onDestroyView() {
_binding = null
+ selectorRV = null
+ loader = null
+ switch = null
+ progressLayout = null
super.onDestroyView()
}
override fun refresh() {
- imageAdapter.refresh(filteredImages, allImages, getUploadingContributions())
+ if (::imageAdapter.isInitialized) {
+ imageAdapter.refresh(filteredImages, allImages, getUploadingContributions())
+ Timber.d("Refreshed imageAdapter")
+ } else {
+ Timber.w("imageAdapter not initialized in refresh")
+ }
}
/**
* Removes the image from the actionable image map
*/
fun removeImage(image: Image) {
- imageAdapter.removeImageFromActionableImageMap(image)
+ if (::imageAdapter.isInitialized) {
+ imageAdapter.removeImageFromActionableImageMap(image)
+ Timber.d("Removed image from actionable image map")
+ } else {
+ Timber.w("imageAdapter not initialized in removeImage")
+ }
}
/**
* Clears the selected images
*/
fun clearSelectedImages() {
- imageAdapter.clearSelectedImages()
+ if (::imageAdapter.isInitialized) {
+ imageAdapter.clearSelectedImages()
+ Timber.d("Cleared selected images")
+ } else {
+ Timber.w("imageAdapter not initialized in clearSelectedImages")
+ }
}
/**
@@ -432,6 +472,15 @@ class ImageFragment :
selectedImages: ArrayList,
shouldRefresh: Boolean,
) {
+ if (::imageAdapter.isInitialized) {
+ imageAdapter.setSelectedImages(selectedImages)
+ if (shouldRefresh) {
+ imageAdapter.refresh(filteredImages, allImages, getUploadingContributions())
+ }
+ Timber.d("Passed %d selected images to imageAdapter, shouldRefresh=%b", selectedImages.size, shouldRefresh)
+ } else {
+ Timber.w("imageAdapter not initialized in passSelectedImages")
+ }
}
/**
@@ -441,6 +490,7 @@ class ImageFragment :
if (!progressDialog.isShowing) {
progressDialogLayout.progressDialogText.text = text
progressDialog.show()
+ Timber.d("Showing mark/unmark progress dialog: %s", text)
}
}
@@ -450,6 +500,7 @@ class ImageFragment :
fun dismissMarkUnmarkProgressDialog() {
if (progressDialog.isShowing) {
progressDialog.dismiss()
+ Timber.d("Dismissed mark/unmark progress dialog")
}
}
@@ -459,4 +510,4 @@ class ImageFragment :
listOf(Contribution.STATE_IN_PROGRESS, Contribution.STATE_FAILED, Contribution.STATE_QUEUED, Contribution.STATE_PAUSED),
)?.subscribeOn(Schedulers.io())
?.blockingGet() ?: emptyList()
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/data/DBOpenHelper.kt b/app/src/main/java/fr/free/nrw/commons/data/DBOpenHelper.kt
index 7cb7f60f7..55ddec5bc 100644
--- a/app/src/main/java/fr/free/nrw/commons/data/DBOpenHelper.kt
+++ b/app/src/main/java/fr/free/nrw/commons/data/DBOpenHelper.kt
@@ -4,11 +4,10 @@ import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteException
import android.database.sqlite.SQLiteOpenHelper
-import fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao
-import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao
-import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao
+import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable
+import fr.free.nrw.commons.bookmarks.pictures.BookmarksTable
import fr.free.nrw.commons.category.CategoryDao
-import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao
+import fr.free.nrw.commons.explore.recentsearches.RecentSearchesTable
import fr.free.nrw.commons.recentlanguages.RecentLanguagesDao
@@ -30,17 +29,17 @@ class DBOpenHelper(
*/
override fun onCreate(db: SQLiteDatabase) {
CategoryDao.Table.onCreate(db)
- BookmarkPicturesDao.Table.onCreate(db)
- BookmarkItemsDao.Table.onCreate(db)
- RecentSearchesDao.Table.onCreate(db)
+ BookmarksTable.onCreate(db)
+ BookmarkItemsTable.onCreate(db)
+ RecentSearchesTable.onCreate(db)
RecentLanguagesDao.Table.onCreate(db)
}
override fun onUpgrade(db: SQLiteDatabase, from: Int, to: Int) {
CategoryDao.Table.onUpdate(db, from, to)
- BookmarkPicturesDao.Table.onUpdate(db, from, to)
- BookmarkItemsDao.Table.onUpdate(db, from, to)
- RecentSearchesDao.Table.onUpdate(db, from, to)
+ BookmarksTable.onUpdate(db, from, to)
+ BookmarkItemsTable.onUpdate(db, from, to)
+ RecentSearchesTable.onUpdate(db, from, to)
RecentLanguagesDao.Table.onUpdate(db, from, to)
deleteTable(db, CONTRIBUTIONS_TABLE)
deleteTable(db, BOOKMARKS_LOCATIONS)
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 44cefe4d5..b1f1b7f9b 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
@@ -7,6 +7,7 @@ import android.speech.RecognizerIntent
import android.view.View
import androidx.activity.result.ActivityResult
import androidx.activity.result.contract.ActivityResultContracts
+import androidx.core.view.WindowCompat
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import fr.free.nrw.commons.CommonsApplication
@@ -20,9 +21,11 @@ import fr.free.nrw.commons.description.EditDescriptionConstants.WIKITEXT
import fr.free.nrw.commons.recentlanguages.RecentLanguagesDao
import fr.free.nrw.commons.settings.Prefs
import fr.free.nrw.commons.theme.BaseActivity
+import fr.free.nrw.commons.utils.applyEdgeToEdgeBottomInsets
import fr.free.nrw.commons.upload.UploadMediaDetail
import fr.free.nrw.commons.upload.UploadMediaDetailAdapter
import fr.free.nrw.commons.utils.DialogUtil.showAlertDialog
+import fr.free.nrw.commons.utils.applyEdgeToEdgeTopPaddingInsets
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.functions.Consumer
import io.reactivex.schedulers.Schedulers
@@ -87,6 +90,10 @@ class DescriptionEditActivity :
super.onCreate(savedInstanceState)
binding = ActivityDescriptionEditBinding.inflate(layoutInflater)
+ applyEdgeToEdgeBottomInsets(binding.btnEditSubmit)
+ WindowCompat.getInsetsController(window, window.decorView)
+ .isAppearanceLightStatusBars = false
+ binding.toolbar.applyEdgeToEdgeTopPaddingInsets()
setContentView(binding.root)
val bundle = intent.extras
@@ -143,7 +150,7 @@ class DescriptionEditActivity :
this,
getString(titleStringID),
getString(messageStringId),
- getString(android.R.string.ok),
+ getString(R.string.ok),
null
)
}
diff --git a/app/src/main/java/fr/free/nrw/commons/di/CommonsDaggerContentProvider.kt b/app/src/main/java/fr/free/nrw/commons/di/CommonsDaggerContentProvider.kt
index c1bda689c..4c77d1aad 100644
--- a/app/src/main/java/fr/free/nrw/commons/di/CommonsDaggerContentProvider.kt
+++ b/app/src/main/java/fr/free/nrw/commons/di/CommonsDaggerContentProvider.kt
@@ -1,14 +1,25 @@
package fr.free.nrw.commons.di
import android.content.ContentProvider
+import android.database.sqlite.SQLiteDatabase
+import fr.free.nrw.commons.data.DBOpenHelper
import fr.free.nrw.commons.di.ApplicationlessInjection.Companion.getInstance
+import javax.inject.Inject
abstract class CommonsDaggerContentProvider : ContentProvider() {
+ @JvmField
+ @Inject
+ var dbOpenHelper: DBOpenHelper? = null
+
override fun onCreate(): Boolean {
inject()
return true
}
+ fun requireDbOpenHelper(): DBOpenHelper = dbOpenHelper!!
+
+ fun requireDb(): SQLiteDatabase = requireDbOpenHelper().writableDatabase!!
+
private fun inject() {
val injection = getInstance(context!!)
diff --git a/app/src/main/java/fr/free/nrw/commons/di/NetworkingModule.kt b/app/src/main/java/fr/free/nrw/commons/di/NetworkingModule.kt
index 2539db312..9246ff303 100644
--- a/app/src/main/java/fr/free/nrw/commons/di/NetworkingModule.kt
+++ b/app/src/main/java/fr/free/nrw/commons/di/NetworkingModule.kt
@@ -7,6 +7,7 @@ import dagger.Provides
import fr.free.nrw.commons.BetaConstants
import fr.free.nrw.commons.BuildConfig
import fr.free.nrw.commons.OkHttpConnectionFactory
+import fr.free.nrw.commons.CommonHeaderRequestInterceptor
import fr.free.nrw.commons.actions.PageEditClient
import fr.free.nrw.commons.actions.PageEditInterface
import fr.free.nrw.commons.actions.ThanksInterface
@@ -60,6 +61,7 @@ class NetworkingModule {
.connectTimeout(120, TimeUnit.SECONDS)
.writeTimeout(120, TimeUnit.SECONDS)
.addInterceptor(httpLoggingInterceptor)
+ .addInterceptor(CommonHeaderRequestInterceptor())
.readTimeout(120, TimeUnit.SECONDS)
.cache(Cache(File(context.cacheDir, "okHttpCache"), OK_HTTP_CACHE_SIZE))
.build()
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/ExploreFragment.java b/app/src/main/java/fr/free/nrw/commons/explore/ExploreFragment.java
deleted file mode 100644
index 475d14287..000000000
--- a/app/src/main/java/fr/free/nrw/commons/explore/ExploreFragment.java
+++ /dev/null
@@ -1,260 +0,0 @@
-package fr.free.nrw.commons.explore;
-
-import static androidx.viewpager.widget.ViewPager.SCROLL_STATE_IDLE;
-import static fr.free.nrw.commons.ViewPagerAdapter.pairOf;
-
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentPagerAdapter;
-import androidx.viewpager.widget.ViewPager.OnPageChangeListener;
-import fr.free.nrw.commons.R;
-import fr.free.nrw.commons.ViewPagerAdapter;
-import fr.free.nrw.commons.contributions.MainActivity;
-import fr.free.nrw.commons.databinding.FragmentExploreBinding;
-import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
-import fr.free.nrw.commons.kvstore.JsonKvStore;
-import fr.free.nrw.commons.theme.BaseActivity;
-import fr.free.nrw.commons.utils.ActivityUtils;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Locale;
-import javax.inject.Inject;
-import javax.inject.Named;
-import kotlin.Pair;
-
-public class ExploreFragment extends CommonsDaggerSupportFragment {
-
- private static final String FEATURED_IMAGES_CATEGORY = "Featured_pictures_on_Wikimedia_Commons";
- private static final String MOBILE_UPLOADS_CATEGORY = "Uploaded_with_Mobile/Android";
- private static final String EXPLORE_MAP = "Map";
- private static final String MEDIA_DETAILS_FRAGMENT_TAG = "MediaDetailsFragment";
-
-
- public FragmentExploreBinding binding;
- ViewPagerAdapter viewPagerAdapter;
- private ExploreListRootFragment featuredRootFragment;
- private ExploreListRootFragment mobileRootFragment;
- private ExploreMapRootFragment mapRootFragment;
- @Inject
- @Named("default_preferences")
- public JsonKvStore applicationKvStore;
-
- // Nearby map state (for if we came from Nearby fragment)
- private double prevZoom;
- private double prevLatitude;
- private double prevLongitude;
-
- public void setScroll(boolean canScroll) {
- if (binding != null) {
- binding.viewPager.setCanScroll(canScroll);
- }
- }
-
- @NonNull
- public static ExploreFragment newInstance() {
- ExploreFragment fragment = new ExploreFragment();
- fragment.setRetainInstance(true);
- return fragment;
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
- @Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- loadNearbyMapData();
- binding = FragmentExploreBinding.inflate(inflater, container, false);
-
- viewPagerAdapter = new ViewPagerAdapter(requireContext(), getChildFragmentManager(),
- FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
-
- binding.viewPager.setAdapter(viewPagerAdapter);
- binding.viewPager.setId(R.id.viewPager);
- binding.tabLayout.setupWithViewPager(binding.viewPager);
- binding.viewPager.addOnPageChangeListener(new OnPageChangeListener() {
- @Override
- public void onPageScrolled(int position, float positionOffset,
- int positionOffsetPixels) {
-
- }
-
- @Override
- public void onPageSelected(int position) {
- if (position == 2) {
- binding.viewPager.setCanScroll(false);
- } else {
- binding.viewPager.setCanScroll(true);
- }
- }
-
- @Override
- public void onPageScrollStateChanged(int state) {
-
- }
- });
- setTabs();
- setHasOptionsMenu(true);
-
- // if we came from 'Show in Explore' in Nearby, jump to Map tab
- if (isCameFromNearbyMap()) {
- binding.viewPager.setCurrentItem(2);
- }
- return binding.getRoot();
- }
-
- /**
- * Sets the titles in the tabLayout and fragments in the viewPager
- */
- public void setTabs() {
- Bundle featuredArguments = new Bundle();
- featuredArguments.putString("categoryName", FEATURED_IMAGES_CATEGORY);
-
- Bundle mobileArguments = new Bundle();
- mobileArguments.putString("categoryName", MOBILE_UPLOADS_CATEGORY);
-
- Bundle mapArguments = new Bundle();
- mapArguments.putString("categoryName", EXPLORE_MAP);
-
- // if we came from 'Show in Explore' in Nearby, pass on zoom and center to Explore map root
- if (isCameFromNearbyMap()) {
- mapArguments.putDouble("prev_zoom", prevZoom);
- mapArguments.putDouble("prev_latitude", prevLatitude);
- mapArguments.putDouble("prev_longitude", prevLongitude);
- }
-
- featuredRootFragment = new ExploreListRootFragment(featuredArguments);
- mobileRootFragment = new ExploreListRootFragment(mobileArguments);
- mapRootFragment = new ExploreMapRootFragment(mapArguments);
-
- ((MainActivity) getActivity()).showTabs();
- ((BaseActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(false);
-
- viewPagerAdapter.setTabs(
- pairOf(R.string.explore_tab_title_featured, featuredRootFragment),
- pairOf(R.string.explore_tab_title_mobile, mobileRootFragment),
- pairOf(R.string.explore_tab_title_map, mapRootFragment)
- );
- viewPagerAdapter.notifyDataSetChanged();
- }
-
- /**
- * Fetch Nearby map camera data from fragment arguments if any.
- */
- public void loadNearbyMapData() {
- // get fragment arguments
- if (getArguments() != null) {
- prevZoom = getArguments().getDouble("prev_zoom");
- prevLatitude = getArguments().getDouble("prev_latitude");
- prevLongitude = getArguments().getDouble("prev_longitude");
- }
- }
-
- /**
- * Checks if fragment arguments contain data from Nearby map. if present, then the user
- * navigated from Nearby using 'Show in Explore'.
- *
- * @return true if user navigated from Nearby map
- **/
- public boolean isCameFromNearbyMap() {
- return prevZoom != 0.0 || prevLatitude != 0.0 || prevLongitude != 0.0;
- }
-
- public boolean onBackPressed() {
- if (binding.tabLayout.getSelectedTabPosition() == 0) {
- if (featuredRootFragment.backPressed()) {
- ((BaseActivity) getActivity()).getSupportActionBar()
- .setDisplayHomeAsUpEnabled(false);
- return true;
- }
- } else if (binding.tabLayout.getSelectedTabPosition() == 1) { //Mobile root fragment
- if (mobileRootFragment.backPressed()) {
- ((BaseActivity) getActivity()).getSupportActionBar()
- .setDisplayHomeAsUpEnabled(false);
- return true;
- }
- } else { //explore map fragment
- if (mapRootFragment.backPressed()) {
- ((BaseActivity) getActivity()).getSupportActionBar()
- .setDisplayHomeAsUpEnabled(false);
- return true;
- }
- }
- return false;
- }
-
- /**
- * This method inflates the menu in the toolbar
- */
- @Override
- public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
- // if logged in 'Show in Nearby' menu item is visible
- if (applicationKvStore.getBoolean("login_skipped") == false) {
- inflater.inflate(R.menu.explore_fragment_menu, menu);
-
- MenuItem others = menu.findItem(R.id.list_item_show_in_nearby);
-
- if (binding.viewPager.getCurrentItem() == 2) {
- others.setVisible(true);
- }
-
- // if on Map tab, show all menu options, else only show search
- binding.viewPager.addOnPageChangeListener(new OnPageChangeListener() {
- @Override
- public void onPageScrolled(int position, float positionOffset,
- int positionOffsetPixels) {
- }
-
- @Override
- public void onPageSelected(int position) {
- others.setVisible((position == 2));
- }
-
- @Override
- public void onPageScrollStateChanged(int state) {
- if (state == SCROLL_STATE_IDLE && binding.viewPager.getCurrentItem() == 2) {
- onPageSelected(2);
- }
- }
- });
- } else {
- inflater.inflate(R.menu.menu_search, menu);
- }
- super.onCreateOptionsMenu(menu, inflater);
- }
-
- /**
- * This method handles the logic on ItemSelect in toolbar menu Currently only 1 choice is
- * available to open search page of the app
- */
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
-
- // Handle item selection
- switch (item.getItemId()) {
- case R.id.action_search:
- ActivityUtils.startActivityWithFlags(getActivity(), SearchActivity.class);
- return true;
- case R.id.list_item_show_in_nearby:
- mapRootFragment.loadNearbyMapFromExplore();
- return true;
- default:
- return super.onOptionsItemSelected(item);
- }
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- binding = null;
- }
-}
-
-
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/ExploreFragment.kt b/app/src/main/java/fr/free/nrw/commons/explore/ExploreFragment.kt
new file mode 100644
index 000000000..bc8f9cfaa
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/explore/ExploreFragment.kt
@@ -0,0 +1,227 @@
+package fr.free.nrw.commons.explore
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.Menu
+import android.view.MenuInflater
+import android.view.MenuItem
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.FragmentPagerAdapter
+import androidx.viewpager.widget.ViewPager
+import androidx.viewpager.widget.ViewPager.OnPageChangeListener
+import fr.free.nrw.commons.R
+import fr.free.nrw.commons.ViewPagerAdapter
+import fr.free.nrw.commons.contributions.MainActivity
+import fr.free.nrw.commons.databinding.FragmentExploreBinding
+import fr.free.nrw.commons.di.CommonsDaggerSupportFragment
+import fr.free.nrw.commons.kvstore.JsonKvStore
+import fr.free.nrw.commons.theme.BaseActivity
+import fr.free.nrw.commons.utils.ActivityUtils.startActivityWithFlags
+import javax.inject.Inject
+import javax.inject.Named
+
+class ExploreFragment : CommonsDaggerSupportFragment() {
+
+ @JvmField
+ @Inject
+ @Named("default_preferences")
+ var applicationKvStore: JsonKvStore? = null
+
+ private var featuredRootFragment: ExploreListRootFragment? = null
+ private var mobileRootFragment: ExploreListRootFragment? = null
+ private var mapRootFragment: ExploreMapRootFragment? = null
+ private var prevZoom = 0.0
+ private var prevLatitude = 0.0
+ private var prevLongitude = 0.0
+ private var viewPagerAdapter: ViewPagerAdapter? = null
+ var binding: FragmentExploreBinding? = null
+
+ fun setScroll(canScroll: Boolean) {
+ if (binding != null) {
+ binding!!.viewPager.canScroll = canScroll
+ }
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ super.onCreate(savedInstanceState)
+ loadNearbyMapData()
+ binding = FragmentExploreBinding.inflate(inflater, container, false)
+
+ viewPagerAdapter = ViewPagerAdapter(
+ requireContext(), childFragmentManager,
+ FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT
+ )
+
+ binding!!.viewPager.adapter = viewPagerAdapter
+ binding!!.viewPager.id = R.id.viewPager
+ binding!!.tabLayout.setupWithViewPager(binding!!.viewPager)
+ binding!!.viewPager.addOnPageChangeListener(object : OnPageChangeListener {
+ override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) = Unit
+ override fun onPageScrollStateChanged(state: Int) = Unit
+ override fun onPageSelected(position: Int) {
+ binding!!.viewPager.canScroll = position != 2
+ if (position == 2) {
+ mapRootFragment?.requestLocationIfNeeded()
+ }
+ }
+ })
+ setTabs()
+ setHasOptionsMenu(true)
+
+ // if we came from 'Show in Explore' in Nearby, jump to Map tab
+ if (isCameFromNearbyMap) {
+ binding!!.viewPager.currentItem = 2
+ }
+ return binding!!.root
+ }
+
+ /**
+ * Sets the titles in the tabLayout and fragments in the viewPager
+ */
+ fun setTabs() {
+ val featuredArguments = Bundle()
+ featuredArguments.putString("categoryName", FEATURED_IMAGES_CATEGORY)
+
+ val mobileArguments = Bundle()
+ mobileArguments.putString("categoryName", MOBILE_UPLOADS_CATEGORY)
+
+ val mapArguments = Bundle()
+ mapArguments.putString("categoryName", EXPLORE_MAP)
+
+ // if we came from 'Show in Explore' in Nearby, pass on zoom and center to Explore map root
+ if (isCameFromNearbyMap) {
+ mapArguments.putDouble("prev_zoom", prevZoom)
+ mapArguments.putDouble("prev_latitude", prevLatitude)
+ mapArguments.putDouble("prev_longitude", prevLongitude)
+ }
+
+ featuredRootFragment = ExploreListRootFragment(featuredArguments)
+ mobileRootFragment = ExploreListRootFragment(mobileArguments)
+ mapRootFragment = ExploreMapRootFragment(mapArguments)
+
+ (activity as MainActivity).showTabs()
+ (activity as BaseActivity).supportActionBar!!.setDisplayHomeAsUpEnabled(false)
+
+ viewPagerAdapter!!.setTabs(
+ R.string.explore_tab_title_featured to featuredRootFragment!!,
+ R.string.explore_tab_title_mobile to mobileRootFragment!!,
+ R.string.explore_tab_title_map to mapRootFragment!!
+ )
+ viewPagerAdapter!!.notifyDataSetChanged()
+ }
+
+ /**
+ * Fetch Nearby map camera data from fragment arguments if any.
+ */
+ private fun loadNearbyMapData() {
+ // get fragment arguments
+ if (arguments != null) {
+ with (requireArguments()) {
+ prevZoom = getDouble("prev_zoom")
+ prevLatitude = getDouble("prev_latitude")
+ prevLongitude = getDouble("prev_longitude")
+ }
+ }
+ }
+
+ /**
+ * Checks if fragment arguments contain data from Nearby map. if present, then the user
+ * navigated from Nearby using 'Show in Explore'.
+ *
+ * @return true if user navigated from Nearby map
+ */
+ private val isCameFromNearbyMap: Boolean
+ get() = prevZoom != 0.0 || prevLatitude != 0.0 || prevLongitude != 0.0
+
+ fun onBackPressed(): Boolean {
+ if (binding!!.tabLayout.selectedTabPosition == 0) {
+ if (featuredRootFragment!!.backPressed()) {
+ (activity as BaseActivity).supportActionBar!!.setDisplayHomeAsUpEnabled(false)
+ return true
+ }
+ } else if (binding!!.tabLayout.selectedTabPosition == 1) { //Mobile root fragment
+ if (mobileRootFragment!!.backPressed()) {
+ (activity as BaseActivity).supportActionBar!!.setDisplayHomeAsUpEnabled(false)
+ return true
+ }
+ } else { //explore map fragment
+ if (mapRootFragment!!.backPressed()) {
+ (activity as BaseActivity).supportActionBar!!.setDisplayHomeAsUpEnabled(false)
+ return true
+ }
+ }
+ return false
+ }
+
+ /**
+ * This method inflates the menu in the toolbar
+ */
+ override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
+ // if logged in 'Show in Nearby' menu item is visible
+ if (applicationKvStore!!.getBoolean("login_skipped") == false) {
+ inflater.inflate(R.menu.explore_fragment_menu, menu)
+
+ val others = menu.findItem(R.id.list_item_show_in_nearby)
+
+ if (binding!!.viewPager.currentItem == 2) {
+ others.setVisible(true)
+ }
+
+ // if on Map tab, show all menu options, else only show search
+ binding!!.viewPager.addOnPageChangeListener(object : OnPageChangeListener {
+ override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) = Unit
+ override fun onPageScrollStateChanged(state: Int) = Unit
+ override fun onPageSelected(position: Int) {
+ binding!!.viewPager.canScroll = position != 2
+ others.setVisible(position == 2)
+ if (position == 2) {
+ mapRootFragment?.requestLocationIfNeeded()
+ }
+ }
+ })
+ } else {
+ inflater.inflate(R.menu.menu_search, menu)
+ }
+ super.onCreateOptionsMenu(menu, inflater)
+ }
+
+ /**
+ * This method handles the logic on ItemSelect in toolbar menu Currently only 1 choice is
+ * available to open search page of the app
+ */
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ // Handle item selection
+ when (item.itemId) {
+ R.id.action_search -> {
+ startActivityWithFlags(requireActivity(), SearchActivity::class.java)
+ return true
+ }
+
+ R.id.list_item_show_in_nearby -> {
+ mapRootFragment!!.loadNearbyMapFromExplore()
+ return true
+ }
+
+ else -> return super.onOptionsItemSelected(item)
+ }
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ binding = null
+ }
+
+ companion object {
+ private const val FEATURED_IMAGES_CATEGORY = "Featured_pictures_on_Wikimedia_Commons"
+ private const val MOBILE_UPLOADS_CATEGORY = "Uploaded_with_Mobile/Android"
+ private const val EXPLORE_MAP = "Map"
+
+ fun newInstance(): ExploreFragment = ExploreFragment().apply {
+ retainInstance = true
+ }
+ }
+}
\ No newline at end of file
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
deleted file mode 100644
index e3ad90119..000000000
--- a/app/src/main/java/fr/free/nrw/commons/explore/ExploreListRootFragment.java
+++ /dev/null
@@ -1,215 +0,0 @@
-package fr.free.nrw.commons.explore;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.fragment.app.Fragment;
-import fr.free.nrw.commons.Media;
-import fr.free.nrw.commons.R;
-import fr.free.nrw.commons.category.CategoryImagesCallback;
-import fr.free.nrw.commons.contributions.MainActivity;
-import fr.free.nrw.commons.databinding.FragmentFeaturedRootBinding;
-import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
-import fr.free.nrw.commons.explore.categories.media.CategoriesMediaFragment;
-import fr.free.nrw.commons.media.MediaDetailPagerFragment;
-import fr.free.nrw.commons.media.MediaDetailProvider;
-import fr.free.nrw.commons.navtab.NavTab;
-
-public class ExploreListRootFragment extends CommonsDaggerSupportFragment implements
- MediaDetailProvider, CategoryImagesCallback {
-
- private MediaDetailPagerFragment mediaDetails;
- private CategoriesMediaFragment listFragment;
-
- private FragmentFeaturedRootBinding binding;
-
- public ExploreListRootFragment() {
- //empty constructor necessary otherwise crashes on recreate
- }
-
- public ExploreListRootFragment(Bundle bundle) {
- String title = bundle.getString("categoryName");
- listFragment = new CategoriesMediaFragment();
- Bundle featuredArguments = new Bundle();
- featuredArguments.putString("categoryName", title);
- listFragment.setArguments(featuredArguments);
- }
-
- @Nullable
- @Override
- public View onCreateView(@NonNull final LayoutInflater inflater,
- @Nullable final ViewGroup container,
- @Nullable final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- binding = FragmentFeaturedRootBinding.inflate(inflater, container, false);
- return binding.getRoot();
- }
-
- @Override
- public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
- if (savedInstanceState == null) {
- setFragment(listFragment, mediaDetails);
- }
- }
-
- public void setFragment(Fragment fragment, Fragment otherFragment) {
- if (fragment.isAdded() && otherFragment != null) {
- getChildFragmentManager()
- .beginTransaction()
- .hide(otherFragment)
- .show(fragment)
- .addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
- .commit();
- getChildFragmentManager().executePendingTransactions();
- } else if (fragment.isAdded() && otherFragment == null) {
- getChildFragmentManager()
- .beginTransaction()
- .show(fragment)
- .addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
- .commit();
- getChildFragmentManager().executePendingTransactions();
- } else if (!fragment.isAdded() && otherFragment != null) {
- getChildFragmentManager()
- .beginTransaction()
- .hide(otherFragment)
- .add(R.id.explore_container, fragment)
- .addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
- .commit();
- getChildFragmentManager().executePendingTransactions();
- } else if (!fragment.isAdded()) {
- getChildFragmentManager()
- .beginTransaction()
- .replace(R.id.explore_container, fragment)
- .addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
- .commit();
- getChildFragmentManager().executePendingTransactions();
- }
- }
-
- public void removeFragment(Fragment fragment) {
- getChildFragmentManager()
- .beginTransaction()
- .remove(fragment)
- .commit();
- getChildFragmentManager().executePendingTransactions();
- }
-
- @Override
- public void onAttach(final Context context) {
- super.onAttach(context);
- }
-
- @Override
- public void onMediaClicked(int position) {
- if (binding!=null) {
- binding.exploreContainer.setVisibility(View.VISIBLE);
- }
- if (((ExploreFragment) getParentFragment()).binding!=null) {
- ((ExploreFragment) getParentFragment()).binding.tabLayout.setVisibility(View.GONE);
- }
- mediaDetails = MediaDetailPagerFragment.newInstance(false, true);
- ((ExploreFragment) getParentFragment()).setScroll(false);
- setFragment(mediaDetails, listFragment);
- mediaDetails.showImage(position);
- }
-
- /**
- * This method is called mediaDetailPagerFragment. It returns the Media Object at that Index
- *
- * @param i It is the index of which media object is to be returned which is same as current
- * index of viewPager.
- * @return Media Object
- */
- @Override
- public Media getMediaAtPosition(int i) {
- if (listFragment != null) {
- return listFragment.getMediaAtPosition(i);
- } else {
- return null;
- }
- }
-
- /**
- * This method is called on from getCount of MediaDetailPagerFragment The viewpager will contain
- * same number of media items as that of media elements in adapter.
- *
- * @return Total Media count in the adapter
- */
- @Override
- public int getTotalMediaCount() {
- if (listFragment != null) {
- return listFragment.getTotalMediaCount();
- } else {
- return 0;
- }
- }
-
- @Override
- public Integer getContributionStateAt(int position) {
- return null;
- }
-
- /**
- * Reload media detail fragment once media is nominated
- *
- * @param index item position that has been nominated
- */
- @Override
- public void refreshNominatedMedia(int index) {
- if (mediaDetails != null && !listFragment.isVisible()) {
- removeFragment(mediaDetails);
- onMediaClicked(index);
- }
- }
-
- /**
- * This method is called on success of API call for featured images or mobile uploads. The
- * viewpager will notified that number of items have changed.
- */
- @Override
- public void viewPagerNotifyDataSetChanged() {
- if (mediaDetails != null) {
- mediaDetails.notifyDataSetChanged();
- }
- }
-
- /**
- * Performs back pressed action on the fragment. Return true if the event was handled by the
- * mediaDetails otherwise returns false.
- *
- * @return
- */
- public boolean backPressed() {
- if (null != mediaDetails && mediaDetails.isVisible()) {
- if (((ExploreFragment) getParentFragment()).binding != null) {
- ((ExploreFragment) getParentFragment()).binding.tabLayout.setVisibility(View.VISIBLE);
- }
- removeFragment(mediaDetails);
- ((ExploreFragment) getParentFragment()).setScroll(true);
- setFragment(listFragment, mediaDetails);
- ((MainActivity) getActivity()).showTabs();
- return true;
- } else {
- if (((MainActivity) getActivity()) != null) {
- ((MainActivity) getActivity()).setSelectedItemId(NavTab.CONTRIBUTIONS.code());
- }
- }
- if (((MainActivity) getActivity()) != null) {
- ((MainActivity) getActivity()).showTabs();
- }
- return false;
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
-
- binding = null;
- }
-}
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/ExploreListRootFragment.kt b/app/src/main/java/fr/free/nrw/commons/explore/ExploreListRootFragment.kt
new file mode 100644
index 000000000..32acebbb1
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/explore/ExploreListRootFragment.kt
@@ -0,0 +1,182 @@
+package fr.free.nrw.commons.explore
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.os.bundleOf
+import androidx.fragment.app.Fragment
+import fr.free.nrw.commons.Media
+import fr.free.nrw.commons.R
+import fr.free.nrw.commons.category.CategoryImagesCallback
+import fr.free.nrw.commons.contributions.MainActivity
+import fr.free.nrw.commons.databinding.FragmentFeaturedRootBinding
+import fr.free.nrw.commons.di.CommonsDaggerSupportFragment
+import fr.free.nrw.commons.explore.categories.media.CategoriesMediaFragment
+import fr.free.nrw.commons.media.MediaDetailPagerFragment
+import fr.free.nrw.commons.media.MediaDetailProvider
+import fr.free.nrw.commons.navtab.NavTab
+
+class ExploreListRootFragment : CommonsDaggerSupportFragment, MediaDetailProvider,
+ CategoryImagesCallback {
+ private var mediaDetails: MediaDetailPagerFragment? = null
+ private var listFragment: CategoriesMediaFragment? = null
+ private var binding: FragmentFeaturedRootBinding? = null
+
+ constructor()
+
+ constructor(bundle: Bundle) {
+ listFragment = CategoriesMediaFragment().apply {
+ arguments = bundleOf(
+ "categoryName" to bundle.getString("categoryName")
+ )
+ }
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ super.onCreate(savedInstanceState)
+
+ binding = FragmentFeaturedRootBinding.inflate(inflater, container, false)
+ return binding!!.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ if (savedInstanceState == null) {
+ setFragment(listFragment!!, mediaDetails)
+ }
+ }
+
+ fun setFragment(fragment: Fragment, otherFragment: Fragment?) {
+ if (fragment.isAdded && otherFragment != null) {
+ childFragmentManager
+ .beginTransaction()
+ .hide(otherFragment)
+ .show(fragment)
+ .addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
+ .commit()
+ childFragmentManager.executePendingTransactions()
+ } else if (fragment.isAdded && otherFragment == null) {
+ childFragmentManager
+ .beginTransaction()
+ .show(fragment)
+ .addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
+ .commit()
+ childFragmentManager.executePendingTransactions()
+ } else if (!fragment.isAdded && otherFragment != null) {
+ childFragmentManager
+ .beginTransaction()
+ .hide(otherFragment)
+ .add(R.id.explore_container, fragment)
+ .addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
+ .commit()
+ childFragmentManager.executePendingTransactions()
+ } else if (!fragment.isAdded) {
+ childFragmentManager
+ .beginTransaction()
+ .replace(R.id.explore_container, fragment)
+ .addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
+ .commit()
+ childFragmentManager.executePendingTransactions()
+ }
+ }
+
+ private fun removeFragment(fragment: Fragment) {
+ childFragmentManager
+ .beginTransaction()
+ .remove(fragment)
+ .commit()
+ childFragmentManager.executePendingTransactions()
+ }
+
+ override fun onMediaClicked(position: Int) {
+ if (binding != null) {
+ binding!!.exploreContainer.visibility = View.VISIBLE
+ }
+ if ((parentFragment as ExploreFragment).binding != null) {
+ (parentFragment as ExploreFragment).binding!!.tabLayout.visibility =
+ View.GONE
+ }
+ mediaDetails = MediaDetailPagerFragment.newInstance(false, true)
+ (parentFragment as ExploreFragment).setScroll(false)
+ setFragment(mediaDetails!!, listFragment)
+ mediaDetails!!.showImage(position)
+ }
+
+ /**
+ * This method is called mediaDetailPagerFragment. It returns the Media Object at that Index
+ *
+ * @param i It is the index of which media object is to be returned which is same as current
+ * index of viewPager.
+ * @return Media Object
+ */
+ override fun getMediaAtPosition(i: Int): Media? = listFragment?.getMediaAtPosition(i)
+
+ /**
+ * This method is called on from getCount of MediaDetailPagerFragment The viewpager will contain
+ * same number of media items as that of media elements in adapter.
+ *
+ * @return Total Media count in the adapter
+ */
+ override fun getTotalMediaCount(): Int = listFragment?.getTotalMediaCount() ?: 0
+
+ override fun getContributionStateAt(position: Int): Int? = null
+
+ /**
+ * Reload media detail fragment once media is nominated
+ *
+ * @param index item position that has been nominated
+ */
+ override fun refreshNominatedMedia(index: Int) {
+ if (mediaDetails != null && !listFragment!!.isVisible) {
+ removeFragment(mediaDetails!!)
+ onMediaClicked(index)
+ }
+ }
+
+ /**
+ * This method is called on success of API call for featured images or mobile uploads. The
+ * viewpager will notified that number of items have changed.
+ */
+ override fun viewPagerNotifyDataSetChanged() {
+ mediaDetails?.notifyDataSetChanged()
+ }
+
+ /**
+ * Performs back pressed action on the fragment. Return true if the event was handled by the
+ * mediaDetails otherwise returns false.
+ *
+ * @return
+ */
+ fun backPressed(): Boolean {
+ if (null != mediaDetails && mediaDetails!!.isVisible) {
+ if ((parentFragment as ExploreFragment).binding != null) {
+ (parentFragment as ExploreFragment).binding!!.tabLayout.visibility =
+ View.VISIBLE
+ }
+ removeFragment(mediaDetails!!)
+ (parentFragment as ExploreFragment).setScroll(true)
+ setFragment(listFragment!!, mediaDetails)
+ (activity as MainActivity).showTabs()
+ return true
+ } else {
+ if ((activity as MainActivity?) != null) {
+ (activity as MainActivity).setSelectedItemId(NavTab.CONTRIBUTIONS.code())
+ }
+ }
+ if ((activity as MainActivity?) != null) {
+ (activity as MainActivity).showTabs()
+ }
+ return false
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+
+ binding = null
+ }
+}
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
deleted file mode 100644
index 31a8e11ba..000000000
--- a/app/src/main/java/fr/free/nrw/commons/explore/ExploreMapRootFragment.java
+++ /dev/null
@@ -1,239 +0,0 @@
-package fr.free.nrw.commons.explore;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.fragment.app.Fragment;
-import fr.free.nrw.commons.Media;
-import fr.free.nrw.commons.R;
-import fr.free.nrw.commons.category.CategoryImagesCallback;
-import fr.free.nrw.commons.contributions.MainActivity;
-import fr.free.nrw.commons.databinding.FragmentFeaturedRootBinding;
-import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
-import fr.free.nrw.commons.explore.map.ExploreMapFragment;
-import fr.free.nrw.commons.media.MediaDetailPagerFragment;
-import fr.free.nrw.commons.media.MediaDetailProvider;
-import fr.free.nrw.commons.navtab.NavTab;
-
-public class ExploreMapRootFragment extends CommonsDaggerSupportFragment implements
- MediaDetailProvider, CategoryImagesCallback {
-
- private MediaDetailPagerFragment mediaDetails;
- private ExploreMapFragment mapFragment;
-
- private FragmentFeaturedRootBinding binding;
-
- public ExploreMapRootFragment() {
- //empty constructor necessary otherwise crashes on recreate
- }
-
- @NonNull
- public static ExploreMapRootFragment newInstance() {
- ExploreMapRootFragment fragment = new ExploreMapRootFragment();
- fragment.setRetainInstance(true);
- return fragment;
- }
-
- public ExploreMapRootFragment(Bundle bundle) {
- // get fragment arguments
- String title = bundle.getString("categoryName");
- double zoom = bundle.getDouble("prev_zoom");
- double latitude = bundle.getDouble("prev_latitude");
- double longitude = bundle.getDouble("prev_longitude");
-
- mapFragment = new ExploreMapFragment();
- Bundle featuredArguments = new Bundle();
- featuredArguments.putString("categoryName", title);
-
- // if we came from 'Show in Explore' in Nearby, pass on zoom and center
- if (zoom != 0.0 || latitude != 0.0 || longitude != 0.0) {
- featuredArguments.putDouble("prev_zoom", zoom);
- featuredArguments.putDouble("prev_latitude", latitude);
- featuredArguments.putDouble("prev_longitude", longitude);
- }
- mapFragment.setArguments(featuredArguments);
- }
-
- @Nullable
- @Override
- public View onCreateView(@NonNull final LayoutInflater inflater,
- @Nullable final ViewGroup container,
- @Nullable final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- binding = FragmentFeaturedRootBinding.inflate(inflater, container, false);
-
- return binding.getRoot();
- }
-
- @Override
- public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
- if (savedInstanceState == null) {
- setFragment(mapFragment, mediaDetails);
- }
- }
-
- public void setFragment(Fragment fragment, Fragment otherFragment) {
- if (fragment.isAdded() && otherFragment != null) {
- getChildFragmentManager()
- .beginTransaction()
- .hide(otherFragment)
- .show(fragment)
- .addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
- .commit();
- getChildFragmentManager().executePendingTransactions();
- } else if (fragment.isAdded() && otherFragment == null) {
- getChildFragmentManager()
- .beginTransaction()
- .show(fragment)
- .addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
- .commit();
- getChildFragmentManager().executePendingTransactions();
- } else if (!fragment.isAdded() && otherFragment != null) {
- getChildFragmentManager()
- .beginTransaction()
- .hide(otherFragment)
- .add(R.id.explore_container, fragment)
- .addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
- .commit();
- getChildFragmentManager().executePendingTransactions();
- } else if (!fragment.isAdded()) {
- getChildFragmentManager()
- .beginTransaction()
- .replace(R.id.explore_container, fragment)
- .addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
- .commit();
- getChildFragmentManager().executePendingTransactions();
- }
- }
-
- public void removeFragment(Fragment fragment) {
- getChildFragmentManager()
- .beginTransaction()
- .remove(fragment)
- .commit();
- getChildFragmentManager().executePendingTransactions();
- }
-
- @Override
- public void onAttach(final Context context) {
- super.onAttach(context);
- }
-
- @Override
- public void onMediaClicked(int position) {
- binding.exploreContainer.setVisibility(View.VISIBLE);
- ((ExploreFragment) getParentFragment()).binding.tabLayout.setVisibility(View.GONE);
- mediaDetails = MediaDetailPagerFragment.newInstance(false, true);
- ((ExploreFragment) getParentFragment()).setScroll(false);
- setFragment(mediaDetails, mapFragment);
- mediaDetails.showImage(position);
- }
-
- /**
- * This method is called mediaDetailPagerFragment. It returns the Media Object at that Index
- *
- * @param i It is the index of which media object is to be returned which is same as current
- * index of viewPager.
- * @return Media Object
- */
- @Override
- public Media getMediaAtPosition(int i) {
- if (mapFragment != null && mapFragment.mediaList != null) {
- return mapFragment.mediaList.get(i);
- } else {
- return null;
- }
- }
-
- /**
- * This method is called on from getCount of MediaDetailPagerFragment The viewpager will contain
- * same number of media items as that of media elements in adapter.
- *
- * @return Total Media count in the adapter
- */
- @Override
- public int getTotalMediaCount() {
- if (mapFragment != null && mapFragment.mediaList != null) {
- return mapFragment.mediaList.size();
- } else {
- return 0;
- }
- }
-
- @Override
- public Integer getContributionStateAt(int position) {
- return null;
- }
-
- /**
- * Reload media detail fragment once media is nominated
- *
- * @param index item position that has been nominated
- */
- @Override
- public void refreshNominatedMedia(int index) {
- if (mediaDetails != null && !mapFragment.isVisible()) {
- removeFragment(mediaDetails);
- onMediaClicked(index);
- }
- }
-
- /**
- * This method is called on success of API call for featured images or mobile uploads. The
- * viewpager will notified that number of items have changed.
- */
- @Override
- public void viewPagerNotifyDataSetChanged() {
- if (mediaDetails != null) {
- mediaDetails.notifyDataSetChanged();
- }
- }
-
- /**
- * Performs back pressed action on the fragment. Return true if the event was handled by the
- * mediaDetails otherwise returns false.
- *
- * @return
- */
- public boolean backPressed() {
- if (null != mediaDetails && mediaDetails.isVisible()) {
- ((ExploreFragment) getParentFragment()).binding.tabLayout.setVisibility(View.VISIBLE);
- removeFragment(mediaDetails);
- ((ExploreFragment) getParentFragment()).setScroll(true);
- setFragment(mapFragment, mediaDetails);
- ((MainActivity) getActivity()).showTabs();
- return true;
-
- }
- if (mapFragment != null && mapFragment.isVisible()) {
- if (mapFragment.backButtonClicked()) {
- // Explore map fragment handled the event no further action required.
- return true;
- } else {
- ((MainActivity) getActivity()).showTabs();
- return false;
- }
- } else {
- ((MainActivity) getActivity()).setSelectedItemId(NavTab.CONTRIBUTIONS.code());
- }
- ((MainActivity) getActivity()).showTabs();
- return false;
- }
-
- public void loadNearbyMapFromExplore() {
- mapFragment.loadNearbyMapFromExplore();
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
-
- binding = null;
- }
-}
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/ExploreMapRootFragment.kt b/app/src/main/java/fr/free/nrw/commons/explore/ExploreMapRootFragment.kt
new file mode 100644
index 000000000..d405709a8
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/explore/ExploreMapRootFragment.kt
@@ -0,0 +1,212 @@
+package fr.free.nrw.commons.explore
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.os.bundleOf
+import androidx.fragment.app.Fragment
+import fr.free.nrw.commons.Media
+import fr.free.nrw.commons.R
+import fr.free.nrw.commons.category.CategoryImagesCallback
+import fr.free.nrw.commons.contributions.MainActivity
+import fr.free.nrw.commons.databinding.FragmentFeaturedRootBinding
+import fr.free.nrw.commons.di.CommonsDaggerSupportFragment
+import fr.free.nrw.commons.explore.map.ExploreMapFragment
+import fr.free.nrw.commons.media.MediaDetailPagerFragment
+import fr.free.nrw.commons.media.MediaDetailProvider
+import fr.free.nrw.commons.navtab.NavTab
+
+class ExploreMapRootFragment : CommonsDaggerSupportFragment, MediaDetailProvider,
+ CategoryImagesCallback {
+ private var mediaDetails: MediaDetailPagerFragment? = null
+ private var mapFragment: ExploreMapFragment? = null
+ private var binding: FragmentFeaturedRootBinding? = null
+
+ constructor()
+
+ constructor(bundle: Bundle) {
+ // get fragment arguments
+ val title = bundle.getString("categoryName")
+ val zoom = bundle.getDouble("prev_zoom")
+ val latitude = bundle.getDouble("prev_latitude")
+ val longitude = bundle.getDouble("prev_longitude")
+
+ mapFragment = ExploreMapFragment()
+ val featuredArguments = bundleOf(
+ "categoryName" to title
+ )
+
+ // if we came from 'Show in Explore' in Nearby, pass on zoom and center
+ if (zoom != 0.0 || latitude != 0.0 || longitude != 0.0) {
+ featuredArguments.putDouble("prev_zoom", zoom)
+ featuredArguments.putDouble("prev_latitude", latitude)
+ featuredArguments.putDouble("prev_longitude", longitude)
+ }
+ mapFragment!!.arguments = featuredArguments
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ super.onCreate(savedInstanceState)
+
+ binding = FragmentFeaturedRootBinding.inflate(inflater, container, false)
+
+ return binding!!.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ if (savedInstanceState == null) {
+ setFragment(mapFragment!!, mediaDetails)
+ }
+ }
+
+ fun setFragment(fragment: Fragment, otherFragment: Fragment?) {
+ if (fragment.isAdded && otherFragment != null) {
+ childFragmentManager
+ .beginTransaction()
+ .hide(otherFragment)
+ .show(fragment)
+ .addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
+ .commit()
+ childFragmentManager.executePendingTransactions()
+ } else if (fragment.isAdded && otherFragment == null) {
+ childFragmentManager
+ .beginTransaction()
+ .show(fragment)
+ .addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
+ .commit()
+ childFragmentManager.executePendingTransactions()
+ } else if (!fragment.isAdded && otherFragment != null) {
+ childFragmentManager
+ .beginTransaction()
+ .hide(otherFragment)
+ .add(R.id.explore_container, fragment)
+ .addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
+ .commit()
+ childFragmentManager.executePendingTransactions()
+ } else if (!fragment.isAdded) {
+ childFragmentManager
+ .beginTransaction()
+ .replace(R.id.explore_container, fragment)
+ .addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
+ .commit()
+ childFragmentManager.executePendingTransactions()
+ }
+ }
+
+ private fun removeFragment(fragment: Fragment) {
+ childFragmentManager
+ .beginTransaction()
+ .remove(fragment)
+ .commit()
+ childFragmentManager.executePendingTransactions()
+ }
+
+ override fun onMediaClicked(position: Int) {
+ binding!!.exploreContainer.visibility = View.VISIBLE
+ (parentFragment as ExploreFragment).binding!!.tabLayout.visibility = View.GONE
+ mediaDetails = MediaDetailPagerFragment.newInstance(false, true)
+ (parentFragment as ExploreFragment).setScroll(false)
+ setFragment(mediaDetails!!, mapFragment)
+ mediaDetails!!.showImage(position)
+ }
+
+ /**
+ * This method is called mediaDetailPagerFragment. It returns the Media Object at that Index
+ *
+ * @param i It is the index of which media object is to be returned which is same as current
+ * index of viewPager.
+ * @return Media Object
+ */
+ override fun getMediaAtPosition(i: Int): Media? = mapFragment?.mediaList?.get(i)
+
+ /**
+ * This method is called on from getCount of MediaDetailPagerFragment The viewpager will contain
+ * same number of media items as that of media elements in adapter.
+ *
+ * @return Total Media count in the adapter
+ */
+ override fun getTotalMediaCount(): Int = mapFragment?.mediaList?.size ?: 0
+
+ override fun getContributionStateAt(position: Int): Int? = null
+
+ /**
+ * Reload media detail fragment once media is nominated
+ *
+ * @param index item position that has been nominated
+ */
+ override fun refreshNominatedMedia(index: Int) {
+ if (mediaDetails != null && !mapFragment!!.isVisible) {
+ removeFragment(mediaDetails!!)
+ onMediaClicked(index)
+ }
+ }
+
+ /**
+ * This method is called on success of API call for featured images or mobile uploads. The
+ * viewpager will notified that number of items have changed.
+ */
+ override fun viewPagerNotifyDataSetChanged() {
+ mediaDetails?.notifyDataSetChanged()
+ }
+
+ /**
+ * Performs back pressed action on the fragment. Return true if the event was handled by the
+ * mediaDetails otherwise returns false.
+ *
+ * @return
+ */
+ fun backPressed(): Boolean {
+ if (null != mediaDetails && mediaDetails!!.isVisible) {
+ (parentFragment as ExploreFragment).binding!!.tabLayout.visibility = View.VISIBLE
+ removeFragment(mediaDetails!!)
+ (parentFragment as ExploreFragment).setScroll(true)
+ setFragment(mapFragment!!, mediaDetails)
+ (activity as MainActivity).showTabs()
+ return true
+ }
+ if (mapFragment != null && mapFragment!!.isVisible) {
+ if (mapFragment!!.backButtonClicked()) {
+ // Explore map fragment handled the event no further action required.
+ return true
+ } else {
+ (activity as MainActivity).showTabs()
+ return false
+ }
+ } else {
+ (activity as MainActivity).setSelectedItemId(NavTab.CONTRIBUTIONS.code())
+ }
+ (activity as MainActivity).showTabs()
+ return false
+ }
+
+ fun loadNearbyMapFromExplore() = mapFragment?.loadNearbyMapFromExplore()
+
+ override fun onDestroy() {
+ super.onDestroy()
+
+ binding = null
+ }
+
+ fun requestLocationIfNeeded() {
+ mapFragment?.requestLocationIfNeeded()
+ }
+
+ override fun setUserVisibleHint(isVisibleToUser: Boolean) {
+ super.setUserVisibleHint(isVisibleToUser)
+ if (isVisibleToUser) {
+ requestLocationIfNeeded()
+ }
+ }
+
+ companion object {
+ fun newInstance(): ExploreMapRootFragment = ExploreMapRootFragment().apply {
+ retainInstance = true
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/ParentViewPager.java b/app/src/main/java/fr/free/nrw/commons/explore/ParentViewPager.java
deleted file mode 100644
index 4112cda95..000000000
--- a/app/src/main/java/fr/free/nrw/commons/explore/ParentViewPager.java
+++ /dev/null
@@ -1,66 +0,0 @@
-package fr.free.nrw.commons.explore;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import androidx.viewpager.widget.ViewPager;
-
-/**
- * ParentViewPager A custom viewPager whose scrolling can be enabled and disabled.
- */
-public class ParentViewPager extends ViewPager {
-
- /**
- * Boolean variable that stores the current state of pager scroll i.e(enabled or disabled)
- */
- private boolean canScroll = true;
-
-
- /**
- * Default constructors
- */
- public ParentViewPager(Context context) {
- super(context);
- }
-
- public ParentViewPager(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
-
- /**
- * Setter method for canScroll.
- */
- public void setCanScroll(boolean canScroll) {
- this.canScroll = canScroll;
- }
-
-
- /**
- * Getter method for canScroll.
- */
- public boolean isCanScroll() {
- return canScroll;
- }
-
-
- /**
- * Method that prevents scrolling if canScroll is set to false.
- */
- @Override
- public boolean onTouchEvent(MotionEvent ev) {
- return canScroll && super.onTouchEvent(ev);
- }
-
-
- /**
- * A facilitator method that allows parent to intercept touch events before its children. thus
- * making it possible to prevent swiping parent on child end.
- */
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- return canScroll && super.onInterceptTouchEvent(ev);
- }
-
-
-}
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/ParentViewPager.kt b/app/src/main/java/fr/free/nrw/commons/explore/ParentViewPager.kt
new file mode 100644
index 000000000..36e85e70a
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/explore/ParentViewPager.kt
@@ -0,0 +1,25 @@
+package fr.free.nrw.commons.explore
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.MotionEvent
+import androidx.viewpager.widget.ViewPager
+
+/**
+ * ParentViewPager A custom viewPager whose scrolling can be enabled and disabled.
+ */
+class ParentViewPager : ViewPager {
+ var canScroll: Boolean = true
+
+ constructor(context: Context) : super(context)
+
+ constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
+
+ override fun onTouchEvent(ev: MotionEvent): Boolean {
+ return canScroll && super.onTouchEvent(ev)
+ }
+
+ override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
+ return canScroll && super.onInterceptTouchEvent(ev)
+ }
+}
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
deleted file mode 100644
index b27ffc338..000000000
--- a/app/src/main/java/fr/free/nrw/commons/explore/SearchActivity.java
+++ /dev/null
@@ -1,285 +0,0 @@
-package fr.free.nrw.commons.explore;
-
-import static fr.free.nrw.commons.ViewPagerAdapter.pairOf;
-
-import android.os.Bundle;
-import android.text.TextUtils;
-import android.view.View;
-import androidx.annotation.NonNull;
-import androidx.fragment.app.FragmentManager;
-import androidx.fragment.app.FragmentTransaction;
-import com.jakewharton.rxbinding2.view.RxView;
-import com.jakewharton.rxbinding2.widget.RxSearchView;
-import fr.free.nrw.commons.Media;
-import fr.free.nrw.commons.R;
-import fr.free.nrw.commons.ViewPagerAdapter;
-import fr.free.nrw.commons.category.CategoryImagesCallback;
-import fr.free.nrw.commons.databinding.ActivitySearchBinding;
-import fr.free.nrw.commons.explore.categories.search.SearchCategoryFragment;
-import fr.free.nrw.commons.explore.depictions.search.SearchDepictionsFragment;
-import fr.free.nrw.commons.explore.media.SearchMediaFragment;
-import fr.free.nrw.commons.explore.models.RecentSearch;
-import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao;
-import fr.free.nrw.commons.explore.recentsearches.RecentSearchesFragment;
-import fr.free.nrw.commons.media.MediaDetailPagerFragment;
-import fr.free.nrw.commons.media.MediaDetailProvider;
-import fr.free.nrw.commons.theme.BaseActivity;
-import fr.free.nrw.commons.utils.FragmentUtils;
-import fr.free.nrw.commons.utils.ViewUtil;
-import io.reactivex.android.schedulers.AndroidSchedulers;
-import java.util.Date;
-import java.util.concurrent.TimeUnit;
-import javax.inject.Inject;
-import timber.log.Timber;
-
-/**
- * Represents search screen of this app
- */
-
-public class SearchActivity extends BaseActivity
- implements MediaDetailProvider, CategoryImagesCallback {
-
- @Inject
- RecentSearchesDao recentSearchesDao;
-
- private SearchMediaFragment searchMediaFragment;
- private SearchCategoryFragment searchCategoryFragment;
- private SearchDepictionsFragment searchDepictionsFragment;
- private RecentSearchesFragment recentSearchesFragment;
- private FragmentManager supportFragmentManager;
- private MediaDetailPagerFragment mediaDetails;
- ViewPagerAdapter viewPagerAdapter;
-
- private ActivitySearchBinding binding;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- binding = ActivitySearchBinding.inflate(getLayoutInflater());
- setContentView(binding.getRoot());
-
- setTitle(getString(R.string.title_activity_search));
- setSupportActionBar(binding.toolbarSearch);
- getSupportActionBar().setDisplayHomeAsUpEnabled(true);
- binding.toolbarSearch.setNavigationOnClickListener(v->onBackPressed());
- supportFragmentManager = getSupportFragmentManager();
- setSearchHistoryFragment();
- viewPagerAdapter = new ViewPagerAdapter(this, getSupportFragmentManager());
- binding.viewPager.setAdapter(viewPagerAdapter);
- binding.viewPager.setOffscreenPageLimit(2); // Because we want all the fragments to be alive
- binding.tabLayout.setupWithViewPager(binding.viewPager);
- setTabs();
- binding.searchBox.setQueryHint(getString(R.string.search_commons));
- binding.searchBox.onActionViewExpanded();
- binding.searchBox.clearFocus();
-
- }
-
- /**
- * This method sets the search history fragment.
- * Search history fragment is displayed when query is empty.
- */
- private void setSearchHistoryFragment() {
- recentSearchesFragment = new RecentSearchesFragment();
- FragmentTransaction transaction = supportFragmentManager.beginTransaction();
- transaction.add(R.id.searchHistoryContainer, recentSearchesFragment).commit();
- }
-
- /**
- * Sets the titles in the tabLayout and fragments in the viewPager
- */
- public void setTabs() {
- searchMediaFragment = new SearchMediaFragment();
- searchDepictionsFragment = new SearchDepictionsFragment();
- searchCategoryFragment= new SearchCategoryFragment();
-
- viewPagerAdapter.setTabs(
- pairOf(R.string.search_tab_title_media, searchMediaFragment),
- pairOf(R.string.search_tab_title_categories, searchCategoryFragment),
- pairOf(R.string.search_tab_title_depictions, searchDepictionsFragment)
- );
- viewPagerAdapter.notifyDataSetChanged();
- getCompositeDisposable().add(RxSearchView.queryTextChanges(binding.searchBox)
- .takeUntil(RxView.detaches(binding.searchBox))
- .debounce(500, TimeUnit.MILLISECONDS)
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(this::handleSearch, Timber::e
- ));
- }
-
- private void handleSearch(final CharSequence query) {
- if (!TextUtils.isEmpty(query)) {
- saveRecentSearch(query.toString());
- binding.viewPager.setVisibility(View.VISIBLE);
- binding.tabLayout.setVisibility(View.VISIBLE);
- binding.searchHistoryContainer.setVisibility(View.GONE);
-
- if (FragmentUtils.isFragmentUIActive(searchDepictionsFragment)) {
- searchDepictionsFragment.onQueryUpdated(query.toString());
- }
-
- if (FragmentUtils.isFragmentUIActive(searchMediaFragment)) {
- searchMediaFragment.onQueryUpdated(query.toString());
- }
-
- if (FragmentUtils.isFragmentUIActive(searchCategoryFragment)) {
- searchCategoryFragment.onQueryUpdated(query.toString());
- }
-
- }
- else {
- //Open RecentSearchesFragment
- recentSearchesFragment.updateRecentSearches();
- binding.viewPager.setVisibility(View.GONE);
- binding.tabLayout.setVisibility(View.GONE);
- setSearchHistoryFragment();
- binding.searchHistoryContainer.setVisibility(View.VISIBLE);
- }
- }
-
- private void saveRecentSearch(@NonNull final String query) {
- final RecentSearch recentSearch = recentSearchesDao.find(query);
- // Newly searched query...
- if (recentSearch == null) {
- recentSearchesDao.save(new RecentSearch(null, query, new Date()));
- } else {
- recentSearch.setLastSearched(new Date());
- recentSearchesDao.save(recentSearch);
- }
- }
-
- /**
- * returns Media Object at position
- * @param i position of Media in the imagesRecyclerView adapter.
- */
- @Override
- public Media getMediaAtPosition(int i) {
- return searchMediaFragment.getMediaAtPosition(i);
- }
-
- /**
- * returns total number of images present in the imagesRecyclerView adapter.
- */
- @Override
- public int getTotalMediaCount() {
- return searchMediaFragment.getTotalMediaCount();
- }
-
- @Override
- public Integer getContributionStateAt(int position) {
- return null;
- }
-
- /**
- * Reload media detail fragment once media is nominated
- *
- * @param index item position that has been nominated
- */
- @Override
- public void refreshNominatedMedia(int index) {
- if (getSupportFragmentManager().getBackStackEntryCount() == 1) {
- onBackPressed();
- onMediaClicked(index);
- }
- }
-
- /**
- * This method is called on success of API call for image Search.
- * The viewpager will notified that number of items have changed.
- */
- @Override
- public void viewPagerNotifyDataSetChanged() {
- if (mediaDetails!=null){
- mediaDetails.notifyDataSetChanged();
- }
- }
-
- /**
- * Open media detail pager fragment on click of image in search results
- * @param index item index that should be opened
- */
- @Override
- public void onMediaClicked(int index) {
- ViewUtil.hideKeyboard(this.findViewById(R.id.searchBox));
- binding.tabLayout.setVisibility(View.GONE);
- binding.viewPager.setVisibility(View.GONE);
- binding.mediaContainer.setVisibility(View.VISIBLE);
- binding.searchBox.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 = MediaDetailPagerFragment.newInstance(false, true);
- supportFragmentManager
- .beginTransaction()
- .hide(supportFragmentManager.getFragments().get(supportFragmentManager.getBackStackEntryCount()))
- .add(R.id.mediaContainer, mediaDetails)
- .addToBackStack(null)
- .commit();
- // Reason for using hide, add instead of replace is to maintain scroll position after
- // coming back to the search activity. See https://github.com/commons-app/apps-android-commons/issues/1631
- // https://stackoverflow.com/questions/11353075/how-can-i-maintain-fragment-state-when-added-to-the-back-stack/19022550#19022550
- supportFragmentManager.executePendingTransactions();
- }
- mediaDetails.showImage(index);
- }
-
- /**
- * This method is called on Screen Rotation
- */
- @Override
- protected void onResume() {
- if (supportFragmentManager.getBackStackEntryCount()==1){
- //FIXME: Temporary fix for screen rotation inside media details. If we don't call onBackPressed then fragment stack is increasing every time.
- //FIXME: Similar issue like this https://github.com/commons-app/apps-android-commons/issues/894
- // This is called on screen rotation when user is inside media details. Ideally it should show Media Details but since we are not saving the state now. We are throwing the user to search screen otherwise the app was crashing.
- //
- onBackPressed();
- }
- super.onResume();
- }
-
- /**
- * This method is called on backPressed of anyFragment in the activity.
- * If condition is called when mediaDetailFragment is opened.
- */
- @Override
- public void onBackPressed() {
- //Remove the backstack entry that gets added when share button is clicked
- //fixing:https://github.com/commons-app/apps-android-commons/issues/2296
- if (getSupportFragmentManager().getBackStackEntryCount() == 2) {
- supportFragmentManager
- .beginTransaction()
- .remove(mediaDetails)
- .commit();
- supportFragmentManager.popBackStack();
- supportFragmentManager.executePendingTransactions();
- }
- if (getSupportFragmentManager().getBackStackEntryCount() == 1) {
- // back to search so show search toolbar and hide navigation toolbar
- binding.searchBox.setVisibility(View.VISIBLE);//set the searchview
- binding.tabLayout.setVisibility(View.VISIBLE);
- binding.viewPager.setVisibility(View.VISIBLE);
- binding.mediaContainer.setVisibility(View.GONE);
- } else {
- binding.toolbarSearch.setVisibility(View.GONE);
- }
- super.onBackPressed();
- }
-
- /**
- * This method is called on click of a recent search to update query in SearchView.
- * @param query Recent Search Query
- */
- public void updateText(String query) {
- binding.searchBox.setQuery(query, true);
- // Clear focus of searchView now. searchView.clearFocus(); does not seem to work Check the below link for more details.
- // https://stackoverflow.com/questions/6117967/how-to-remove-focus-without-setting-focus-to-another-control/15481511
- binding.viewPager.requestFocus();
- }
-
- @Override protected void onDestroy() {
- super.onDestroy();
- //Dispose the disposables when the activity is destroyed
- getCompositeDisposable().dispose();
- binding = null;
- }
-}
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/SearchActivity.kt b/app/src/main/java/fr/free/nrw/commons/explore/SearchActivity.kt
new file mode 100644
index 000000000..0d7dfd218
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/explore/SearchActivity.kt
@@ -0,0 +1,254 @@
+package fr.free.nrw.commons.explore
+
+import android.os.Bundle
+import android.text.TextUtils
+import android.view.View
+import androidx.fragment.app.FragmentManager
+import com.jakewharton.rxbinding2.view.RxView
+import com.jakewharton.rxbinding2.widget.RxSearchView
+import fr.free.nrw.commons.Media
+import fr.free.nrw.commons.R
+import fr.free.nrw.commons.ViewPagerAdapter
+import fr.free.nrw.commons.category.CategoryImagesCallback
+import fr.free.nrw.commons.databinding.ActivitySearchBinding
+import fr.free.nrw.commons.explore.categories.search.SearchCategoryFragment
+import fr.free.nrw.commons.explore.depictions.search.SearchDepictionsFragment
+import fr.free.nrw.commons.explore.media.SearchMediaFragment
+import fr.free.nrw.commons.explore.models.RecentSearch
+import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao
+import fr.free.nrw.commons.explore.recentsearches.RecentSearchesFragment
+import fr.free.nrw.commons.media.MediaDetailPagerFragment
+import fr.free.nrw.commons.media.MediaDetailProvider
+import fr.free.nrw.commons.theme.BaseActivity
+import fr.free.nrw.commons.utils.FragmentUtils.isFragmentUIActive
+import fr.free.nrw.commons.utils.ViewUtil.hideKeyboard
+import fr.free.nrw.commons.utils.applyEdgeToEdgeAllInsets
+import io.reactivex.android.schedulers.AndroidSchedulers
+import timber.log.Timber
+import java.util.Date
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+
+/**
+ * Represents search screen of this app
+ */
+class SearchActivity : BaseActivity(), MediaDetailProvider, CategoryImagesCallback {
+ @JvmField
+ @Inject
+ var recentSearchesDao: RecentSearchesDao? = null
+
+ private var searchMediaFragment: SearchMediaFragment? = null
+ private var searchCategoryFragment: SearchCategoryFragment? = null
+ private var searchDepictionsFragment: SearchDepictionsFragment? = null
+ private var recentSearchesFragment: RecentSearchesFragment? = null
+ private var supportFragmentManager: FragmentManager? = null
+ private var mediaDetails: MediaDetailPagerFragment? = null
+ private var viewPagerAdapter: ViewPagerAdapter? = null
+ private var binding: ActivitySearchBinding? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivitySearchBinding.inflate(layoutInflater)
+ applyEdgeToEdgeAllInsets(binding!!.root)
+ setContentView(binding!!.root)
+
+ title = getString(R.string.title_activity_search)
+ setSupportActionBar(binding!!.toolbarSearch)
+ supportActionBar!!.setDisplayHomeAsUpEnabled(true)
+ binding!!.toolbarSearch.setNavigationOnClickListener { onBackPressed() }
+ supportFragmentManager = getSupportFragmentManager()
+ setSearchHistoryFragment()
+ viewPagerAdapter = ViewPagerAdapter(this, getSupportFragmentManager())
+ binding!!.viewPager.adapter = viewPagerAdapter
+ binding!!.viewPager.offscreenPageLimit = 2 // Because we want all the fragments to be alive
+ binding!!.tabLayout.setupWithViewPager(binding!!.viewPager)
+ setTabs()
+ binding!!.searchBox.queryHint = getString(R.string.search_commons)
+ binding!!.searchBox.onActionViewExpanded()
+ binding!!.searchBox.clearFocus()
+ }
+
+ /**
+ * This method sets the search history fragment.
+ * Search history fragment is displayed when query is empty.
+ */
+ private fun setSearchHistoryFragment() {
+ recentSearchesFragment = RecentSearchesFragment()
+ val transaction = supportFragmentManager!!.beginTransaction()
+ transaction.add(R.id.searchHistoryContainer, recentSearchesFragment!!).commit()
+ }
+
+ /**
+ * Sets the titles in the tabLayout and fragments in the viewPager
+ */
+ fun setTabs() {
+ searchMediaFragment = SearchMediaFragment()
+ searchDepictionsFragment = SearchDepictionsFragment()
+ searchCategoryFragment = SearchCategoryFragment()
+
+ viewPagerAdapter!!.setTabs(
+ R.string.search_tab_title_media to searchMediaFragment!!,
+ R.string.search_tab_title_categories to searchCategoryFragment!!,
+ R.string.search_tab_title_depictions to searchDepictionsFragment!!
+ )
+ viewPagerAdapter!!.notifyDataSetChanged()
+ compositeDisposable.add(
+ RxSearchView.queryTextChanges(binding!!.searchBox)
+ .takeUntil(RxView.detaches(binding!!.searchBox))
+ .debounce(500, TimeUnit.MILLISECONDS)
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(::handleSearch, Timber::e)
+ )
+ }
+
+ private fun handleSearch(query: CharSequence) {
+ if (!TextUtils.isEmpty(query)) {
+ saveRecentSearch(query.toString())
+ binding!!.viewPager.visibility = View.VISIBLE
+ binding!!.tabLayout.visibility = View.VISIBLE
+ binding!!.searchHistoryContainer.visibility = View.GONE
+
+ if (isFragmentUIActive(searchDepictionsFragment)) {
+ searchDepictionsFragment!!.onQueryUpdated(query.toString())
+ }
+
+ if (isFragmentUIActive(searchMediaFragment)) {
+ searchMediaFragment!!.onQueryUpdated(query.toString())
+ }
+
+ if (isFragmentUIActive(searchCategoryFragment)) {
+ searchCategoryFragment!!.onQueryUpdated(query.toString())
+ }
+ } else {
+ //Open RecentSearchesFragment
+ recentSearchesFragment!!.updateRecentSearches()
+ binding!!.viewPager.visibility = View.GONE
+ binding!!.tabLayout.visibility = View.GONE
+ setSearchHistoryFragment()
+ binding!!.searchHistoryContainer.visibility = View.VISIBLE
+ }
+ }
+
+ private fun saveRecentSearch(query: String) {
+ val recentSearch = recentSearchesDao!!.find(query)
+ // Newly searched query...
+ if (recentSearch == null) {
+ recentSearchesDao!!.save(RecentSearch(null, query, Date()))
+ } else {
+ recentSearch.lastSearched = Date()
+ recentSearchesDao!!.save(recentSearch)
+ }
+ }
+
+ override fun getMediaAtPosition(i: Int): Media? = searchMediaFragment!!.getMediaAtPosition(i)
+
+ override fun getTotalMediaCount(): Int = searchMediaFragment!!.getTotalMediaCount()
+
+ override fun getContributionStateAt(position: Int): Int? = null
+
+ /**
+ * Reload media detail fragment once media is nominated
+ *
+ * @param index item position that has been nominated
+ */
+ override fun refreshNominatedMedia(index: Int) {
+ if (getSupportFragmentManager().backStackEntryCount == 1) {
+ onBackPressed()
+ onMediaClicked(index)
+ }
+ }
+
+ /**
+ * This method is called on success of API call for image Search.
+ * The viewpager will notified that number of items have changed.
+ */
+ override fun viewPagerNotifyDataSetChanged() {
+ mediaDetails?.notifyDataSetChanged()
+ }
+
+ /**
+ * Open media detail pager fragment on click of image in search results
+ * @param position item index that should be opened
+ */
+ override fun onMediaClicked(position: Int) {
+ hideKeyboard(findViewById(R.id.searchBox))
+ binding!!.tabLayout.visibility = View.GONE
+ binding!!.viewPager.visibility = View.GONE
+ binding!!.mediaContainer.visibility = View.VISIBLE
+ binding!!.searchBox.visibility =
+ 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 = MediaDetailPagerFragment.newInstance(false, true)
+ supportFragmentManager!!
+ .beginTransaction()
+ .hide(supportFragmentManager!!.fragments[supportFragmentManager!!.backStackEntryCount])
+ .add(R.id.mediaContainer, mediaDetails!!)
+ .addToBackStack(null)
+ .commit()
+ // Reason for using hide, add instead of replace is to maintain scroll position after
+ // coming back to the search activity. See https://github.com/commons-app/apps-android-commons/issues/1631
+ // https://stackoverflow.com/questions/11353075/how-can-i-maintain-fragment-state-when-added-to-the-back-stack/19022550#19022550
+ supportFragmentManager!!.executePendingTransactions()
+ }
+ mediaDetails!!.showImage(position)
+ }
+
+ /**
+ * This method is called on Screen Rotation
+ */
+ override fun onResume() {
+ if (supportFragmentManager!!.backStackEntryCount == 1) {
+ //FIXME: Temporary fix for screen rotation inside media details. If we don't call onBackPressed then fragment stack is increasing every time.
+ //FIXME: Similar issue like this https://github.com/commons-app/apps-android-commons/issues/894
+ // This is called on screen rotation when user is inside media details. Ideally it should show Media Details but since we are not saving the state now. We are throwing the user to search screen otherwise the app was crashing.
+ onBackPressed()
+ }
+ super.onResume()
+ }
+
+ /**
+ * This method is called on backPressed of anyFragment in the activity.
+ * If condition is called when mediaDetailFragment is opened.
+ */
+ override fun onBackPressed() {
+ //Remove the backstack entry that gets added when share button is clicked
+ //fixing:https://github.com/commons-app/apps-android-commons/issues/2296
+ if (getSupportFragmentManager().backStackEntryCount == 2) {
+ supportFragmentManager!!
+ .beginTransaction()
+ .remove(mediaDetails!!)
+ .commit()
+ supportFragmentManager!!.popBackStack()
+ supportFragmentManager!!.executePendingTransactions()
+ }
+ if (getSupportFragmentManager().backStackEntryCount == 1) {
+ // back to search so show search toolbar and hide navigation toolbar
+ binding!!.searchBox.visibility = View.VISIBLE //set the searchview
+ binding!!.tabLayout.visibility = View.VISIBLE
+ binding!!.viewPager.visibility = View.VISIBLE
+ binding!!.mediaContainer.visibility = View.GONE
+ } else {
+ binding!!.toolbarSearch.visibility = View.GONE
+ }
+ super.onBackPressed()
+ }
+
+ /**
+ * This method is called on click of a recent search to update query in SearchView.
+ * @param query Recent Search Query
+ */
+ fun updateText(query: String?) {
+ binding!!.searchBox.setQuery(query, true)
+ // Clear focus of searchView now. searchView.clearFocus(); does not seem to work Check the below link for more details.
+ // https://stackoverflow.com/questions/6117967/how-to-remove-focus-without-setting-focus-to-another-control/15481511
+ binding!!.viewPager.requestFocus()
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ //Dispose the disposables when the activity is destroyed
+ compositeDisposable.dispose()
+ binding = null
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/depictions/PageableDepictionsFragment.kt b/app/src/main/java/fr/free/nrw/commons/explore/depictions/PageableDepictionsFragment.kt
index 9c41628a2..7c1d08bac 100644
--- a/app/src/main/java/fr/free/nrw/commons/explore/depictions/PageableDepictionsFragment.kt
+++ b/app/src/main/java/fr/free/nrw/commons/explore/depictions/PageableDepictionsFragment.kt
@@ -7,6 +7,6 @@ import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
abstract class PageableDepictionsFragment : BasePagingFragment() {
override val errorTextId: Int = R.string.error_loading_depictions
override val pagedListAdapter by lazy {
- DepictionAdapter { WikidataItemDetailsActivity.startYourself(context, it) }
+ DepictionAdapter { WikidataItemDetailsActivity.startYourself(requireContext(), it) }
}
}
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
deleted file mode 100644
index ec5ea42a4..000000000
--- a/app/src/main/java/fr/free/nrw/commons/explore/depictions/WikidataItemDetailsActivity.java
+++ /dev/null
@@ -1,302 +0,0 @@
-package fr.free.nrw.commons.explore.depictions;
-
-import static fr.free.nrw.commons.ViewPagerAdapter.pairOf;
-import static fr.free.nrw.commons.utils.UrlUtilsKt.handleWebUrl;
-
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import androidx.fragment.app.FragmentManager;
-import com.google.android.material.snackbar.Snackbar;
-import fr.free.nrw.commons.Media;
-import fr.free.nrw.commons.R;
-import fr.free.nrw.commons.ViewPagerAdapter;
-import fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao;
-import fr.free.nrw.commons.category.CategoryImagesCallback;
-import fr.free.nrw.commons.databinding.ActivityWikidataItemDetailsBinding;
-import fr.free.nrw.commons.explore.depictions.child.ChildDepictionsFragment;
-import fr.free.nrw.commons.explore.depictions.media.DepictedImagesFragment;
-import fr.free.nrw.commons.explore.depictions.parent.ParentDepictionsFragment;
-import fr.free.nrw.commons.media.MediaDetailPagerFragment;
-import fr.free.nrw.commons.media.MediaDetailProvider;
-import fr.free.nrw.commons.theme.BaseActivity;
-import fr.free.nrw.commons.upload.structure.depictions.DepictModel;
-import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
-import fr.free.nrw.commons.wikidata.WikidataConstants;
-import io.reactivex.android.schedulers.AndroidSchedulers;
-import io.reactivex.disposables.CompositeDisposable;
-import io.reactivex.schedulers.Schedulers;
-import javax.inject.Inject;
-
-/**
- * Activity to show depiction media, parent classes and child classes of depicted items in Explore
- */
-public class WikidataItemDetailsActivity extends BaseActivity implements MediaDetailProvider,
- CategoryImagesCallback {
- private FragmentManager supportFragmentManager;
- private DepictedImagesFragment depictionImagesListFragment;
- private MediaDetailPagerFragment mediaDetailPagerFragment;
-
- /**
- * Name of the depicted item
- * Ex: Rabbit
- */
-
- @Inject BookmarkItemsDao bookmarkItemsDao;
- private CompositeDisposable compositeDisposable;
- @Inject
- DepictModel depictModel;
- private String wikidataItemName;
- private ActivityWikidataItemDetailsBinding binding;
-
- ViewPagerAdapter viewPagerAdapter;
- private DepictedItem wikidataItem;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- binding = ActivityWikidataItemDetailsBinding.inflate(getLayoutInflater());
- setContentView(binding.getRoot());
- compositeDisposable = new CompositeDisposable();
- supportFragmentManager = getSupportFragmentManager();
- viewPagerAdapter = new ViewPagerAdapter(this, getSupportFragmentManager());
- binding.viewPager.setAdapter(viewPagerAdapter);
- binding.viewPager.setOffscreenPageLimit(2);
- binding.tabLayout.setupWithViewPager(binding.viewPager);
-
- final DepictedItem depictedItem = getIntent().getParcelableExtra(
- WikidataConstants.BOOKMARKS_ITEMS);
- wikidataItem = depictedItem;
- setSupportActionBar(binding.toolbarBinding.toolbar);
- getSupportActionBar().setDisplayHomeAsUpEnabled(true);
- setTabs();
- setPageTitle();
- }
-
- /**
- * Gets the passed wikidataItemName from the intents and displays it as the page title
- */
- private void setPageTitle() {
- if (getIntent() != null && getIntent().getStringExtra("wikidataItemName") != null) {
- setTitle(getIntent().getStringExtra("wikidataItemName"));
- }
- }
-
- /**
- * This method is called on success of API call for featured Images.
- * The viewpager will notified that number of items have changed.
- */
- @Override
- public void viewPagerNotifyDataSetChanged() {
- if (mediaDetailPagerFragment !=null){
- mediaDetailPagerFragment.notifyDataSetChanged();
- }
- }
-
- /**
- * This activity contains 3 tabs and a viewpager. This method is used to set the titles of tab,
- * Set the fragments according to the tab selected in the viewPager.
- */
- private void setTabs() {
- depictionImagesListFragment = new DepictedImagesFragment();
- ChildDepictionsFragment childDepictionsFragment = new ChildDepictionsFragment();
- ParentDepictionsFragment parentDepictionsFragment = new ParentDepictionsFragment();
- wikidataItemName = getIntent().getStringExtra("wikidataItemName");
- String entityId = getIntent().getStringExtra("entityId");
- if (getIntent() != null && wikidataItemName != null) {
- Bundle arguments = new Bundle();
- arguments.putString("wikidataItemName", wikidataItemName);
- arguments.putString("entityId", entityId);
- depictionImagesListFragment.setArguments(arguments);
- parentDepictionsFragment.setArguments(arguments);
- childDepictionsFragment.setArguments(arguments);
- }
-
- viewPagerAdapter.setTabs(
- pairOf(R.string.title_for_media, depictionImagesListFragment),
- pairOf(R.string.title_for_subcategories, childDepictionsFragment),
- pairOf(R.string.title_for_parent_categories, parentDepictionsFragment)
- );
- binding.viewPager.setOffscreenPageLimit(2);
- viewPagerAdapter.notifyDataSetChanged();
-
- }
-
-
- /**
- * Shows media detail fragment when user clicks on any image in the list
- */
- @Override
- public void onMediaClicked(int position) {
- binding.tabLayout.setVisibility(View.GONE);
- binding.viewPager.setVisibility(View.GONE);
- binding.mediaContainer.setVisibility(View.VISIBLE);
- if (mediaDetailPagerFragment == null || !mediaDetailPagerFragment.isVisible()) {
- // set isFeaturedImage true for featured images, to include author field on media detail
- mediaDetailPagerFragment = MediaDetailPagerFragment.newInstance(false, true);
- FragmentManager supportFragmentManager = getSupportFragmentManager();
- supportFragmentManager
- .beginTransaction()
- .replace(R.id.mediaContainer, mediaDetailPagerFragment)
- .addToBackStack(null)
- .commit();
- supportFragmentManager.executePendingTransactions();
- }
- mediaDetailPagerFragment.showImage(position);
- }
-
- /**
- * This method is called mediaDetailPagerFragment. It returns the Media Object at that Index
- * @param i It is the index of which media object is to be returned which is same as
- * current index of viewPager.
- * @return Media Object
- */
- @Override
- public Media getMediaAtPosition(int i) {
- return depictionImagesListFragment.getMediaAtPosition(i);
- }
-
- /**
- * This method is called on backPressed of anyFragment in the activity.
- * If condition is called when mediaDetailFragment is opened.
- */
- @Override
- public void onBackPressed() {
- if (supportFragmentManager.getBackStackEntryCount() == 1){
- binding.tabLayout.setVisibility(View.VISIBLE);
- binding.viewPager.setVisibility(View.VISIBLE);
- binding.mediaContainer.setVisibility(View.GONE);
- }
- super.onBackPressed();
- }
-
- /**
- * This method is called on from getCount of MediaDetailPagerFragment
- * The viewpager will contain same number of media items as that of media elements in adapter.
- * @return Total Media count in the adapter
- */
- @Override
- public int getTotalMediaCount() {
- return depictionImagesListFragment.getTotalMediaCount();
- }
-
- @Override
- public Integer getContributionStateAt(int position) {
- return null;
- }
-
- /**
- * Reload media detail fragment once media is nominated
- *
- * @param index item position that has been nominated
- */
- @Override
- public void refreshNominatedMedia(int index) {
- if (getSupportFragmentManager().getBackStackEntryCount() == 1) {
- onBackPressed();
- onMediaClicked(index);
- }
- }
-
- /**
- * Consumers should be simply using this method to use this activity.
- *
- * @param context A Context of the application package implementing this class.
- * @param depictedItem Name of the depicts for displaying its details
- */
- public static void startYourself(Context context, DepictedItem depictedItem) {
- Intent intent = new Intent(context, WikidataItemDetailsActivity.class);
- intent.putExtra("wikidataItemName", depictedItem.getName());
- intent.putExtra("entityId", depictedItem.getId());
- intent.putExtra(WikidataConstants.BOOKMARKS_ITEMS, depictedItem);
- context.startActivity(intent);
- }
-
- /**
- * This function inflates the menu
- */
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- MenuInflater menuInflater=getMenuInflater();
- menuInflater.inflate(R.menu.menu_wikidata_item,menu);
-
- updateBookmarkState(menu.findItem(R.id.menu_bookmark_current_item));
-
- return super.onCreateOptionsMenu(menu);
- }
-
- /**
- * This method handles the logic on item select in toolbar menu
- * Currently only 1 choice is available to open Wikidata item details page in browser
- */
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
-
- switch (item.getItemId()){
- case R.id.browser_actions_menu_items:
- String entityId=getIntent().getStringExtra("entityId");
- Uri uri = Uri.parse("https://www.wikidata.org/wiki/" + entityId);
- handleWebUrl(this, uri);
- return true;
- case R.id.menu_bookmark_current_item:
-
- if(getIntent().getStringExtra("fragment") != null) {
- compositeDisposable.add(depictModel.getDepictions(
- getIntent().getStringExtra("entityId")
- ).subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(depictedItems -> {
- final boolean bookmarkExists = bookmarkItemsDao.updateBookmarkItem(
- depictedItems.get(0));
- final Snackbar snackbar
- = bookmarkExists ? Snackbar.make(findViewById(R.id.toolbar_layout),
- R.string.add_bookmark, Snackbar.LENGTH_LONG)
- : Snackbar.make(findViewById(R.id.toolbar_layout),
- R.string.remove_bookmark,
- Snackbar.LENGTH_LONG);
-
- snackbar.show();
- updateBookmarkState(item);
- }));
-
- } else {
- final boolean bookmarkExists
- = bookmarkItemsDao.updateBookmarkItem(wikidataItem);
- final Snackbar snackbar
- = bookmarkExists ? Snackbar.make(findViewById(R.id.toolbar_layout),
- R.string.add_bookmark, Snackbar.LENGTH_LONG)
- : Snackbar.make(findViewById(R.id.toolbar_layout), R.string.remove_bookmark,
- Snackbar.LENGTH_LONG);
-
- snackbar.show();
- updateBookmarkState(item);
- }
- return true;
- case android.R.id.home:
- onBackPressed();
- return true;
- default:
- return super.onOptionsItemSelected(item);
- }
- }
-
- private void updateBookmarkState(final MenuItem item) {
- final boolean isBookmarked;
- if(getIntent().getStringExtra("fragment") != null) {
- isBookmarked
- = bookmarkItemsDao.findBookmarkItem(getIntent().getStringExtra("entityId"));
- } else {
- isBookmarked = bookmarkItemsDao.findBookmarkItem(wikidataItem.getId());
- }
- final int icon
- = isBookmarked ? R.drawable.menu_ic_round_star_filled_24px
- : R.drawable.menu_ic_round_star_border_24px;
- item.setIcon(icon);
- }
-}
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/depictions/WikidataItemDetailsActivity.kt b/app/src/main/java/fr/free/nrw/commons/explore/depictions/WikidataItemDetailsActivity.kt
new file mode 100644
index 000000000..d025fdfe1
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/explore/depictions/WikidataItemDetailsActivity.kt
@@ -0,0 +1,297 @@
+package fr.free.nrw.commons.explore.depictions
+
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import android.os.Bundle
+import android.view.Menu
+import android.view.MenuItem
+import android.view.View
+import androidx.core.os.bundleOf
+import androidx.fragment.app.FragmentManager
+import com.google.android.material.snackbar.Snackbar
+import fr.free.nrw.commons.Media
+import fr.free.nrw.commons.R
+import fr.free.nrw.commons.ViewPagerAdapter
+import fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao
+import fr.free.nrw.commons.category.CategoryImagesCallback
+import fr.free.nrw.commons.databinding.ActivityWikidataItemDetailsBinding
+import fr.free.nrw.commons.explore.depictions.child.ChildDepictionsFragment
+import fr.free.nrw.commons.explore.depictions.media.DepictedImagesFragment
+import fr.free.nrw.commons.explore.depictions.parent.ParentDepictionsFragment
+import fr.free.nrw.commons.media.MediaDetailPagerFragment
+import fr.free.nrw.commons.media.MediaDetailProvider
+import fr.free.nrw.commons.theme.BaseActivity
+import fr.free.nrw.commons.upload.structure.depictions.DepictModel
+import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
+import fr.free.nrw.commons.utils.applyEdgeToEdgeAllInsets
+import fr.free.nrw.commons.utils.handleWebUrl
+import fr.free.nrw.commons.wikidata.WikidataConstants
+import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.functions.Consumer
+import io.reactivex.schedulers.Schedulers
+import javax.inject.Inject
+
+/**
+ * Activity to show depiction media, parent classes and child classes of depicted items in Explore
+ */
+class WikidataItemDetailsActivity : BaseActivity(), MediaDetailProvider, CategoryImagesCallback {
+ @JvmField
+ @Inject
+ var bookmarkItemsDao: BookmarkItemsDao? = null
+
+ @JvmField
+ @Inject
+ var depictModel: DepictModel? = null
+
+ private var supportFragmentManager: FragmentManager? = null
+ private var depictionImagesListFragment: DepictedImagesFragment? = null
+ private var mediaDetailPagerFragment: MediaDetailPagerFragment? = null
+ private var binding: ActivityWikidataItemDetailsBinding? = null
+
+ var viewPagerAdapter: ViewPagerAdapter? = null
+ private var wikidataItem: DepictedItem? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ binding = ActivityWikidataItemDetailsBinding.inflate(layoutInflater)
+ applyEdgeToEdgeAllInsets(binding!!.root)
+ setContentView(binding!!.root)
+ supportFragmentManager = getSupportFragmentManager()
+ viewPagerAdapter = ViewPagerAdapter(this, getSupportFragmentManager())
+ binding!!.viewPager.adapter = viewPagerAdapter
+ binding!!.viewPager.offscreenPageLimit = 2
+ binding!!.tabLayout.setupWithViewPager(binding!!.viewPager)
+
+ wikidataItem = intent.getParcelableExtra(WikidataConstants.BOOKMARKS_ITEMS)
+ setSupportActionBar(binding!!.toolbarBinding.toolbar)
+ supportActionBar!!.setDisplayHomeAsUpEnabled(true)
+ setTabs()
+ setPageTitle()
+ }
+
+ /**
+ * Gets the passed wikidataItemName from the intents and displays it as the page title
+ */
+ private fun setPageTitle() {
+ if (intent != null && intent.getStringExtra("wikidataItemName") != null) {
+ title = intent.getStringExtra("wikidataItemName")
+ }
+ }
+
+ /**
+ * This method is called on success of API call for featured Images.
+ * The viewpager will notified that number of items have changed.
+ */
+ override fun viewPagerNotifyDataSetChanged() {
+ if (mediaDetailPagerFragment != null) {
+ mediaDetailPagerFragment!!.notifyDataSetChanged()
+ }
+ }
+
+ /**
+ * This activity contains 3 tabs and a viewpager. This method is used to set the titles of tab,
+ * Set the fragments according to the tab selected in the viewPager.
+ */
+ private fun setTabs() {
+ depictionImagesListFragment = DepictedImagesFragment()
+ val childDepictionsFragment = ChildDepictionsFragment()
+ val parentDepictionsFragment = ParentDepictionsFragment()
+ val wikidataItemName = intent.getStringExtra("wikidataItemName")
+ val entityId = intent.getStringExtra("entityId")
+ if (intent != null && wikidataItemName != null) {
+ val arguments = bundleOf(
+ "wikidataItemName" to wikidataItemName,
+ "entityId" to entityId
+ )
+ depictionImagesListFragment!!.arguments = arguments
+ parentDepictionsFragment.arguments = arguments
+ childDepictionsFragment.arguments = arguments
+ }
+
+ viewPagerAdapter!!.setTabs(
+ R.string.title_for_media to depictionImagesListFragment!!,
+ R.string.title_for_child_classes to childDepictionsFragment,
+ R.string.title_for_parent_classes to parentDepictionsFragment
+ )
+ binding!!.viewPager.offscreenPageLimit = 2
+ viewPagerAdapter!!.notifyDataSetChanged()
+ }
+
+
+ /**
+ * Shows media detail fragment when user clicks on any image in the list
+ */
+ override fun onMediaClicked(position: Int) {
+ binding!!.tabLayout.visibility = View.GONE
+ binding!!.viewPager.visibility = View.GONE
+ binding!!.mediaContainer.visibility = View.VISIBLE
+ if (mediaDetailPagerFragment == null || !mediaDetailPagerFragment!!.isVisible) {
+ // set isFeaturedImage true for featured images, to include author field on media detail
+ mediaDetailPagerFragment = MediaDetailPagerFragment.newInstance(false, true)
+ val supportFragmentManager = getSupportFragmentManager()
+ supportFragmentManager
+ .beginTransaction()
+ .replace(R.id.mediaContainer, mediaDetailPagerFragment!!)
+ .addToBackStack(null)
+ .commit()
+ supportFragmentManager.executePendingTransactions()
+ }
+ mediaDetailPagerFragment!!.showImage(position)
+ }
+
+ /**
+ * This method is called mediaDetailPagerFragment. It returns the Media Object at that Index
+ * @param i It is the index of which media object is to be returned which is same as
+ * current index of viewPager.
+ * @return Media Object
+ */
+ override fun getMediaAtPosition(i: Int): Media? {
+ return depictionImagesListFragment!!.getMediaAtPosition(i)
+ }
+
+ /**
+ * This method is called on backPressed of anyFragment in the activity.
+ * If condition is called when mediaDetailFragment is opened.
+ */
+ override fun onBackPressed() {
+ if (supportFragmentManager!!.backStackEntryCount == 1) {
+ binding!!.tabLayout.visibility = View.VISIBLE
+ binding!!.viewPager.visibility = View.VISIBLE
+ binding!!.mediaContainer.visibility = View.GONE
+ }
+ super.onBackPressed()
+ }
+
+ /**
+ * This method is called on from getCount of MediaDetailPagerFragment
+ * The viewpager will contain same number of media items as that of media elements in adapter.
+ * @return Total Media count in the adapter
+ */
+ override fun getTotalMediaCount(): Int = depictionImagesListFragment!!.getTotalMediaCount()
+
+ override fun getContributionStateAt(position: Int): Int? = null
+
+ /**
+ * Reload media detail fragment once media is nominated
+ *
+ * @param index item position that has been nominated
+ */
+ override fun refreshNominatedMedia(index: Int) {
+ if (getSupportFragmentManager().backStackEntryCount == 1) {
+ onBackPressed()
+ onMediaClicked(index)
+ }
+ }
+
+ /**
+ * This function inflates the menu
+ */
+ override fun onCreateOptionsMenu(menu: Menu): Boolean {
+ val menuInflater = menuInflater
+ menuInflater.inflate(R.menu.menu_wikidata_item, menu)
+
+ updateBookmarkState(menu.findItem(R.id.menu_bookmark_current_item))
+
+ return super.onCreateOptionsMenu(menu)
+ }
+
+ /**
+ * This method handles the logic on item select in toolbar menu
+ * Currently only 1 choice is available to open Wikidata item details page in browser
+ */
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ when (item.itemId) {
+ R.id.browser_actions_menu_items -> {
+ val entityId = intent.getStringExtra("entityId")
+ val uri = Uri.parse("https://www.wikidata.org/wiki/$entityId")
+ handleWebUrl(this, uri)
+ return true
+ }
+
+ R.id.menu_bookmark_current_item -> {
+ if (intent.getStringExtra("fragment") != null) {
+ compositeDisposable!!.add(
+ depictModel!!.getDepictions(
+ intent.getStringExtra("entityId")!!
+ ).subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(Consumer> { depictedItems: List ->
+ val bookmarkExists = bookmarkItemsDao!!.updateBookmarkItem(
+ depictedItems[0]!!
+ )
+ val snackbar = if (bookmarkExists)
+ Snackbar.make(
+ findViewById(R.id.toolbar_layout),
+ R.string.add_bookmark, Snackbar.LENGTH_LONG
+ )
+ else
+ Snackbar.make(
+ findViewById(R.id.toolbar_layout),
+ R.string.remove_bookmark,
+ Snackbar.LENGTH_LONG
+ )
+
+ snackbar.show()
+ updateBookmarkState(item)
+ })
+ )
+ } else {
+ val bookmarkExists = bookmarkItemsDao!!.updateBookmarkItem(wikidataItem!!)
+ val snackbar = if (bookmarkExists)
+ Snackbar.make(
+ findViewById(R.id.toolbar_layout),
+ R.string.add_bookmark, Snackbar.LENGTH_LONG
+ )
+ else
+ Snackbar.make(
+ findViewById(R.id.toolbar_layout), R.string.remove_bookmark,
+ Snackbar.LENGTH_LONG
+ )
+
+ snackbar.show()
+ updateBookmarkState(item)
+ }
+ return true
+ }
+
+ android.R.id.home -> {
+ onBackPressed()
+ return true
+ }
+
+ else -> return super.onOptionsItemSelected(item)
+ }
+ }
+
+ private fun updateBookmarkState(item: MenuItem) {
+ val isBookmarked: Boolean = if (intent.getStringExtra("fragment") != null) {
+ bookmarkItemsDao!!.findBookmarkItem(intent.getStringExtra("entityId"))
+ } else {
+ bookmarkItemsDao!!.findBookmarkItem(wikidataItem!!.id)
+ }
+ item.setIcon(if (isBookmarked) {
+ R.drawable.menu_ic_round_star_filled_24px
+ } else {
+ R.drawable.menu_ic_round_star_border_24px
+ })
+ }
+
+ companion object {
+ /**
+ * Consumers should be simply using this method to use this activity.
+ *
+ * @param context A Context of the application package implementing this class.
+ * @param depictedItem Name of the depicts for displaying its details
+ */
+ fun startYourself(context: Context, depictedItem: DepictedItem) {
+ val intent = Intent(context, WikidataItemDetailsActivity::class.java).apply {
+ putExtra("wikidataItemName", depictedItem.name)
+ putExtra("entityId", depictedItem.id)
+ putExtra(WikidataConstants.BOOKMARKS_ITEMS, depictedItem)
+ }
+ context.startActivity(intent)
+ }
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapCalls.java b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapCalls.java
deleted file mode 100644
index 5e674dceb..000000000
--- a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapCalls.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package fr.free.nrw.commons.explore.map;
-
-import androidx.annotation.NonNull;
-import fr.free.nrw.commons.Media;
-import fr.free.nrw.commons.location.LatLng;
-import fr.free.nrw.commons.media.MediaClient;
-import java.util.Collections;
-import java.util.List;
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-@Singleton
-public class ExploreMapCalls {
-
- @Inject
- MediaClient mediaClient;
-
- @Inject
- public ExploreMapCalls() {
- }
-
- /**
- * Calls method to query Commons for uploads around a location
- *
- * @param currentLatLng coordinates of search location
- * @return list of places obtained
- */
- @NonNull
- List callCommonsQuery(final LatLng currentLatLng) {
- String coordinates = currentLatLng.getLatitude() + "|" + currentLatLng.getLongitude();
- return mediaClient.getMediaListFromGeoSearch(coordinates).blockingGet();
- }
-
-}
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapCalls.kt b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapCalls.kt
new file mode 100644
index 000000000..6c62d3667
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapCalls.kt
@@ -0,0 +1,25 @@
+package fr.free.nrw.commons.explore.map
+
+import fr.free.nrw.commons.Media
+import fr.free.nrw.commons.location.LatLng
+import fr.free.nrw.commons.media.MediaClient
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class ExploreMapCalls @Inject constructor() {
+ @Inject
+ @JvmField
+ var mediaClient: MediaClient? = null
+
+ /**
+ * Calls method to query Commons for uploads around a location
+ *
+ * @param currentLatLng coordinates of search location
+ * @return list of places obtained
+ */
+ fun callCommonsQuery(currentLatLng: LatLng): List {
+ val coordinates = currentLatLng.latitude.toString() + "|" + currentLatLng.longitude
+ return mediaClient!!.getMediaListFromGeoSearch(coordinates).blockingGet()
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapContract.java b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapContract.java
deleted file mode 100644
index feb66bf55..000000000
--- a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapContract.java
+++ /dev/null
@@ -1,45 +0,0 @@
-package fr.free.nrw.commons.explore.map;
-
-import android.content.Context;
-import fr.free.nrw.commons.BaseMarker;
-import fr.free.nrw.commons.kvstore.JsonKvStore;
-import fr.free.nrw.commons.location.LatLng;
-import fr.free.nrw.commons.location.LocationServiceManager;
-import java.util.List;
-
-public class ExploreMapContract {
-
- interface View {
- boolean isNetworkConnectionEstablished();
- void populatePlaces(LatLng curlatLng);
- void askForLocationPermission();
- void recenterMap(LatLng curLatLng);
- void hideBottomDetailsSheet();
- LatLng getMapCenter();
- LatLng getMapFocus();
- LatLng getLastMapFocus();
- void addMarkersToMap(final List nearbyBaseMarkers);
- void clearAllMarkers();
- void addSearchThisAreaButtonAction();
- void setSearchThisAreaButtonVisibility(boolean isVisible);
- void setProgressBarVisibility(boolean isVisible);
- boolean isDetailsBottomSheetVisible();
- boolean isSearchThisAreaButtonVisible();
- Context getContext();
- LatLng getLastLocation();
- void disableFABRecenter();
- void enableFABRecenter();
- void setFABRecenterAction(android.view.View.OnClickListener onClickListener);
- boolean backButtonClicked();
- }
-
- interface UserActions {
- void updateMap(LocationServiceManager.LocationChangeType locationChangeType);
- void lockUnlockNearby(boolean isNearbyLocked);
- void attachView(View view);
- void detachView();
- void setActionListeners(JsonKvStore applicationKvStore);
- boolean backButtonClicked();
- }
-
-}
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapContract.kt b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapContract.kt
new file mode 100644
index 000000000..306446a43
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapContract.kt
@@ -0,0 +1,43 @@
+package fr.free.nrw.commons.explore.map
+
+import android.content.Context
+import android.view.View
+import fr.free.nrw.commons.BaseMarker
+import fr.free.nrw.commons.kvstore.JsonKvStore
+import fr.free.nrw.commons.location.LatLng
+import fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType
+
+class ExploreMapContract {
+ interface View {
+ fun isNetworkConnectionEstablished(): Boolean
+ fun populatePlaces(curlatLng: LatLng?)
+ fun askForLocationPermission()
+ fun recenterMap(curLatLng: LatLng?)
+ fun hideBottomDetailsSheet()
+ fun getMapCenter(): LatLng?
+ fun getMapFocus(): LatLng?
+ fun getLastMapFocus(): LatLng?
+ fun addMarkersToMap(nearbyBaseMarkers: List?)
+ fun clearAllMarkers()
+ fun addSearchThisAreaButtonAction()
+ fun setSearchThisAreaButtonVisibility(isVisible: Boolean)
+ fun setProgressBarVisibility(isVisible: Boolean)
+ fun isDetailsBottomSheetVisible(): Boolean
+ fun isSearchThisAreaButtonVisible(): Boolean
+ fun getContext(): Context?
+ fun getLastLocation(): LatLng?
+ fun disableFABRecenter()
+ fun enableFABRecenter()
+ fun setFABRecenterAction(onClickListener: android.view.View.OnClickListener?)
+ fun backButtonClicked(): Boolean
+ }
+
+ interface UserActions {
+ fun updateMap(locationChangeType: LocationChangeType)
+ fun lockUnlockNearby(isNearbyLocked: Boolean)
+ fun attachView(view: View?)
+ fun detachView()
+ fun setActionListeners(applicationKvStore: JsonKvStore?)
+ fun backButtonClicked(): Boolean
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapController.java b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapController.java
deleted file mode 100644
index c944f75a1..000000000
--- a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapController.java
+++ /dev/null
@@ -1,213 +0,0 @@
-package fr.free.nrw.commons.explore.map;
-
-import static fr.free.nrw.commons.utils.LengthUtils.computeDistanceBetween;
-import static fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.drawable.Drawable;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
-import com.bumptech.glide.Glide;
-import com.bumptech.glide.request.RequestOptions;
-import com.bumptech.glide.request.target.CustomTarget;
-import com.bumptech.glide.request.transition.Transition;
-import fr.free.nrw.commons.BaseMarker;
-import fr.free.nrw.commons.MapController;
-import fr.free.nrw.commons.Media;
-import fr.free.nrw.commons.R;
-import fr.free.nrw.commons.location.LatLng;
-import fr.free.nrw.commons.nearby.Place;
-import fr.free.nrw.commons.utils.ImageUtils;
-import fr.free.nrw.commons.utils.LocationUtils;
-import fr.free.nrw.commons.utils.PlaceUtils;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import javax.inject.Inject;
-import timber.log.Timber;
-
-public class ExploreMapController extends MapController {
-
- private final ExploreMapCalls exploreMapCalls;
- public LatLng latestSearchLocation; // Can be current and camera target on search this area button is used
- public LatLng currentLocation; // current location of user
- public double latestSearchRadius = 0; // Any last search radius
- public double currentLocationSearchRadius = 0; // Search radius of only searches around current location
-
-
- @Inject
- public ExploreMapController(ExploreMapCalls explorePlaces) {
- this.exploreMapCalls = explorePlaces;
- }
-
- /**
- * Takes location as parameter and returns ExplorePlaces info that holds currentLatLng, mediaList,
- * explorePlaceList and boundaryCoordinates
- *
- * @param currentLatLng is current geolocation
- * @param searchLatLng is the location that we want to search around
- * @param checkingAroundCurrentLocation is a boolean flag. True if we want to check around
- * current location, false if another location
- * @return explorePlacesInfo info that holds currentLatLng, mediaList, explorePlaceList and
- * boundaryCoordinates
- */
- public ExplorePlacesInfo loadAttractionsFromLocation(LatLng currentLatLng, LatLng searchLatLng,
- boolean checkingAroundCurrentLocation) {
-
- if (searchLatLng == null) {
- Timber.d("Loading attractions explore map, but search is null");
- return null;
- }
-
- ExplorePlacesInfo explorePlacesInfo = new ExplorePlacesInfo();
- try {
- explorePlacesInfo.currentLatLng = currentLatLng;
- latestSearchLocation = searchLatLng;
-
- List mediaList = exploreMapCalls.callCommonsQuery(searchLatLng);
- LatLng[] boundaryCoordinates = {mediaList.get(0).getCoordinates(), // south
- mediaList.get(0).getCoordinates(), // north
- mediaList.get(0).getCoordinates(), // west
- mediaList.get(0).getCoordinates()};// east, init with a random location
-
- if (searchLatLng != null) {
- Timber.d("Sorting places by distance...");
- final Map distances = new HashMap<>();
- for (Media media : mediaList) {
- distances.put(media,
- computeDistanceBetween(media.getCoordinates(), searchLatLng));
- // Find boundaries with basic find max approach
- if (media.getCoordinates().getLatitude()
- < boundaryCoordinates[0].getLatitude()) {
- boundaryCoordinates[0] = media.getCoordinates();
- }
- if (media.getCoordinates().getLatitude()
- > boundaryCoordinates[1].getLatitude()) {
- boundaryCoordinates[1] = media.getCoordinates();
- }
- if (media.getCoordinates().getLongitude()
- < boundaryCoordinates[2].getLongitude()) {
- boundaryCoordinates[2] = media.getCoordinates();
- }
- if (media.getCoordinates().getLongitude()
- > boundaryCoordinates[3].getLongitude()) {
- boundaryCoordinates[3] = media.getCoordinates();
- }
- }
- }
- explorePlacesInfo.mediaList = mediaList;
- explorePlacesInfo.explorePlaceList = PlaceUtils.mediaToExplorePlace(mediaList);
- explorePlacesInfo.boundaryCoordinates = boundaryCoordinates;
-
- // Sets latestSearchRadius to maximum distance among boundaries and search location
- for (LatLng bound : boundaryCoordinates) {
- double distance = LocationUtils.calculateDistance(bound.getLatitude(),
- bound.getLongitude(), searchLatLng.getLatitude(), searchLatLng.getLongitude());
- if (distance > latestSearchRadius) {
- latestSearchRadius = distance;
- }
- }
-
- // Our radius searched around us, will be used to understand when user search their own location, we will follow them
- if (checkingAroundCurrentLocation) {
- currentLocationSearchRadius = latestSearchRadius;
- currentLocation = currentLatLng;
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- return explorePlacesInfo;
- }
-
- /**
- * Loads attractions from location for map view, we need to return places in Place data type
- *
- * @return baseMarkerOptions list that holds nearby places with their icons
- */
- public static List loadAttractionsFromLocationToBaseMarkerOptions(
- LatLng currentLatLng,
- final List placeList,
- Context context,
- NearbyBaseMarkerThumbCallback callback,
- ExplorePlacesInfo explorePlacesInfo) {
- List baseMarkerList = new ArrayList<>();
-
- if (placeList == null) {
- return baseMarkerList;
- }
-
- VectorDrawableCompat vectorDrawable = null;
- try {
- vectorDrawable = VectorDrawableCompat.create(
- context.getResources(), R.drawable.ic_custom_map_marker_dark, context.getTheme());
-
- } catch (Resources.NotFoundException e) {
- // ignore when running tests.
- }
- if (vectorDrawable != null) {
- for (Place explorePlace : placeList) {
- final BaseMarker baseMarker = new BaseMarker();
- String distance = formatDistanceBetween(currentLatLng, explorePlace.location);
- explorePlace.setDistance(distance);
-
- baseMarker.setTitle(
- explorePlace.name.substring(5, explorePlace.name.lastIndexOf(".")));
- baseMarker.setPosition(
- new fr.free.nrw.commons.location.LatLng(
- explorePlace.location.getLatitude(),
- explorePlace.location.getLongitude(), 0));
- baseMarker.setPlace(explorePlace);
-
- Glide.with(context)
- .asBitmap()
- .load(explorePlace.getThumb())
- .placeholder(R.drawable.image_placeholder_96)
- .apply(new RequestOptions().override(96, 96).centerCrop())
- .into(new CustomTarget() {
- // We add icons to markers when bitmaps are ready
- @Override
- public void onResourceReady(@NonNull Bitmap resource,
- @Nullable Transition super Bitmap> transition) {
- baseMarker.setIcon(
- ImageUtils.addRedBorder(resource, 6, context));
- baseMarkerList.add(baseMarker);
- if (baseMarkerList.size()
- == placeList.size()) { // if true, we added all markers to list and can trigger thumbs ready callback
- callback.onNearbyBaseMarkerThumbsReady(baseMarkerList,
- explorePlacesInfo);
- }
- }
-
- @Override
- public void onLoadCleared(@Nullable Drawable placeholder) {
- }
-
- // We add thumbnail icon for images that couldn't be loaded
- @Override
- public void onLoadFailed(@Nullable final Drawable errorDrawable) {
- super.onLoadFailed(errorDrawable);
- baseMarker.fromResource(context, R.drawable.image_placeholder_96);
- baseMarkerList.add(baseMarker);
- if (baseMarkerList.size()
- == placeList.size()) { // if true, we added all markers to list and can trigger thumbs ready callback
- callback.onNearbyBaseMarkerThumbsReady(baseMarkerList,
- explorePlacesInfo);
- }
- }
- });
- }
- }
- return baseMarkerList;
- }
-
- interface NearbyBaseMarkerThumbCallback {
-
- // Callback to notify thumbnails of explore markers are added as icons and ready
- void onNearbyBaseMarkerThumbsReady(List baseMarkers,
- ExplorePlacesInfo explorePlacesInfo);
- }
-}
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapController.kt b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapController.kt
new file mode 100644
index 000000000..0873572d1
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapController.kt
@@ -0,0 +1,219 @@
+package fr.free.nrw.commons.explore.map
+
+import android.content.Context
+import android.content.res.Resources
+import android.graphics.Bitmap
+import android.graphics.drawable.Drawable
+import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat
+import com.bumptech.glide.Glide
+import com.bumptech.glide.request.RequestOptions
+import com.bumptech.glide.request.target.CustomTarget
+import com.bumptech.glide.request.transition.Transition
+import fr.free.nrw.commons.BaseMarker
+import fr.free.nrw.commons.MapController
+import fr.free.nrw.commons.Media
+import fr.free.nrw.commons.R
+import fr.free.nrw.commons.location.LatLng
+import fr.free.nrw.commons.nearby.Place
+import fr.free.nrw.commons.utils.ImageUtils.addRedBorder
+import fr.free.nrw.commons.utils.LengthUtils.computeDistanceBetween
+import fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween
+import fr.free.nrw.commons.utils.LocationUtils.calculateDistance
+import fr.free.nrw.commons.utils.PlaceUtils.mediaToExplorePlace
+import timber.log.Timber
+import javax.inject.Inject
+
+class ExploreMapController @Inject constructor(
+ private val exploreMapCalls: ExploreMapCalls
+) : MapController() {
+ // Can be current and camera target on search this area button is used
+ private var latestSearchLocation: LatLng? = null
+
+ // Any last search radius
+ private var latestSearchRadius: Double = 0.0
+
+ // Search radius of only searches around current location
+ private var currentLocationSearchRadius: Double = 0.0
+
+ @JvmField
+ // current location of user
+ var currentLocation: LatLng? = null
+
+ /**
+ * Takes location as parameter and returns ExplorePlaces info that holds currentLatLng, mediaList,
+ * explorePlaceList and boundaryCoordinates
+ *
+ * @param currentLatLng is current geolocation
+ * @param searchLatLng is the location that we want to search around
+ * @param checkingAroundCurrentLocation is a boolean flag. True if we want to check around
+ * current location, false if another location
+ * @return explorePlacesInfo info that holds currentLatLng, mediaList, explorePlaceList and
+ * boundaryCoordinates
+ */
+ fun loadAttractionsFromLocation(
+ currentLatLng: LatLng?, searchLatLng: LatLng?,
+ checkingAroundCurrentLocation: Boolean
+ ): ExplorePlacesInfo? {
+ if (searchLatLng == null) {
+ Timber.d("Loading attractions explore map, but search is null")
+ return null
+ }
+
+ val explorePlacesInfo = ExplorePlacesInfo()
+ try {
+ explorePlacesInfo.currentLatLng = currentLatLng
+ latestSearchLocation = searchLatLng
+
+ val mediaList = exploreMapCalls.callCommonsQuery(searchLatLng)
+ val boundaryCoordinates = arrayOf(
+ mediaList[0].coordinates!!, // south
+ mediaList[0].coordinates!!, // north
+ mediaList[0].coordinates!!, // west
+ mediaList[0].coordinates!!
+ ) // east, init with a random location
+
+ Timber.d("Sorting places by distance...")
+ val distances: MutableMap = HashMap()
+ for (media in mediaList) {
+ distances[media] = computeDistanceBetween(media.coordinates!!, searchLatLng)
+ // Find boundaries with basic find max approach
+ if (media.coordinates!!.latitude
+ < boundaryCoordinates[0]!!.latitude
+ ) {
+ boundaryCoordinates[0] = media.coordinates!!
+ }
+ if (media.coordinates!!.latitude
+ > boundaryCoordinates[1]!!.latitude
+ ) {
+ boundaryCoordinates[1] = media.coordinates!!
+ }
+ if (media.coordinates!!.longitude
+ < boundaryCoordinates[2]!!.longitude
+ ) {
+ boundaryCoordinates[2] = media.coordinates!!
+ }
+ if (media.coordinates!!.longitude
+ > boundaryCoordinates[3]!!.longitude
+ ) {
+ boundaryCoordinates[3] = media.coordinates!!
+ }
+ }
+ explorePlacesInfo.mediaList = mediaList
+ explorePlacesInfo.explorePlaceList = mediaToExplorePlace(mediaList)
+ explorePlacesInfo.boundaryCoordinates = boundaryCoordinates
+
+ // Sets latestSearchRadius to maximum distance among boundaries and search location
+ for ((latitude, longitude) in boundaryCoordinates) {
+ val distance = calculateDistance(
+ latitude,
+ longitude, searchLatLng.latitude, searchLatLng.longitude
+ )
+ if (distance > latestSearchRadius) {
+ latestSearchRadius = distance
+ }
+ }
+
+ // Our radius searched around us, will be used to understand when user search their own location, we will follow them
+ if (checkingAroundCurrentLocation) {
+ currentLocationSearchRadius = latestSearchRadius
+ currentLocation = currentLatLng
+ }
+ } catch (e: Exception) {
+ Timber.e(e)
+ }
+ return explorePlacesInfo
+ }
+
+ interface NearbyBaseMarkerThumbCallback {
+ // Callback to notify thumbnails of explore markers are added as icons and ready
+ fun onNearbyBaseMarkerThumbsReady(
+ baseMarkers: List?,
+ explorePlacesInfo: ExplorePlacesInfo?
+ )
+ }
+
+ companion object {
+ /**
+ * Loads attractions from location for map view, we need to return places in Place data type
+ *
+ * @return baseMarkerOptions list that holds nearby places with their icons
+ */
+ fun loadAttractionsFromLocationToBaseMarkerOptions(
+ currentLatLng: LatLng?,
+ placeList: List?,
+ context: Context,
+ callback: NearbyBaseMarkerThumbCallback,
+ explorePlacesInfo: ExplorePlacesInfo?
+ ): List {
+ val baseMarkerList: MutableList = ArrayList()
+
+ if (placeList == null) {
+ return baseMarkerList
+ }
+
+ var vectorDrawable: VectorDrawableCompat? = null
+ try {
+ vectorDrawable = VectorDrawableCompat.create(
+ context.resources, R.drawable.ic_custom_map_marker_dark, context.theme
+ )
+ } catch (e: Resources.NotFoundException) {
+ // ignore when running tests.
+ }
+ if (vectorDrawable != null) {
+ for (explorePlace in placeList) {
+ val baseMarker = BaseMarker()
+ val distance = formatDistanceBetween(currentLatLng, explorePlace.location)
+ explorePlace.setDistance(distance)
+
+ baseMarker.title =
+ explorePlace.name.substring(5, explorePlace.name.lastIndexOf("."))
+ baseMarker.position = LatLng(
+ explorePlace.location.latitude,
+ explorePlace.location.longitude, 0f
+ )
+ baseMarker.place = explorePlace
+
+ Glide.with(context)
+ .asBitmap()
+ .load(explorePlace.thumb)
+ .placeholder(R.drawable.image_placeholder_96)
+ .apply(RequestOptions().override(96, 96).centerCrop())
+ .into(object : CustomTarget() {
+ // We add icons to markers when bitmaps are ready
+ override fun onResourceReady(
+ resource: Bitmap,
+ transition: Transition?
+ ) {
+ baseMarker.icon = addRedBorder(resource, 6, context)
+ baseMarkerList.add(baseMarker)
+ if (baseMarkerList.size == placeList.size) {
+ // if true, we added all markers to list and can trigger thumbs ready callback
+ callback.onNearbyBaseMarkerThumbsReady(
+ baseMarkerList,
+ explorePlacesInfo
+ )
+ }
+ }
+
+ override fun onLoadCleared(placeholder: Drawable?) = Unit
+
+ // We add thumbnail icon for images that couldn't be loaded
+ override fun onLoadFailed(errorDrawable: Drawable?) {
+ super.onLoadFailed(errorDrawable)
+ baseMarker.fromResource(context, R.drawable.image_placeholder_96)
+ baseMarkerList.add(baseMarker)
+ if (baseMarkerList.size == placeList.size) {
+ // if true, we added all markers to list and can trigger thumbs ready callback
+ callback.onNearbyBaseMarkerThumbsReady(
+ baseMarkerList,
+ explorePlacesInfo
+ )
+ }
+ }
+ })
+ }
+ }
+ return baseMarkerList
+ }
+ }
+}
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
deleted file mode 100644
index 364f4d53a..000000000
--- a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.java
+++ /dev/null
@@ -1,1140 +0,0 @@
-package fr.free.nrw.commons.explore.map;
-
-import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED;
-import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.LOCATION_SLIGHTLY_CHANGED;
-import static fr.free.nrw.commons.utils.GeoCoordinatesKt.handleGeoCoordinates;
-import static fr.free.nrw.commons.utils.MapUtils.ZOOM_LEVEL;
-import static fr.free.nrw.commons.utils.UrlUtilsKt.handleWebUrl;
-
-import android.Manifest.permission;
-import android.annotation.SuppressLint;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.graphics.Bitmap;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.location.Location;
-import android.location.LocationManager;
-import android.os.Build;
-import android.os.Bundle;
-import android.preference.PreferenceManager;
-import android.text.Html;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import androidx.activity.result.ActivityResultLauncher;
-import androidx.activity.result.contract.ActivityResultContracts;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.core.content.ContextCompat;
-import com.google.android.material.bottomsheet.BottomSheetBehavior;
-import com.google.android.material.snackbar.Snackbar;
-import fr.free.nrw.commons.BaseMarker;
-import fr.free.nrw.commons.MapController;
-import fr.free.nrw.commons.Media;
-import fr.free.nrw.commons.R;
-import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao;
-import fr.free.nrw.commons.contributions.MainActivity;
-import fr.free.nrw.commons.databinding.FragmentExploreMapBinding;
-import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
-import fr.free.nrw.commons.explore.ExploreMapRootFragment;
-import fr.free.nrw.commons.explore.paging.LiveDataConverter;
-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.LocationPermissionCallback;
-import fr.free.nrw.commons.location.LocationServiceManager;
-import fr.free.nrw.commons.location.LocationUpdateListener;
-import fr.free.nrw.commons.media.MediaClient;
-import fr.free.nrw.commons.nearby.Place;
-import fr.free.nrw.commons.utils.DialogUtil;
-import fr.free.nrw.commons.utils.MapUtils;
-import fr.free.nrw.commons.utils.NetworkUtils;
-import fr.free.nrw.commons.utils.SystemThemeUtils;
-import fr.free.nrw.commons.utils.ViewUtil;
-import io.reactivex.Observable;
-import io.reactivex.android.schedulers.AndroidSchedulers;
-import io.reactivex.schedulers.Schedulers;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import javax.inject.Inject;
-import javax.inject.Named;
-import org.osmdroid.events.MapEventsReceiver;
-import org.osmdroid.events.MapListener;
-import org.osmdroid.events.ScrollEvent;
-import org.osmdroid.events.ZoomEvent;
-import org.osmdroid.tileprovider.tilesource.TileSourceFactory;
-import org.osmdroid.util.GeoPoint;
-import org.osmdroid.util.constants.GeoConstants;
-import org.osmdroid.views.CustomZoomButtonsController;
-import org.osmdroid.views.overlay.ItemizedIconOverlay.OnItemGestureListener;
-import org.osmdroid.views.overlay.ItemizedOverlayWithFocus;
-import org.osmdroid.views.overlay.MapEventsOverlay;
-import org.osmdroid.views.overlay.Overlay;
-import org.osmdroid.views.overlay.OverlayItem;
-import org.osmdroid.views.overlay.ScaleBarOverlay;
-import org.osmdroid.views.overlay.ScaleDiskOverlay;
-import org.osmdroid.views.overlay.TilesOverlay;
-import timber.log.Timber;
-
-public class ExploreMapFragment extends CommonsDaggerSupportFragment
- implements ExploreMapContract.View, LocationUpdateListener, LocationPermissionCallback {
-
- private BottomSheetBehavior bottomSheetDetailsBehavior;
- private BroadcastReceiver broadcastReceiver;
- private boolean isNetworkErrorOccurred;
- private Snackbar snackbar;
- private boolean isDarkTheme;
- private boolean isPermissionDenied;
- private fr.free.nrw.commons.location.LatLng lastKnownLocation; // last location of user
- private fr.free.nrw.commons.location.LatLng lastFocusLocation; // last location that map is focused
- public List mediaList;
- private boolean recenterToUserLocation; // true is recenter is needed (ie. when current location is in visible map boundaries)
- private BaseMarker clickedMarker;
- private GeoPoint mapCenter;
- private GeoPoint lastMapFocus;
- IntentFilter intentFilter = new IntentFilter(MapUtils.NETWORK_INTENT_ACTION);
- private Map baseMarkerOverlayMap;
-
- @Inject
- LiveDataConverter liveDataConverter;
- @Inject
- MediaClient mediaClient;
- @Inject
- LocationServiceManager locationManager;
- @Inject
- ExploreMapController exploreMapController;
- @Inject
- @Named("default_preferences")
- JsonKvStore applicationKvStore;
- @Inject
- BookmarkLocationsDao bookmarkLocationDao; // May be needed in future if we want to integrate bookmarking explore places
- @Inject
- SystemThemeUtils systemThemeUtils;
- LocationPermissionsHelper locationPermissionsHelper;
-
- // Nearby map state (if we came from Nearby)
- private double prevZoom;
- private double prevLatitude;
- private double prevLongitude;
- private boolean recentlyCameFromNearbyMap;
-
- private ExploreMapPresenter presenter;
-
- public FragmentExploreMapBinding binding;
-
- private ActivityResultLauncher activityResultLauncher = registerForActivityResult(
- new ActivityResultContracts.RequestPermission(), isGranted -> {
- if (isGranted) {
- locationPermissionGranted();
- } else {
- if (shouldShowRequestPermissionRationale(permission.ACCESS_FINE_LOCATION)) {
- DialogUtil.showAlertDialog(getActivity(),
- getActivity().getString(R.string.location_permission_title),
- getActivity().getString(R.string.location_permission_rationale_explore),
- getActivity().getString(android.R.string.ok),
- getActivity().getString(android.R.string.cancel),
- () -> {
- askForLocationPermission();
- },
- null,
- null
- );
- } else {
- if (isPermissionDenied) {
- locationPermissionsHelper.showAppSettingsDialog(getActivity(),
- R.string.explore_map_needs_location);
- }
- Timber.d("The user checked 'Don't ask again' or denied the permission twice");
- isPermissionDenied = true;
- }
- }
- });
-
- @NonNull
- public static ExploreMapFragment newInstance() {
- ExploreMapFragment fragment = new ExploreMapFragment();
- fragment.setRetainInstance(true);
- return fragment;
- }
-
- @Override
- public View onCreateView(
- @NonNull LayoutInflater inflater,
- ViewGroup container,
- Bundle savedInstanceState
- ) {
- loadNearbyMapData();
- binding = FragmentExploreMapBinding.inflate(getLayoutInflater());
- return binding.getRoot();
- }
-
- @Override
- public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
- setSearchThisAreaButtonVisibility(false);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- binding.tvAttribution.setText(
- Html.fromHtml(getString(R.string.map_attribution), Html.FROM_HTML_MODE_LEGACY));
- } else {
- binding.tvAttribution.setText(Html.fromHtml(getString(R.string.map_attribution)));
- }
- initNetworkBroadCastReceiver();
- locationPermissionsHelper = new LocationPermissionsHelper(getActivity(), locationManager,
- this);
- if (presenter == null) {
- presenter = new ExploreMapPresenter(bookmarkLocationDao);
- }
- setHasOptionsMenu(true);
-
- isDarkTheme = systemThemeUtils.isDeviceInNightMode();
- isPermissionDenied = false;
- presenter.attachView(this);
-
- initViews();
- presenter.setActionListeners(applicationKvStore);
-
- org.osmdroid.config.Configuration.getInstance().load(this.getContext(),
- PreferenceManager.getDefaultSharedPreferences(this.getContext()));
-
- binding.mapView.setTileSource(TileSourceFactory.WIKIMEDIA);
- binding.mapView.setTilesScaledToDpi(true);
-
- org.osmdroid.config.Configuration.getInstance().getAdditionalHttpRequestProperties().put(
- "Referer", "http://maps.wikimedia.org/"
- );
-
- ScaleBarOverlay scaleBarOverlay = new ScaleBarOverlay(binding.mapView);
- scaleBarOverlay.setScaleBarOffset(15, 25);
- Paint barPaint = new Paint();
- barPaint.setARGB(200, 255, 250, 250);
- scaleBarOverlay.setBackgroundPaint(barPaint);
- scaleBarOverlay.enableScaleBar();
- binding.mapView.getOverlays().add(scaleBarOverlay);
- binding.mapView.getZoomController()
- .setVisibility(CustomZoomButtonsController.Visibility.NEVER);
- binding.mapView.setMultiTouchControls(true);
-
- if (!isCameFromNearbyMap()) {
- binding.mapView.getController().setZoom(ZOOM_LEVEL);
- }
-
-
- binding.mapView.getOverlays().add(new MapEventsOverlay(new MapEventsReceiver() {
- @Override
- public boolean singleTapConfirmedHelper(GeoPoint p) {
- if (clickedMarker != null) {
- removeMarker(clickedMarker);
- addMarkerToMap(clickedMarker);
- binding.mapView.invalidate();
- } else {
- Timber.e("CLICKED MARKER IS NULL");
- }
- if (bottomSheetDetailsBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED) {
- // Back should first hide the bottom sheet if it is expanded
- bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
- } else if (isDetailsBottomSheetVisible()) {
- hideBottomDetailsSheet();
- }
- return true;
- }
-
- @Override
- public boolean longPressHelper(GeoPoint p) {
- return false;
- }
- }));
-
- binding.mapView.addMapListener(new MapListener() {
- @Override
- public boolean onScroll(ScrollEvent event) {
- if (getLastMapFocus() != null) {
- Location mylocation = new Location("");
- Location dest_location = new Location("");
- dest_location.setLatitude(binding.mapView.getMapCenter().getLatitude());
- dest_location.setLongitude(binding.mapView.getMapCenter().getLongitude());
- mylocation.setLatitude(getLastMapFocus().getLatitude());
- mylocation.setLongitude(getLastMapFocus().getLongitude());
- Float distance = mylocation.distanceTo(dest_location);//in meters
- if (getLastMapFocus() != null) {
- if (isNetworkConnectionEstablished() && (event.getX() > 0
- || event.getY() > 0)) {
- if (distance > 2000.0) {
- setSearchThisAreaButtonVisibility(true);
- } else {
- setSearchThisAreaButtonVisibility(false);
- }
- }
- } else {
- setSearchThisAreaButtonVisibility(false);
- }
- }
-
- return true;
- }
-
- @Override
- public boolean onZoom(ZoomEvent event) {
- return false;
- }
-
- });
- if (!locationPermissionsHelper.checkLocationPermission(getActivity())) {
- askForLocationPermission();
- }
- }
-
- @Override
- public void onResume() {
- super.onResume();
- binding.mapView.onResume();
- presenter.attachView(this);
- registerNetworkReceiver();
- if (isResumed()) {
- if (locationPermissionsHelper.checkLocationPermission(getActivity())) {
- performMapReadyActions();
- } else {
- startMapWithoutPermission();
- }
- }
- }
-
- @Override
- public void onPause() {
- super.onPause();
- // unregistering the broadcastReceiver, as it was causing an exception and a potential crash
- unregisterNetworkReceiver();
- }
-
-
- /**
- * Unregisters the networkReceiver
- */
- private void unregisterNetworkReceiver() {
- if (getActivity() != null) {
- getActivity().unregisterReceiver(broadcastReceiver);
- }
- }
-
- private void startMapWithoutPermission() {
- lastKnownLocation = MapUtils.getDefaultLatLng();
- moveCameraToPosition(
- new GeoPoint(lastKnownLocation.getLatitude(), lastKnownLocation.getLongitude()));
- presenter.onMapReady(exploreMapController);
- }
-
- private void registerNetworkReceiver() {
- if (getActivity() != null) {
- getActivity().registerReceiver(broadcastReceiver, intentFilter);
- }
- }
-
- private void performMapReadyActions() {
- if (isDarkTheme) {
- binding.mapView.getOverlayManager().getTilesOverlay()
- .setColorFilter(TilesOverlay.INVERT_COLORS);
- }
- if (applicationKvStore.getBoolean("doNotAskForLocationPermission", false) &&
- !locationPermissionsHelper.checkLocationPermission(getActivity())) {
- isPermissionDenied = true;
- }
-
- lastKnownLocation = getLastLocation();
-
- if (lastKnownLocation == null) {
- lastKnownLocation = MapUtils.getDefaultLatLng();
- }
-
- // if we came from 'Show in Explore' in Nearby, load Nearby map center and zoom
- if (isCameFromNearbyMap()) {
- moveCameraToPosition(
- new GeoPoint(prevLatitude, prevLongitude),
- prevZoom,
- 1L
- );
- } else {
- moveCameraToPosition(
- new GeoPoint(lastKnownLocation.getLatitude(), lastKnownLocation.getLongitude()));
- }
- presenter.onMapReady(exploreMapController);
- }
-
- /**
- * Fetch Nearby map camera data from fragment arguments if any.
- */
- public void loadNearbyMapData() {
- // get fragment arguments
- if (getArguments() != null) {
- prevZoom = getArguments().getDouble("prev_zoom");
- prevLatitude = getArguments().getDouble("prev_latitude");
- prevLongitude = getArguments().getDouble("prev_longitude");
- }
-
- setRecentlyCameFromNearbyMap(isCameFromNearbyMap());
- }
-
- /**
- * @return The LatLng from the previous Fragment's map center or (0,0,0) coordinates
- * if that information is not available/applicable.
- */
- public LatLng getPreviousLatLng() {
- return new LatLng(prevLatitude, prevLongitude, (float)prevZoom);
- }
-
- /**
- * Checks if fragment arguments contain data from Nearby map, indicating that the user navigated
- * from Nearby using 'Show in Explore'.
- *
- * @return true if user navigated from Nearby map
- **/
- public boolean isCameFromNearbyMap() {
- return prevZoom != 0.0 || prevLatitude != 0.0 || prevLongitude != 0.0;
- }
-
- /**
- * Gets the value that indicates if the user navigated from "Show in Explore" in Nearby and
- * that the LatLng from Nearby has yet to be searched for map markers.
- */
- public boolean recentlyCameFromNearbyMap() {
- return recentlyCameFromNearbyMap;
- }
-
- /**
- * Sets the value that indicates if the user navigated from "Show in Explore" in Nearby and
- * that the LatLng from Nearby has yet to be searched for map markers.
- * @param newValue The value to set.
- */
- public void setRecentlyCameFromNearbyMap(boolean newValue) {
- recentlyCameFromNearbyMap = newValue;
- }
-
- public void loadNearbyMapFromExplore() {
- ((MainActivity) getContext()).loadNearbyMapFromExplore(
- binding.mapView.getZoomLevelDouble(),
- binding.mapView.getMapCenter().getLatitude(),
- binding.mapView.getMapCenter().getLongitude()
- );
- }
-
- private void initViews() {
- Timber.d("init views called");
- initBottomSheets();
- setBottomSheetCallbacks();
- }
-
- /**
- * a) Creates bottom sheet behaviours from bottom sheet, sets initial states and visibility
- * b) Gets the touch event on the map to perform following actions:
- * if bottom sheet details are expanded or collapsed hide the bottom sheet details.
- */
- @SuppressLint("ClickableViewAccessibility")
- private void initBottomSheets() {
- bottomSheetDetailsBehavior = BottomSheetBehavior.from(
- binding.bottomSheetDetailsBinding.getRoot());
- bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
- binding.bottomSheetDetailsBinding.getRoot().setVisibility(View.VISIBLE);
- }
-
- /**
- * Defines how bottom sheets will act on click
- */
- private void setBottomSheetCallbacks() {
- binding.bottomSheetDetailsBinding.getRoot().setOnClickListener(v -> {
- if (bottomSheetDetailsBehavior.getState() == BottomSheetBehavior.STATE_COLLAPSED) {
- bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
- } else if (bottomSheetDetailsBehavior.getState()
- == BottomSheetBehavior.STATE_EXPANDED) {
- bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
- }
- });
- }
-
- @Override
- public void onLocationChangedSignificantly(LatLng latLng) {
- Timber.d("Location significantly changed");
- if (latLng != null) {
- handleLocationUpdate(latLng, LOCATION_SIGNIFICANTLY_CHANGED);
- }
- }
-
- @Override
- public void onLocationChangedSlightly(LatLng latLng) {
- Timber.d("Location slightly changed");
- if (latLng != null) {//If the map has never ever shown the current location, lets do it know
- handleLocationUpdate(latLng, LOCATION_SLIGHTLY_CHANGED);
- }
- }
-
- private void handleLocationUpdate(final fr.free.nrw.commons.location.LatLng latLng,
- final LocationServiceManager.LocationChangeType locationChangeType) {
- lastKnownLocation = latLng;
- exploreMapController.currentLocation = lastKnownLocation;
- presenter.updateMap(locationChangeType);
- }
-
- @Override
- public void onLocationChangedMedium(LatLng latLng) {
-
- }
-
- @Override
- public boolean isNetworkConnectionEstablished() {
- return NetworkUtils.isInternetConnectionEstablished(getActivity());
- }
-
- @Override
- public void populatePlaces(LatLng currentLatLng) {
- final Observable nearbyPlacesInfoObservable;
- if (currentLatLng == null) {
- return;
- }
- if (currentLatLng.equals(
- getLastMapFocus())) { // Means we are checking around current location
- nearbyPlacesInfoObservable = presenter.loadAttractionsFromLocation(currentLatLng,
- getLastMapFocus(), true);
- } else {
- nearbyPlacesInfoObservable = presenter.loadAttractionsFromLocation(getLastMapFocus(),
- currentLatLng, false);
- }
- getCompositeDisposable().add(nearbyPlacesInfoObservable
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(explorePlacesInfo -> {
- mediaList = explorePlacesInfo.mediaList;
- if (mediaList == null) {
- showResponseMessage(getString(R.string.no_pictures_in_this_area));
- }
- updateMapMarkers(explorePlacesInfo);
- lastMapFocus = new GeoPoint(currentLatLng.getLatitude(),
- currentLatLng.getLongitude());
- },
- throwable -> {
- Timber.d(throwable);
- // Not showing the user, throwable localizedErrorMessage
- showErrorMessage(getString(R.string.error_fetching_nearby_places));
-
- setProgressBarVisibility(false);
- presenter.lockUnlockNearby(false);
- }));
- if (recenterToUserLocation) {
- recenterToUserLocation = false;
- }
- }
-
- /**
- * Updates map markers according to latest situation
- *
- * @param explorePlacesInfo holds several information as current location, marker list etc.
- */
- private void updateMapMarkers(final MapController.ExplorePlacesInfo explorePlacesInfo) {
- presenter.updateMapMarkers(explorePlacesInfo);
- }
-
- private void showErrorMessage(final String message) {
- ViewUtil.showLongToast(getActivity(), message);
- }
-
- private void showResponseMessage(final String message) {
- ViewUtil.showLongSnackbar(getView(), message);
- }
-
- @Override
- public void askForLocationPermission() {
- Timber.d("Asking for location permission");
- activityResultLauncher.launch(permission.ACCESS_FINE_LOCATION);
- }
-
- private void locationPermissionGranted() {
- isPermissionDenied = false;
- applicationKvStore.putBoolean("doNotAskForLocationPermission", false);
- lastKnownLocation = locationManager.getLastLocation();
- fr.free.nrw.commons.location.LatLng target = lastKnownLocation;
- if (lastKnownLocation != null) {
- GeoPoint targetP = new GeoPoint(target.getLatitude(), target.getLongitude());
- mapCenter = targetP;
- binding.mapView.getController().setCenter(targetP);
- recenterMarkerToPosition(targetP);
- moveCameraToPosition(targetP);
- } else if (locationManager.isGPSProviderEnabled()
- || locationManager.isNetworkProviderEnabled()) {
- locationManager.requestLocationUpdatesFromProvider(LocationManager.NETWORK_PROVIDER);
- locationManager.requestLocationUpdatesFromProvider(LocationManager.GPS_PROVIDER);
- setProgressBarVisibility(true);
- } else {
- locationPermissionsHelper.showLocationOffDialog(getActivity(),
- R.string.ask_to_turn_location_on_text);
- }
- presenter.onMapReady(exploreMapController);
- registerUnregisterLocationListener(false);
- }
-
- public void registerUnregisterLocationListener(final boolean removeLocationListener) {
- MapUtils.registerUnregisterLocationListener(removeLocationListener, locationManager, this);
- }
-
- @Override
- public void recenterMap(LatLng currentLatLng) {
- // if user has denied permission twice, then show dialog
- if (isPermissionDenied) {
- if (locationPermissionsHelper.checkLocationPermission(getActivity())) {
- // this will run when user has given permission by opening app's settings
- isPermissionDenied = false;
- recenterMap(currentLatLng);
- } else {
- askForLocationPermission();
- }
- } else {
- if (!locationPermissionsHelper.checkLocationPermission(getActivity())) {
- askForLocationPermission();
- } else {
- locationPermissionGranted();
- }
- }
- if (currentLatLng == null) {
- recenterToUserLocation = true;
- return;
- }
- recenterMarkerToPosition(
- new GeoPoint(currentLatLng.getLatitude(), currentLatLng.getLongitude()));
- binding.mapView.getController()
- .animateTo(new GeoPoint(currentLatLng.getLatitude(), currentLatLng.getLongitude()));
- if (lastMapFocus != null) {
- Location mylocation = new Location("");
- Location dest_location = new Location("");
- dest_location.setLatitude(binding.mapView.getMapCenter().getLatitude());
- dest_location.setLongitude(binding.mapView.getMapCenter().getLongitude());
- mylocation.setLatitude(lastMapFocus.getLatitude());
- mylocation.setLongitude(lastMapFocus.getLongitude());
- Float distance = mylocation.distanceTo(dest_location);//in meters
- if (lastMapFocus != null) {
- if (isNetworkConnectionEstablished()) {
- if (distance > 2000.0) {
- setSearchThisAreaButtonVisibility(true);
- } else {
- setSearchThisAreaButtonVisibility(false);
- }
- }
- } else {
- setSearchThisAreaButtonVisibility(false);
- }
- }
- }
-
- @Override
- public void hideBottomDetailsSheet() {
- bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
- }
-
- /**
- * Same bottom sheet carries information for all nearby places, so we need to pass information
- * (title, description, distance and links) to view on nearby marker click
- *
- * @param place Place of clicked nearby marker
- */
- private void passInfoToSheet(final Place place) {
- binding.bottomSheetDetailsBinding.directionsButton.setOnClickListener(
- view -> handleGeoCoordinates(requireActivity(),
- place.getLocation(), binding.mapView.getZoomLevelDouble()));
-
- binding.bottomSheetDetailsBinding.commonsButton.setVisibility(
- place.hasCommonsLink() ? View.VISIBLE : View.GONE);
- binding.bottomSheetDetailsBinding.commonsButton.setOnClickListener(
- view -> handleWebUrl(getContext(), place.siteLinks.getCommonsLink()));
-
- int index = 0;
- for (Media media : mediaList) {
- if (media.getFilename().equals(place.name)) {
- int finalIndex = index;
- binding.bottomSheetDetailsBinding.mediaDetailsButton.setOnClickListener(view -> {
- ((ExploreMapRootFragment) getParentFragment()).onMediaClicked(finalIndex);
- });
- }
- index++;
- }
- binding.bottomSheetDetailsBinding.title.setText(
- place.name.substring(5, place.name.lastIndexOf(".")));
- binding.bottomSheetDetailsBinding.category.setText(place.distance);
- // Remove label since it is double information
- String descriptionText = place.getLongDescription()
- .replace(place.getName() + " (", "");
- descriptionText = (descriptionText.equals(place.getLongDescription()) ? descriptionText
- : descriptionText.replaceFirst(".$", ""));
- // Set the short description after we remove place name from long description
- binding.bottomSheetDetailsBinding.description.setText(descriptionText);
- }
-
- @Override
- public void addSearchThisAreaButtonAction() {
- binding.searchThisAreaButton.setOnClickListener(presenter.onSearchThisAreaClicked());
- }
-
- @Override
- public void setSearchThisAreaButtonVisibility(boolean isVisible) {
- binding.searchThisAreaButton.setVisibility(isVisible ? View.VISIBLE : View.GONE);
- }
-
- @Override
- public void setProgressBarVisibility(boolean isVisible) {
- binding.mapProgressBar.setVisibility(isVisible ? View.VISIBLE : View.GONE);
- }
-
- @Override
- public boolean isDetailsBottomSheetVisible() {
- if (binding.bottomSheetDetailsBinding.getRoot().getVisibility() == View.VISIBLE) {
- return true;
- } else {
- return false;
- }
- }
-
- @Override
- public boolean isSearchThisAreaButtonVisible() {
- return binding.bottomSheetDetailsBinding.getRoot().getVisibility() == View.VISIBLE;
- }
-
- @Override
- public LatLng getLastLocation() {
- if (lastKnownLocation == null) {
- lastKnownLocation = locationManager.getLastLocation();
- }
- return lastKnownLocation;
- }
-
- @Override
- public void disableFABRecenter() {
- binding.fabRecenter.setEnabled(false);
- }
-
- @Override
- public void enableFABRecenter() {
- binding.fabRecenter.setEnabled(true);
- }
-
- /**
- * Adds a markers to the map based on the list of NearbyBaseMarker.
- *
- * @param nearbyBaseMarkers The NearbyBaseMarker object representing the markers to be added.
- */
- @Override
- public void addMarkersToMap(List nearbyBaseMarkers) {
- clearAllMarkers();
- for (int i = 0; i < nearbyBaseMarkers.size(); i++) {
- addMarkerToMap(nearbyBaseMarkers.get(i));
- }
- binding.mapView.invalidate();
- }
-
- /**
- * Adds a marker to the map based on the specified NearbyBaseMarker.
- *
- * @param nearbyBaseMarker The NearbyBaseMarker object representing the marker to be added.
- */
- private void addMarkerToMap(BaseMarker nearbyBaseMarker) {
- if (isAttachedToActivity()) {
- ArrayList items = new ArrayList<>();
- Bitmap icon = nearbyBaseMarker.getIcon();
- Drawable d = new BitmapDrawable(getResources(), icon);
- GeoPoint point = new GeoPoint(
- nearbyBaseMarker.getPlace().location.getLatitude(),
- nearbyBaseMarker.getPlace().location.getLongitude());
-
- Media markerMedia = this.getMediaFromImageURL(nearbyBaseMarker.getPlace().pic);
- String authorUser = null;
- if (markerMedia != null) {
- authorUser = markerMedia.getAuthorOrUser();
- // HTML text is sometimes part of the author string and needs to be removed
- authorUser = Html.fromHtml(authorUser, Html.FROM_HTML_MODE_LEGACY).toString();
- }
-
- String title = nearbyBaseMarker.getPlace().name;
- // Remove "File:" if present at start
- if (title.startsWith("File:")) {
- title = title.substring(5);
- }
- // Remove extensions like .jpg, .jpeg, .png, .svg (case insensitive)
- title = title.replaceAll("(?i)\\.(jpg|jpeg|png|svg)$", "");
- title = title.replace("_", " ");
- //Truncate if too long because it doesn't fit the screen
- if (title.length() > 43) {
- title = title.substring(0, 40) + "…";
- }
-
- OverlayItem item = new OverlayItem(title, authorUser, point);
- item.setMarker(d);
- items.add(item);
- ItemizedOverlayWithFocus overlay = new ItemizedOverlayWithFocus(items,
- new OnItemGestureListener() {
- @Override
- public boolean onItemSingleTapUp(int index, OverlayItem item) {
- final Place place = nearbyBaseMarker.getPlace();
- if (clickedMarker != null) {
- removeMarker(clickedMarker);
- addMarkerToMap(clickedMarker);
- bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
- bottomSheetDetailsBehavior.setState(
- BottomSheetBehavior.STATE_COLLAPSED);
- }
- clickedMarker = nearbyBaseMarker;
- passInfoToSheet(place);
-
- //Move the overlay to the top so it can be fully seen.
- moveOverlayToTop(getOverlay(item));
- return true;
- }
-
- @Override
- public boolean onItemLongPress(int index, OverlayItem item) {
- return false;
- }
- }, getContext());
-
- if (this.baseMarkerOverlayMap == null) {
- this.baseMarkerOverlayMap = new HashMap<>();
- }
- this.baseMarkerOverlayMap.put(nearbyBaseMarker, overlay);
-
- overlay.setFocusItemsOnTap(true);
- binding.mapView.getOverlays().add(overlay); // Add the overlay to the map
- }
- }
-
- /**
- * Moves the specified Overlay above all other Overlays. This prevents other Overlays from
- * obstructing it. Upon failure, this method returns early.
- * @param overlay The Overlay to move.
- */
- private void moveOverlayToTop (Overlay overlay) {
- if (overlay == null || binding == null || binding.mapView.getOverlays() == null) {
- return;
- }
-
- boolean successfulRemoval = binding.mapView.getOverlays().remove(overlay);
- if (!successfulRemoval) {
- return;
- }
-
- binding.mapView.getOverlays().add(overlay);
- }
-
- /**
- * Performs a linear search for the first Overlay which contains the specified OverlayItem.
- *
- * @param item The OverlayItem contained within the first target Overlay.
- * @return The first Overlay which contains the specified OverlayItem or null if the Overlay
- * could not be found.
- */
- private Overlay getOverlay (OverlayItem item) {
- if (item == null || binding == null || binding.mapView.getOverlays() == null) {
- return null;
- }
-
- for (int i = 0; i < binding.mapView.getOverlays().size(); i++) {
- if (binding.mapView.getOverlays().get(i) instanceof ItemizedOverlayWithFocus) {
- ItemizedOverlayWithFocus overlay =
- (ItemizedOverlayWithFocus)binding.mapView.getOverlays().get(i);
-
- for (int j = 0; j < overlay.size(); j++) {
- if (overlay.getItem(j) == item) {
- return overlay;
- }
- }
- }
- }
-
- return null;
- }
-
- /**
- * Retrieves the specific Media object from the mediaList field.
- * @param url The specific Media's image URL.
- * @return The Media object that matches the URL or null if it could not be found.
- */
- private Media getMediaFromImageURL(String url) {
- if (mediaList == null || url == null) {
- return null;
- }
-
- for (int i = 0; i < mediaList.size(); i++) {
- if (mediaList.get(i) != null && mediaList.get(i).getImageUrl() != null
- && mediaList.get(i).getImageUrl().equals(url)) {
- return mediaList.get(i);
- }
- }
-
- return null;
- }
-
- /**
- * Removes a marker from the map based on the specified NearbyBaseMarker.
- *
- * @param nearbyBaseMarker The NearbyBaseMarker object representing the marker to be removed.
- */
- private void removeMarker(BaseMarker nearbyBaseMarker) {
- if (nearbyBaseMarker == null || nearbyBaseMarker.getPlace().getName() == null ||
- baseMarkerOverlayMap == null || !baseMarkerOverlayMap.containsKey(nearbyBaseMarker)) {
- return;
- }
-
- Overlay target = baseMarkerOverlayMap.get(nearbyBaseMarker);
- List overlays = binding.mapView.getOverlays();
-
- for (int i = 0; i < overlays.size(); i++) {
- Overlay overlay = overlays.get(i);
-
- if (overlay.equals(target)) {
- binding.mapView.getOverlays().remove(i);
- binding.mapView.invalidate();
- baseMarkerOverlayMap.remove(nearbyBaseMarker);
- break;
- }
- }
- }
-
- /**
- * Clears all markers from the map and resets certain map overlays and gestures. After clearing
- * markers, it re-adds a scale bar overlay and rotation gesture overlay to the map.
- */
- @Override
- public void clearAllMarkers() {
- if (isAttachedToActivity()) {
- binding.mapView.getOverlayManager().clear();
- GeoPoint geoPoint = mapCenter;
- if (geoPoint != null) {
- List overlays = binding.mapView.getOverlays();
- ScaleDiskOverlay diskOverlay =
- new ScaleDiskOverlay(this.getContext(),
- geoPoint, 2000, GeoConstants.UnitOfMeasure.foot);
- Paint circlePaint = new Paint();
- circlePaint.setColor(Color.rgb(128, 128, 128));
- circlePaint.setStyle(Paint.Style.STROKE);
- circlePaint.setStrokeWidth(2f);
- diskOverlay.setCirclePaint2(circlePaint);
- Paint diskPaint = new Paint();
- diskPaint.setColor(Color.argb(40, 128, 128, 128));
- diskPaint.setStyle(Paint.Style.FILL_AND_STROKE);
- diskOverlay.setCirclePaint1(diskPaint);
- diskOverlay.setDisplaySizeMin(900);
- diskOverlay.setDisplaySizeMax(1700);
- binding.mapView.getOverlays().add(diskOverlay);
- org.osmdroid.views.overlay.Marker startMarker = new org.osmdroid.views.overlay.Marker(
- binding.mapView);
- startMarker.setPosition(geoPoint);
- startMarker.setAnchor(org.osmdroid.views.overlay.Marker.ANCHOR_CENTER,
- org.osmdroid.views.overlay.Marker.ANCHOR_BOTTOM);
- startMarker.setIcon(
- ContextCompat.getDrawable(this.getContext(),
- R.drawable.current_location_marker));
- startMarker.setTitle("Your Location");
- startMarker.setTextLabelFontSize(24);
- binding.mapView.getOverlays().add(startMarker);
- }
- ScaleBarOverlay scaleBarOverlay = new ScaleBarOverlay(binding.mapView);
- scaleBarOverlay.setScaleBarOffset(15, 25);
- Paint barPaint = new Paint();
- barPaint.setARGB(200, 255, 250, 250);
- scaleBarOverlay.setBackgroundPaint(barPaint);
- scaleBarOverlay.enableScaleBar();
- binding.mapView.getOverlays().add(scaleBarOverlay);
- binding.mapView.getOverlays().add(new MapEventsOverlay(new MapEventsReceiver() {
- @Override
- public boolean singleTapConfirmedHelper(GeoPoint p) {
- if (clickedMarker != null) {
- removeMarker(clickedMarker);
- addMarkerToMap(clickedMarker);
- binding.mapView.invalidate();
- } else {
- Timber.e("CLICKED MARKER IS NULL");
- }
- if (bottomSheetDetailsBehavior.getState()
- == BottomSheetBehavior.STATE_EXPANDED) {
- // Back should first hide the bottom sheet if it is expanded
- bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
- } else if (isDetailsBottomSheetVisible()) {
- hideBottomDetailsSheet();
- }
- return true;
- }
-
- @Override
- public boolean longPressHelper(GeoPoint p) {
- return false;
- }
- }));
- binding.mapView.setMultiTouchControls(true);
- }
- }
-
- /**
- * Recenters the map view to the specified GeoPoint and updates the marker to indicate the new
- * position.
- *
- * @param geoPoint The GeoPoint representing the new center position for the map.
- */
- private void recenterMarkerToPosition(GeoPoint geoPoint) {
- if (geoPoint != null) {
- binding.mapView.getController().setCenter(geoPoint);
- List overlays = binding.mapView.getOverlays();
- for (int i = 0; i < overlays.size(); i++) {
- if (overlays.get(i) instanceof org.osmdroid.views.overlay.Marker) {
- binding.mapView.getOverlays().remove(i);
- } else if (overlays.get(i) instanceof ScaleDiskOverlay) {
- binding.mapView.getOverlays().remove(i);
- }
- }
- ScaleDiskOverlay diskOverlay =
- new ScaleDiskOverlay(this.getContext(),
- geoPoint, 2000, GeoConstants.UnitOfMeasure.foot);
- Paint circlePaint = new Paint();
- circlePaint.setColor(Color.rgb(128, 128, 128));
- circlePaint.setStyle(Paint.Style.STROKE);
- circlePaint.setStrokeWidth(2f);
- diskOverlay.setCirclePaint2(circlePaint);
- Paint diskPaint = new Paint();
- diskPaint.setColor(Color.argb(40, 128, 128, 128));
- diskPaint.setStyle(Paint.Style.FILL_AND_STROKE);
- diskOverlay.setCirclePaint1(diskPaint);
- diskOverlay.setDisplaySizeMin(900);
- diskOverlay.setDisplaySizeMax(1700);
- binding.mapView.getOverlays().add(diskOverlay);
- org.osmdroid.views.overlay.Marker startMarker = new org.osmdroid.views.overlay.Marker(
- binding.mapView);
- startMarker.setPosition(geoPoint);
- startMarker.setAnchor(org.osmdroid.views.overlay.Marker.ANCHOR_CENTER,
- org.osmdroid.views.overlay.Marker.ANCHOR_BOTTOM);
- startMarker.setIcon(
- ContextCompat.getDrawable(this.getContext(), R.drawable.current_location_marker));
- startMarker.setTitle("Your Location");
- startMarker.setTextLabelFontSize(24);
- binding.mapView.getOverlays().add(startMarker);
- }
- }
-
- /**
- * Moves the camera of the map view to the specified GeoPoint using an animation.
- *
- * @param geoPoint The GeoPoint representing the new camera position for the map.
- */
- private void moveCameraToPosition(GeoPoint geoPoint) {
- binding.mapView.getController().animateTo(geoPoint);
- }
-
- /**
- * Moves the camera of the map view to the specified GeoPoint at specified zoom level and speed
- * using an animation.
- *
- * @param geoPoint The GeoPoint representing the new camera position for the map.
- * @param zoom Zoom level of the map camera
- * @param speed Speed of animation
- */
- private void moveCameraToPosition(GeoPoint geoPoint, double zoom, long speed) {
- binding.mapView.getController().animateTo(geoPoint, zoom, speed);
- }
-
- @Override
- public fr.free.nrw.commons.location.LatLng getLastMapFocus() {
- return lastMapFocus == null ? getMapCenter() : new fr.free.nrw.commons.location.LatLng(
- lastMapFocus.getLatitude(), lastMapFocus.getLongitude(), 100);
- }
-
- @Override
- public fr.free.nrw.commons.location.LatLng getMapCenter() {
- fr.free.nrw.commons.location.LatLng latLnge = null;
- if (mapCenter != null) {
- latLnge = new fr.free.nrw.commons.location.LatLng(
- mapCenter.getLatitude(), mapCenter.getLongitude(), 100);
- } else {
- if (applicationKvStore.getString("LastLocation") != null) {
- final String[] locationLatLng
- = applicationKvStore.getString("LastLocation").split(",");
- lastKnownLocation
- = new fr.free.nrw.commons.location.LatLng(Double.parseDouble(locationLatLng[0]),
- Double.parseDouble(locationLatLng[1]), 1f);
- latLnge = lastKnownLocation;
- } else {
- latLnge = new fr.free.nrw.commons.location.LatLng(51.506255446947776,
- -0.07483536015053005, 1f);
- }
- }
- return latLnge;
- }
-
- @Override
- public fr.free.nrw.commons.location.LatLng getMapFocus() {
- fr.free.nrw.commons.location.LatLng mapFocusedLatLng = new fr.free.nrw.commons.location.LatLng(
- binding.mapView.getMapCenter().getLatitude(),
- binding.mapView.getMapCenter().getLongitude(), 100);
- return mapFocusedLatLng;
- }
-
- @Override
- public void setFABRecenterAction(OnClickListener onClickListener) {
- binding.fabRecenter.setOnClickListener(onClickListener);
- }
-
- @Override
- public boolean backButtonClicked() {
- if (!(bottomSheetDetailsBehavior.getState() == BottomSheetBehavior.STATE_HIDDEN)) {
- bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
- return true;
- } else {
- return false;
- }
- }
-
- /**
- * Adds network broadcast receiver to recognize connection established
- */
- private void initNetworkBroadCastReceiver() {
- broadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(final Context context, final Intent intent) {
- if (getActivity() != null) {
- if (NetworkUtils.isInternetConnectionEstablished(getActivity())) {
- if (isNetworkErrorOccurred) {
- presenter.updateMap(LOCATION_SIGNIFICANTLY_CHANGED);
- isNetworkErrorOccurred = false;
- }
-
- if (snackbar != null) {
- snackbar.dismiss();
- snackbar = null;
- }
- } else {
- if (snackbar == null) {
- snackbar = Snackbar.make(getView(), R.string.no_internet,
- Snackbar.LENGTH_INDEFINITE);
- setSearchThisAreaButtonVisibility(false);
- setProgressBarVisibility(false);
- }
-
- isNetworkErrorOccurred = true;
- snackbar.show();
- }
- }
- }
- };
- }
-
- /**
- * helper function to confirm that this fragment has been attached.
- **/
- public boolean isAttachedToActivity() {
- boolean attached = isVisible() && getActivity() != null;
- return attached;
- }
-
- @Override
- public void onLocationPermissionDenied(String toastMessage) {
- }
-
- @Override
- public void onLocationPermissionGranted() {
- }
-}
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.kt b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.kt
new file mode 100644
index 000000000..a1bae09fb
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.kt
@@ -0,0 +1,1139 @@
+@file:Suppress("DEPRECATION")
+
+package fr.free.nrw.commons.explore.map
+
+import android.Manifest.permission
+import android.annotation.SuppressLint
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.graphics.Color
+import android.graphics.Paint
+import android.graphics.drawable.Drawable
+import android.location.Location
+import android.location.LocationManager
+import android.os.Build
+import android.os.Bundle
+import android.preference.PreferenceManager
+import android.text.Html
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.LinearLayout
+import androidx.activity.result.contract.ActivityResultContracts.RequestPermission
+import androidx.core.content.ContextCompat
+import androidx.core.graphics.drawable.toDrawable
+import androidx.core.view.isVisible
+import com.google.android.material.bottomsheet.BottomSheetBehavior
+import com.google.android.material.snackbar.Snackbar
+import fr.free.nrw.commons.BaseMarker
+import fr.free.nrw.commons.MapController.ExplorePlacesInfo
+import fr.free.nrw.commons.Media
+import fr.free.nrw.commons.R
+import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao
+import fr.free.nrw.commons.contributions.MainActivity
+import fr.free.nrw.commons.databinding.FragmentExploreMapBinding
+import fr.free.nrw.commons.di.CommonsDaggerSupportFragment
+import fr.free.nrw.commons.explore.ExploreMapRootFragment
+import fr.free.nrw.commons.explore.paging.LiveDataConverter
+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.LocationPermissionCallback
+import fr.free.nrw.commons.location.LocationServiceManager
+import fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType
+import fr.free.nrw.commons.location.LocationUpdateListener
+import fr.free.nrw.commons.media.MediaClient
+import fr.free.nrw.commons.nearby.Place
+import fr.free.nrw.commons.utils.DialogUtil.showAlertDialog
+import fr.free.nrw.commons.utils.MapUtils
+import fr.free.nrw.commons.utils.MapUtils.ZOOM_LEVEL
+import fr.free.nrw.commons.utils.MapUtils.defaultLatLng
+import fr.free.nrw.commons.utils.NetworkUtils.isInternetConnectionEstablished
+import fr.free.nrw.commons.utils.SystemThemeUtils
+import fr.free.nrw.commons.utils.ViewUtil.showLongSnackbar
+import fr.free.nrw.commons.utils.ViewUtil.showLongToast
+import fr.free.nrw.commons.utils.handleGeoCoordinates
+import fr.free.nrw.commons.utils.handleWebUrl
+import io.reactivex.Observable
+import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.functions.Consumer
+import io.reactivex.schedulers.Schedulers
+import org.osmdroid.config.Configuration
+import org.osmdroid.events.MapEventsReceiver
+import org.osmdroid.events.MapListener
+import org.osmdroid.events.ScrollEvent
+import org.osmdroid.events.ZoomEvent
+import org.osmdroid.tileprovider.tilesource.TileSourceFactory
+import org.osmdroid.util.GeoPoint
+import org.osmdroid.util.constants.GeoConstants
+import org.osmdroid.views.CustomZoomButtonsController
+import org.osmdroid.views.overlay.ItemizedIconOverlay.OnItemGestureListener
+import org.osmdroid.views.overlay.ItemizedOverlayWithFocus
+import org.osmdroid.views.overlay.MapEventsOverlay
+import org.osmdroid.views.overlay.Marker
+import org.osmdroid.views.overlay.Overlay
+import org.osmdroid.views.overlay.OverlayItem
+import org.osmdroid.views.overlay.ScaleBarOverlay
+import org.osmdroid.views.overlay.ScaleDiskOverlay
+import org.osmdroid.views.overlay.TilesOverlay
+import timber.log.Timber
+import javax.inject.Inject
+import javax.inject.Named
+
+class ExploreMapFragment : CommonsDaggerSupportFragment(), ExploreMapContract.View,
+ LocationUpdateListener, LocationPermissionCallback {
+ private var bottomSheetDetailsBehavior: BottomSheetBehavior<*>? = null
+ private var broadcastReceiver: BroadcastReceiver? = null
+ private var isNetworkErrorOccurred = false
+ private var snackbar: Snackbar? = null
+ private var isDarkTheme = false
+ private var isPermissionDenied = false
+ private var lastKnownLocation: LatLng? = null // last location of user
+ private var recenterToUserLocation = false // true is recenter is needed (ie. when current location is in visible map boundaries)
+ private var clickedMarker: BaseMarker? = null
+ private var mapCenter: GeoPoint? = null
+ private var lastMapFocus: GeoPoint? = null
+ private var intentFilter: IntentFilter = IntentFilter(MapUtils.NETWORK_INTENT_ACTION)
+ private var baseMarkerOverlayMap: MutableMap? = null
+ private var locationPermissionsHelper: LocationPermissionsHelper? = null
+ private var prevZoom = 0.0
+ private var prevLatitude = 0.0
+ private var prevLongitude = 0.0
+ private var recentlyCameFromNearbyMap = false
+ private var presenter: ExploreMapPresenter? = null
+ private var binding: FragmentExploreMapBinding? = null
+ var mediaList: MutableList? = null
+ private set
+
+ @Inject
+ lateinit var liveDataConverter: LiveDataConverter
+
+ @Inject
+ lateinit var mediaClient: MediaClient
+
+ @Inject
+ lateinit var locationManager: LocationServiceManager
+
+ @Inject
+ lateinit var exploreMapController: ExploreMapController
+
+ @Inject
+ @Named("default_preferences")
+ lateinit var applicationKvStore: JsonKvStore
+
+ @Inject
+ lateinit var bookmarkLocationDao: BookmarkLocationsDao // May be needed in future if we want to integrate bookmarking explore places
+
+ @Inject
+ lateinit var systemThemeUtils: SystemThemeUtils
+
+ private val activityResultLauncher = registerForActivityResult(
+ RequestPermission()
+ ) { isGranted: Boolean? ->
+ if (isGranted == true) {
+ locationPermissionGranted()
+ } else {
+ if (shouldShowRequestPermissionRationale(permission.ACCESS_FINE_LOCATION)) {
+ showAlertDialog(
+ requireActivity(),
+ requireActivity().getString(R.string.location_permission_title),
+ requireActivity().getString(R.string.location_permission_rationale_explore),
+ requireActivity().getString(R.string.ok),
+ requireActivity().getString(R.string.cancel),
+ { askForLocationPermission() },
+ null,
+ null
+ )
+ } else {
+ if (isPermissionDenied) {
+ locationPermissionsHelper!!.showAppSettingsDialog(
+ requireActivity(),
+ R.string.explore_map_needs_location
+ )
+ }
+ Timber.d("The user checked 'Don't ask again' or denied the permission twice")
+ isPermissionDenied = true
+ }
+ }
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ loadNearbyMapData()
+ binding = FragmentExploreMapBinding.inflate(getLayoutInflater())
+ return binding!!.getRoot()
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ setSearchThisAreaButtonVisibility(false)
+ binding!!.tvAttribution.text = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ Html.fromHtml(getString(R.string.map_attribution), Html.FROM_HTML_MODE_LEGACY)
+ } else {
+ Html.fromHtml(getString(R.string.map_attribution))
+ }
+ initNetworkBroadCastReceiver()
+ locationPermissionsHelper = LocationPermissionsHelper(
+ requireActivity(), locationManager,
+ this
+ )
+ if (presenter == null) {
+ presenter = ExploreMapPresenter(bookmarkLocationDao)
+ }
+ setHasOptionsMenu(true)
+
+ isDarkTheme = systemThemeUtils.isDeviceInNightMode()
+ isPermissionDenied = false
+ presenter!!.attachView(this)
+
+ initViews()
+ presenter!!.setActionListeners(applicationKvStore)
+
+ Configuration.getInstance().load(
+ requireContext(),
+ PreferenceManager.getDefaultSharedPreferences(requireContext())
+ )
+
+ binding!!.mapView.setTileSource(TileSourceFactory.WIKIMEDIA)
+ binding!!.mapView.setTilesScaledToDpi(true)
+
+ Configuration.getInstance().additionalHttpRequestProperties.put(
+ "Referer", "http://maps.wikimedia.org/"
+ )
+
+ val scaleBarOverlay = ScaleBarOverlay(binding!!.mapView)
+ scaleBarOverlay.setScaleBarOffset(15, 25)
+ val barPaint = Paint()
+ barPaint.setARGB(200, 255, 250, 250)
+ scaleBarOverlay.setBackgroundPaint(barPaint)
+ scaleBarOverlay.enableScaleBar()
+ binding!!.mapView.overlays.add(scaleBarOverlay)
+ binding!!.mapView.zoomController
+ .setVisibility(CustomZoomButtonsController.Visibility.NEVER)
+ binding!!.mapView.setMultiTouchControls(true)
+
+ if (!isCameFromNearbyMap) {
+ binding!!.mapView.controller.setZoom(ZOOM_LEVEL.toDouble())
+ }
+
+
+ binding!!.mapView.overlays.add(MapEventsOverlay(object : MapEventsReceiver {
+ override fun singleTapConfirmedHelper(p: GeoPoint?): Boolean {
+ if (clickedMarker != null) {
+ removeMarker(clickedMarker)
+ addMarkerToMap(clickedMarker!!)
+ binding!!.mapView.invalidate()
+ } else {
+ Timber.e("CLICKED MARKER IS NULL")
+ }
+ if (bottomSheetDetailsBehavior!!.getState() == BottomSheetBehavior.STATE_EXPANDED) {
+ // Back should first hide the bottom sheet if it is expanded
+ bottomSheetDetailsBehavior!!.setState(BottomSheetBehavior.STATE_HIDDEN)
+ } else if (isDetailsBottomSheetVisible()) {
+ hideBottomDetailsSheet()
+ }
+ return true
+ }
+
+ override fun longPressHelper(p: GeoPoint?): Boolean = false
+ }))
+
+ binding!!.mapView.addMapListener(object : MapListener {
+ override fun onScroll(event: ScrollEvent): Boolean {
+ if (getLastMapFocus() != null) {
+ val mylocation = Location("")
+ val dest_location = Location("")
+ dest_location.latitude = binding!!.mapView.mapCenter.latitude
+ dest_location.longitude = binding!!.mapView.mapCenter.longitude
+ mylocation.latitude = getLastMapFocus()!!.latitude
+ mylocation.longitude = getLastMapFocus()!!.longitude
+ val distance = mylocation.distanceTo(dest_location) //in meters
+ if (getLastMapFocus() != null) {
+ if (isNetworkConnectionEstablished() && (event.getX() > 0
+ || event.getY() > 0)
+ ) {
+ setSearchThisAreaButtonVisibility(distance > 2000.0)
+ }
+ } else {
+ setSearchThisAreaButtonVisibility(false)
+ }
+ }
+
+ return true
+ }
+
+ override fun onZoom(event: ZoomEvent?): Boolean = false
+ })
+ // removed tha permission check here to prevent it from running on fragment creation
+ }
+
+ override fun onResume() {
+ super.onResume()
+ binding!!.mapView.onResume()
+ presenter!!.attachView(this)
+ locationManager.addLocationListener(this)
+ if (broadcastReceiver != null) {
+ requireActivity().registerReceiver(broadcastReceiver, intentFilter)
+ }
+ setSearchThisAreaButtonVisibility(false)
+ }
+
+ override fun onPause() {
+ super.onPause()
+ // unregistering the broadcastReceiver, as it was causing an exception and a potential crash
+ unregisterNetworkReceiver()
+ locationManager.unregisterLocationManager()
+ locationManager.removeLocationListener(this)
+ }
+
+ fun requestLocationIfNeeded() {
+ if (!isVisible) return // skips if not visible to user
+ if (locationPermissionsHelper!!.checkLocationPermission(requireActivity())) {
+ if (locationPermissionsHelper!!.isLocationAccessToAppsTurnedOn()) {
+ locationManager.registerLocationManager()
+ drawMyLocationMarker()
+ } else {
+ locationPermissionsHelper!!.showLocationOffDialog(requireActivity(), R.string.location_off_dialog_text)
+ }
+ } else {
+ locationPermissionsHelper!!.requestForLocationAccess(
+ R.string.location_permission_title,
+ R.string.location_permission_rationale
+ )
+ }
+ }
+
+ private fun drawMyLocationMarker() {
+ val location = locationManager.getLastLocation()
+ if (location != null) {
+ val geoPoint = GeoPoint(location.latitude, location.longitude)
+ val startMarker = Marker(binding!!.mapView).apply {
+ setPosition(geoPoint)
+ setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM)
+ icon = ContextCompat.getDrawable(requireContext(), R.drawable.current_location_marker)
+ title = "Your Location"
+ textLabelFontSize = 24
+ }
+ binding!!.mapView.overlays.add(startMarker)
+ binding!!.mapView.invalidate()
+ }
+ }
+
+ /**
+ * Unregisters the networkReceiver
+ */
+ private fun unregisterNetworkReceiver() =
+ activity?.unregisterReceiver(broadcastReceiver)
+
+ private fun startMapWithoutPermission() {
+ lastKnownLocation = defaultLatLng
+ moveCameraToPosition(
+ GeoPoint(lastKnownLocation!!.latitude, lastKnownLocation!!.longitude)
+ )
+ presenter!!.onMapReady(exploreMapController)
+ }
+
+ private fun registerNetworkReceiver() =
+ activity?.registerReceiver(broadcastReceiver, intentFilter)
+
+ private fun performMapReadyActions() {
+ if (isDarkTheme) {
+ binding!!.mapView.overlayManager.tilesOverlay.setColorFilter(TilesOverlay.INVERT_COLORS)
+ }
+
+ if (applicationKvStore.getBoolean("doNotAskForLocationPermission", false) &&
+ !locationPermissionsHelper!!.checkLocationPermission(requireActivity())) {
+ isPermissionDenied = true
+ }
+
+ lastKnownLocation = getLastLocation()
+
+ if (lastKnownLocation == null) {
+ lastKnownLocation = defaultLatLng
+ }
+
+ // if we came from 'Show in Explore' in Nearby, load Nearby map center and zoom
+ if (isCameFromNearbyMap) {
+ moveCameraToPosition(
+ GeoPoint(prevLatitude, prevLongitude),
+ prevZoom,
+ 1L
+ )
+ } else {
+ moveCameraToPosition(
+ GeoPoint(lastKnownLocation!!.latitude, lastKnownLocation!!.longitude)
+ )
+ }
+ presenter!!.onMapReady(exploreMapController)
+ }
+
+ /**
+ * Fetch Nearby map camera data from fragment arguments if any.
+ */
+ fun loadNearbyMapData() {
+ // get fragment arguments
+ if (arguments != null) {
+ with (requireArguments()) {
+ prevZoom = getDouble("prev_zoom")
+ prevLatitude = getDouble("prev_latitude")
+ prevLongitude = getDouble("prev_longitude")
+ }
+ }
+
+ setRecentlyCameFromNearbyMap(isCameFromNearbyMap)
+ }
+
+ /**
+ * @return The LatLng from the previous Fragment's map center or (0,0,0) coordinates
+ * if that information is not available/applicable.
+ */
+ val previousLatLng: LatLng
+ get() = LatLng(prevLatitude, prevLongitude, prevZoom.toFloat())
+
+ /**
+ * Checks if fragment arguments contain data from Nearby map, indicating that the user navigated
+ * from Nearby using 'Show in Explore'.
+ *
+ * @return true if user navigated from Nearby map
+ */
+ val isCameFromNearbyMap: Boolean
+ get() = prevZoom != 0.0 || prevLatitude != 0.0 || prevLongitude != 0.0
+
+ /**
+ * Gets the value that indicates if the user navigated from "Show in Explore" in Nearby and
+ * that the LatLng from Nearby has yet to be searched for map markers.
+ */
+ fun recentlyCameFromNearbyMap(): Boolean =
+ recentlyCameFromNearbyMap
+
+ /**
+ * Sets the value that indicates if the user navigated from "Show in Explore" in Nearby and
+ * that the LatLng from Nearby has yet to be searched for map markers.
+ * @param newValue The value to set.
+ */
+ fun setRecentlyCameFromNearbyMap(newValue: Boolean) {
+ recentlyCameFromNearbyMap = newValue
+ }
+
+ fun loadNearbyMapFromExplore() {
+ (requireContext() as MainActivity).loadNearbyMapFromExplore(
+ binding!!.mapView.zoomLevelDouble,
+ binding!!.mapView.mapCenter.latitude,
+ binding!!.mapView.mapCenter.longitude
+ )
+ }
+
+ private fun initViews() {
+ initBottomSheets()
+ setBottomSheetCallbacks()
+ }
+
+ /**
+ * a) Creates bottom sheet behaviours from bottom sheet, sets initial states and visibility
+ * b) Gets the touch event on the map to perform following actions:
+ * if bottom sheet details are expanded or collapsed hide the bottom sheet details.
+ */
+ @SuppressLint("ClickableViewAccessibility")
+ private fun initBottomSheets() {
+ bottomSheetDetailsBehavior = BottomSheetBehavior.from(
+ binding!!.bottomSheetDetailsBinding.getRoot()
+ )
+ bottomSheetDetailsBehavior!!.setState(BottomSheetBehavior.STATE_HIDDEN)
+ binding!!.bottomSheetDetailsBinding.getRoot().visibility = View.VISIBLE
+ }
+
+ /**
+ * Defines how bottom sheets will act on click
+ */
+ private fun setBottomSheetCallbacks() {
+ binding!!.bottomSheetDetailsBinding.getRoot()
+ .setOnClickListener { v: View? ->
+ if (bottomSheetDetailsBehavior!!.getState() == BottomSheetBehavior.STATE_COLLAPSED) {
+ bottomSheetDetailsBehavior!!.setState(BottomSheetBehavior.STATE_EXPANDED)
+ } else if (bottomSheetDetailsBehavior!!.getState()
+ == BottomSheetBehavior.STATE_EXPANDED
+ ) {
+ bottomSheetDetailsBehavior!!.setState(BottomSheetBehavior.STATE_COLLAPSED)
+ }
+ }
+ }
+
+ override fun onLocationChangedSignificantly(latLng: LatLng) =
+ handleLocationUpdate(latLng, LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED)
+
+ override fun onLocationChangedSlightly(latLng: LatLng) =
+ handleLocationUpdate(latLng, LocationChangeType.LOCATION_SLIGHTLY_CHANGED)
+
+ private fun handleLocationUpdate(
+ latLng: LatLng?,
+ locationChangeType: LocationChangeType
+ ) {
+ lastKnownLocation = latLng
+ exploreMapController.currentLocation = lastKnownLocation
+ presenter!!.updateMap(locationChangeType)
+ }
+
+ override fun onLocationChangedMedium(latLng: LatLng) = Unit
+
+ override fun isNetworkConnectionEstablished(): Boolean =
+ isInternetConnectionEstablished(requireActivity())
+
+ override fun populatePlaces(curlatLng: LatLng?) {
+ val nearbyPlacesInfoObservable: Observable
+ if (curlatLng == null) {
+ return
+ }
+ if (curlatLng.equals(
+ getLastMapFocus()
+ )
+ ) { // Means we are checking around current location
+ nearbyPlacesInfoObservable = presenter!!.loadAttractionsFromLocation(
+ curlatLng,
+ getLastMapFocus(), true
+ )
+ } else {
+ nearbyPlacesInfoObservable = presenter!!.loadAttractionsFromLocation(
+ getLastMapFocus(),
+ curlatLng, false
+ )
+ }
+ compositeDisposable.add(
+ nearbyPlacesInfoObservable
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(
+ Consumer { explorePlacesInfo: ExplorePlacesInfo? ->
+ mediaList = explorePlacesInfo!!.mediaList.toMutableList()
+ if (mediaList!!.isEmpty()) {
+ showResponseMessage(getString(R.string.no_pictures_in_this_area))
+ }
+ updateMapMarkers(explorePlacesInfo)
+ lastMapFocus = GeoPoint(
+ curlatLng.latitude,
+ curlatLng.longitude
+ )
+ },
+ Consumer { throwable: Throwable? ->
+ Timber.d(throwable)
+ // Not showing the user, throwable localizedErrorMessage
+ showErrorMessage(getString(R.string.error_fetching_nearby_places))
+
+ setProgressBarVisibility(false)
+ presenter!!.lockUnlockNearby(false)
+ })
+ )
+ if (recenterToUserLocation) {
+ recenterToUserLocation = false
+ }
+ }
+
+ /**
+ * Updates map markers according to latest situation
+ *
+ * @param explorePlacesInfo holds several information as current location, marker list etc.
+ */
+ private fun updateMapMarkers(explorePlacesInfo: ExplorePlacesInfo) =
+ presenter!!.updateMapMarkers(explorePlacesInfo)
+
+ private fun showErrorMessage(message: String) =
+ showLongToast(requireActivity(), message)
+
+ private fun showResponseMessage(message: String) =
+ showLongSnackbar(requireView(), message)
+
+ override fun askForLocationPermission() {
+ Timber.d("Asking for location permission")
+ activityResultLauncher.launch(permission.ACCESS_FINE_LOCATION)
+ }
+
+ private fun locationPermissionGranted() {
+ isPermissionDenied = false
+ applicationKvStore.putBoolean("doNotAskForLocationPermission", false)
+ lastKnownLocation = locationManager.getLastLocation()
+ val target = lastKnownLocation
+ if (lastKnownLocation != null) {
+ val targetP = GeoPoint(target!!.latitude, target.longitude)
+ mapCenter = targetP
+ binding!!.mapView.controller.setCenter(targetP)
+ recenterMarkerToPosition(targetP)
+ moveCameraToPosition(targetP)
+ } else if (locationManager.isGPSProviderEnabled()
+ || locationManager.isNetworkProviderEnabled()
+ ) {
+ locationManager.requestLocationUpdatesFromProvider(LocationManager.NETWORK_PROVIDER)
+ locationManager.requestLocationUpdatesFromProvider(LocationManager.GPS_PROVIDER)
+ setProgressBarVisibility(true)
+ } else {
+ locationPermissionsHelper!!.showLocationOffDialog(
+ requireActivity(),
+ R.string.ask_to_turn_location_on_text
+ )
+ }
+ presenter!!.onMapReady(exploreMapController)
+ registerUnregisterLocationListener(false)
+ }
+
+ fun registerUnregisterLocationListener(removeLocationListener: Boolean) {
+ MapUtils.registerUnregisterLocationListener(removeLocationListener, locationManager, this)
+ }
+
+ override fun recenterMap(curLatLng: LatLng?) {
+ // if user has denied permission twice, then show dialog
+ if (isPermissionDenied) {
+ if (locationPermissionsHelper!!.checkLocationPermission(requireActivity())) {
+ // this will run when user has given permission by opening app's settings
+ isPermissionDenied = false
+ recenterMap(curLatLng)
+ } else {
+ askForLocationPermission()
+ }
+ } else {
+ if (!locationPermissionsHelper!!.checkLocationPermission(requireActivity())) {
+ askForLocationPermission()
+ } else {
+ locationPermissionGranted()
+ }
+ }
+ if (curLatLng == null) {
+ recenterToUserLocation = true
+ return
+ }
+ recenterMarkerToPosition(
+ GeoPoint(curLatLng.latitude, curLatLng.longitude)
+ )
+ binding!!.mapView.controller.animateTo(
+ GeoPoint(curLatLng.latitude, curLatLng.longitude)
+ )
+ if (lastMapFocus != null) {
+ val mylocation = Location("")
+ val dest_location = Location("")
+ dest_location.latitude = binding!!.mapView.mapCenter.latitude
+ dest_location.longitude = binding!!.mapView.mapCenter.longitude
+ mylocation.latitude = lastMapFocus!!.latitude
+ mylocation.longitude = lastMapFocus!!.longitude
+ val distance = mylocation.distanceTo(dest_location) //in meters
+ if (lastMapFocus != null) {
+ if (isNetworkConnectionEstablished()) {
+ setSearchThisAreaButtonVisibility(distance > 2000.0)
+ }
+ } else {
+ setSearchThisAreaButtonVisibility(false)
+ }
+ }
+ }
+
+ override fun hideBottomDetailsSheet() {
+ bottomSheetDetailsBehavior!!.setState(BottomSheetBehavior.STATE_HIDDEN)
+ }
+
+ /**
+ * Same bottom sheet carries information for all nearby places, so we need to pass information
+ * (title, description, distance and links) to view on nearby marker click
+ *
+ * @param place Place of clicked nearby marker
+ */
+ private fun passInfoToSheet(place: Place) {
+ binding!!.bottomSheetDetailsBinding.directionsButton.setOnClickListener {
+ handleGeoCoordinates(requireActivity(), place.getLocation(), binding!!.mapView.zoomLevelDouble)
+ }
+
+ binding!!.bottomSheetDetailsBinding.commonsButton.visibility = if (place.hasCommonsLink()) View.VISIBLE else View.GONE
+ binding!!.bottomSheetDetailsBinding.commonsButton.setOnClickListener {
+ handleWebUrl(requireContext(), place.siteLinks.commonsLink)
+ }
+
+ var index = 0
+ for (media in mediaList!!) {
+ if (media.filename == place.name) {
+ val finalIndex = index
+ binding!!.bottomSheetDetailsBinding.mediaDetailsButton.setOnClickListener {
+ (parentFragment as ExploreMapRootFragment).onMediaClicked(finalIndex)
+ }
+ }
+ index++
+ }
+ binding!!.bottomSheetDetailsBinding.title.text = place.name.substring(5, place.name.lastIndexOf("."))
+ binding!!.bottomSheetDetailsBinding.category.text = place.distance
+ // Remove label since it is double information
+ var descriptionText = place.longDescription
+ .replace(place.getName() + " (", "")
+ descriptionText = (if (descriptionText == place.longDescription)
+ descriptionText
+ else
+ descriptionText.replaceFirst(".$".toRegex(), ""))
+ // Set the short description after we remove place name from long description
+ binding!!.bottomSheetDetailsBinding.description.text = descriptionText
+ }
+
+ override fun addSearchThisAreaButtonAction() {
+ binding!!.searchThisAreaButton.setOnClickListener(presenter!!.onSearchThisAreaClicked())
+ }
+
+ override fun setSearchThisAreaButtonVisibility(isVisible: Boolean) {
+ binding!!.searchThisAreaButton.visibility = if (isVisible) View.VISIBLE else View.GONE
+ }
+
+ override fun setProgressBarVisibility(isVisible: Boolean) {
+ binding!!.mapProgressBar.visibility = if (isVisible) View.VISIBLE else View.GONE
+ }
+
+ override fun isDetailsBottomSheetVisible(): Boolean =
+ binding!!.bottomSheetDetailsBinding.getRoot().isVisible
+
+ override fun isSearchThisAreaButtonVisible(): Boolean =
+ binding!!.bottomSheetDetailsBinding.getRoot().isVisible
+
+ override fun getLastLocation(): LatLng? {
+ if (lastKnownLocation == null) {
+ lastKnownLocation = locationManager.getLastLocation()
+ }
+ return lastKnownLocation
+ }
+
+ override fun disableFABRecenter() {
+ binding!!.fabRecenter.setEnabled(false)
+ }
+
+ override fun enableFABRecenter() {
+ binding!!.fabRecenter.setEnabled(true)
+ }
+
+ /**
+ * Adds a markers to the map based on the list of NearbyBaseMarker.
+ *
+ * @param nearbyBaseMarkers The NearbyBaseMarker object representing the markers to be added.
+ */
+ override fun addMarkersToMap(nearbyBaseMarkers: List?) {
+ clearAllMarkers()
+ nearbyBaseMarkers?.forEach {
+ addMarkerToMap(it!!)
+ }
+ binding!!.mapView.invalidate()
+ }
+
+ /**
+ * Adds a marker to the map based on the specified NearbyBaseMarker.
+ *
+ * @param nearbyBaseMarker The NearbyBaseMarker object representing the marker to be added.
+ */
+ private fun addMarkerToMap(nearbyBaseMarker: BaseMarker) {
+ if (isAttachedToActivity) {
+ val items = mutableListOf()
+ val d: Drawable = nearbyBaseMarker.icon!!.toDrawable(resources)
+ val point = GeoPoint(
+ nearbyBaseMarker.place.location.latitude,
+ nearbyBaseMarker.place.location.longitude
+ )
+
+ val markerMedia = getMediaFromImageURL(nearbyBaseMarker.place.pic)
+ var authorUser: String? = null
+ if (markerMedia != null) {
+ // HTML text is sometimes part of the author string and needs to be removed
+ authorUser = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ Html.fromHtml(markerMedia.getAuthorOrUser(), Html.FROM_HTML_MODE_LEGACY)
+ } else {
+ Html.fromHtml(markerMedia.getAuthorOrUser())
+ }.toString()
+ }
+
+ var title = nearbyBaseMarker.place.name
+ // Remove "File:" if present at start
+ if (title.startsWith("File:")) {
+ title = title.substring(5)
+ }
+ // Remove extensions like .jpg, .jpeg, .png, .svg (case insensitive)
+ title = title.replace("(?i)\\.(jpg|jpeg|png|svg)$".toRegex(), "")
+ title = title.replace("_", " ")
+ //Truncate if too long because it doesn't fit the screen
+ if (title.length > 43) {
+ title = title.substring(0, 40) + "…"
+ }
+
+ val item = OverlayItem(title, authorUser, point)
+ item.setMarker(d)
+ items.add(item)
+ val overlay = ItemizedOverlayWithFocus(
+ items,
+ object : OnItemGestureListener {
+ override fun onItemSingleTapUp(index: Int, item: OverlayItem?): Boolean {
+ val place = nearbyBaseMarker.place
+ if (clickedMarker != null) {
+ removeMarker(clickedMarker)
+ addMarkerToMap(clickedMarker!!)
+ bottomSheetDetailsBehavior!!.setState(BottomSheetBehavior.STATE_HIDDEN)
+ bottomSheetDetailsBehavior!!.setState(BottomSheetBehavior.STATE_COLLAPSED)
+ }
+ clickedMarker = nearbyBaseMarker
+ passInfoToSheet(place)
+
+ //Move the overlay to the top so it can be fully seen.
+ moveOverlayToTop(getOverlay(item))
+ return true
+ }
+
+ override fun onItemLongPress(index: Int, item: OverlayItem?): Boolean = false
+ }, requireContext()
+ )
+
+ if (baseMarkerOverlayMap == null) {
+ baseMarkerOverlayMap = HashMap()
+ }
+ baseMarkerOverlayMap!!.put(nearbyBaseMarker, overlay)
+
+ overlay.setFocusItemsOnTap(true)
+ binding!!.mapView.overlays.add(overlay) // Add the overlay to the map
+ }
+ }
+
+ /**
+ * Moves the specified Overlay above all other Overlays. This prevents other Overlays from
+ * obstructing it. Upon failure, this method returns early.
+ * @param overlay The Overlay to move.
+ */
+ private fun moveOverlayToTop(overlay: Overlay?) {
+ if (overlay == null || binding == null || binding!!.mapView.overlays == null) {
+ return
+ }
+
+ val successfulRemoval = binding!!.mapView.overlays.remove(overlay)
+ if (!successfulRemoval) {
+ return
+ }
+
+ binding!!.mapView.overlays.add(overlay)
+ }
+
+ /**
+ * Performs a linear search for the first Overlay which contains the specified OverlayItem.
+ *
+ * @param item The OverlayItem contained within the first target Overlay.
+ * @return The first Overlay which contains the specified OverlayItem or null if the Overlay
+ * could not be found.
+ */
+ private fun getOverlay(item: OverlayItem?): Overlay? {
+ if (item == null || binding == null || binding!!.mapView.overlays == null) {
+ return null
+ }
+
+ for (i in binding!!.mapView.overlays.indices) {
+ if (binding!!.mapView.overlays[i] is ItemizedOverlayWithFocus<*>) {
+ val overlay = binding!!.mapView.overlays[i] as ItemizedOverlayWithFocus<*>
+
+ for (j in 0.. {
+ setARGB(200, 255, 250, 250)
+ })
+ enableScaleBar()
+ })
+
+ binding!!.mapView.overlays.add(MapEventsOverlay(object : MapEventsReceiver {
+ override fun singleTapConfirmedHelper(p: GeoPoint?): Boolean {
+ if (clickedMarker != null) {
+ removeMarker(clickedMarker)
+ addMarkerToMap(clickedMarker!!)
+ binding!!.mapView.invalidate()
+ } else {
+ Timber.e("CLICKED MARKER IS NULL")
+ }
+ if (bottomSheetDetailsBehavior!!.getState() == BottomSheetBehavior.STATE_EXPANDED) {
+ // Back should first hide the bottom sheet if it is expanded
+ bottomSheetDetailsBehavior!!.setState(BottomSheetBehavior.STATE_HIDDEN)
+ } else if (isDetailsBottomSheetVisible()) {
+ hideBottomDetailsSheet()
+ }
+ return true
+ }
+
+ override fun longPressHelper(p: GeoPoint?): Boolean = false
+ }))
+ binding!!.mapView.setMultiTouchControls(true)
+ }
+ }
+
+ /**
+ * Recenters the map view to the specified GeoPoint and updates the marker to indicate the new
+ * position.
+ *
+ * @param geoPoint The GeoPoint representing the new center position for the map.
+ */
+ private fun recenterMarkerToPosition(geoPoint: GeoPoint?) {
+ if (geoPoint != null) {
+ binding!!.mapView.controller.setCenter(geoPoint)
+ val overlays = binding!!.mapView.overlays
+ // collects the indices of items to remove
+ val indicesToRemove = mutableListOf()
+ for (i in overlays.indices) {
+ if (overlays[i] is Marker || overlays[i] is ScaleDiskOverlay) {
+ indicesToRemove.add(i)
+ }
+ }
+ // removes the items in reverse order to avoid index shifting
+ indicesToRemove.sortedDescending().forEach { index ->
+ binding!!.mapView.overlays.removeAt(index)
+ }
+ val diskOverlay = ScaleDiskOverlay(
+ requireContext(),
+ geoPoint, 2000, GeoConstants.UnitOfMeasure.foot
+ ).apply {
+ setCirclePaint2(Paint().apply {
+ setColor(Color.rgb(128, 128, 128))
+ this.style = Paint.Style.STROKE
+ this.strokeWidth = 2f
+ })
+ setCirclePaint1(Paint().apply {
+ setColor(Color.argb(40, 128, 128, 128))
+ this.style = Paint.Style.FILL_AND_STROKE
+ })
+ setDisplaySizeMin(900)
+ setDisplaySizeMax(1700)
+ }
+ binding!!.mapView.overlays.add(diskOverlay)
+ val startMarker = Marker(
+ binding!!.mapView
+ ).apply {
+ setPosition(geoPoint)
+ setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM)
+ icon = ContextCompat.getDrawable(requireContext(), R.drawable.current_location_marker)
+ title = "Your Location"
+ textLabelFontSize = 24
+ }
+ binding!!.mapView.overlays.add(startMarker)
+ }
+ }
+
+ /**
+ * Moves the camera of the map view to the specified GeoPoint using an animation.
+ *
+ * @param geoPoint The GeoPoint representing the new camera position for the map.
+ */
+ private fun moveCameraToPosition(geoPoint: GeoPoint?) {
+ binding!!.mapView.controller.animateTo(geoPoint)
+ }
+
+ /**
+ * Moves the camera of the map view to the specified GeoPoint at specified zoom level and speed
+ * using an animation.
+ *
+ * @param geoPoint The GeoPoint representing the new camera position for the map.
+ * @param zoom Zoom level of the map camera
+ * @param speed Speed of animation
+ */
+ private fun moveCameraToPosition(geoPoint: GeoPoint?, zoom: Double, speed: Long) {
+ binding!!.mapView.controller.animateTo(geoPoint, zoom, speed)
+ }
+
+ override fun getLastMapFocus(): LatLng? = if (lastMapFocus == null) {
+ getMapCenter()
+ } else {
+ LatLng(lastMapFocus!!.latitude, lastMapFocus!!.longitude, 100f)
+ }
+
+ override fun getMapCenter(): LatLng? = if (mapCenter != null) {
+ LatLng(mapCenter!!.latitude, mapCenter!!.longitude, 100f)
+ } else {
+ if (applicationKvStore.getString("LastLocation") != null) {
+ val locationLatLng: Array =
+ applicationKvStore.getString("LastLocation")!!
+ .split(",".toRegex())
+ .dropLastWhile { it.isEmpty() }
+ .toTypedArray()
+ lastKnownLocation = LatLng(
+ locationLatLng[0]!!.toDouble(),
+ locationLatLng[1]!!.toDouble(), 1f
+ )
+ lastKnownLocation
+ } else {
+ LatLng(51.506255446947776, -0.07483536015053005, 1f)
+ }
+ }
+
+ override fun getMapFocus(): LatLng? = LatLng(
+ binding!!.mapView.mapCenter.latitude,
+ binding!!.mapView.mapCenter.longitude, 100f
+ )
+
+ override fun setFABRecenterAction(onClickListener: View.OnClickListener?) {
+ binding!!.fabRecenter.setOnClickListener(onClickListener)
+ }
+
+ override fun backButtonClicked(): Boolean {
+ if (bottomSheetDetailsBehavior!!.getState() != BottomSheetBehavior.STATE_HIDDEN) {
+ bottomSheetDetailsBehavior!!.setState(BottomSheetBehavior.STATE_HIDDEN)
+ return true
+ } else {
+ return false
+ }
+ }
+
+ /**
+ * Adds network broadcast receiver to recognize connection established
+ */
+ private fun initNetworkBroadCastReceiver() {
+ broadcastReceiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent?) {
+ if (activity != null) {
+ if (isInternetConnectionEstablished(requireActivity())) {
+ if (isNetworkErrorOccurred) {
+ presenter!!.updateMap(LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED)
+ isNetworkErrorOccurred = false
+ }
+
+ if (snackbar != null) {
+ snackbar!!.dismiss()
+ snackbar = null
+ }
+ } else {
+ if (snackbar == null) {
+ snackbar = Snackbar.make(
+ requireView(), R.string.no_internet,
+ Snackbar.LENGTH_INDEFINITE
+ )
+ setSearchThisAreaButtonVisibility(false)
+ setProgressBarVisibility(false)
+ }
+
+ isNetworkErrorOccurred = true
+ snackbar!!.show()
+ }
+ }
+ }
+ }
+ }
+
+ val isAttachedToActivity: Boolean
+ get() = isVisible && activity != null
+
+ override fun onLocationPermissionDenied(toastMessage: String) = Unit
+
+ override fun onLocationPermissionGranted() {
+ if (locationPermissionsHelper!!.isLocationAccessToAppsTurnedOn()) {
+ locationManager.registerLocationManager()
+ drawMyLocationMarker()
+ } else {
+ locationPermissionsHelper!!.showLocationOffDialog(requireActivity(), R.string.location_off_dialog_text)
+ }
+ onLocationChanged(LocationChangeType.PERMISSION_JUST_GRANTED, null)
+ }
+
+ fun onLocationChanged(locationChangeType: LocationChangeType, location: Location?) {
+ if (locationChangeType == LocationChangeType.PERMISSION_JUST_GRANTED) {
+ val curLatLng = locationManager.getLastLocation() ?: getMapCenter()
+ populatePlaces(curLatLng)
+ } else {
+ presenter!!.updateMap(locationChangeType)
+ }
+ }
+
+ companion object {
+ fun newInstance(): ExploreMapFragment {
+ val fragment = ExploreMapFragment()
+ fragment.setRetainInstance(true)
+ return fragment
+ }
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapPresenter.java b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapPresenter.java
deleted file mode 100644
index 70f785b40..000000000
--- a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapPresenter.java
+++ /dev/null
@@ -1,237 +0,0 @@
-package fr.free.nrw.commons.explore.map;
-
-import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED;
-import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.SEARCH_CUSTOM_AREA;
-
-
-import android.location.Location;
-import android.view.View;
-import fr.free.nrw.commons.BaseMarker;
-import fr.free.nrw.commons.MapController;
-import fr.free.nrw.commons.MapController.ExplorePlacesInfo;
-import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao;
-import fr.free.nrw.commons.explore.map.ExploreMapController.NearbyBaseMarkerThumbCallback;
-import fr.free.nrw.commons.kvstore.JsonKvStore;
-import fr.free.nrw.commons.location.LatLng;
-import fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType;
-import fr.free.nrw.commons.nearby.Place;
-import io.reactivex.Observable;
-import java.lang.reflect.Proxy;
-import java.util.List;
-import timber.log.Timber;
-
-public class ExploreMapPresenter
- implements ExploreMapContract.UserActions,
- NearbyBaseMarkerThumbCallback {
-
- BookmarkLocationsDao bookmarkLocationDao;
- private boolean isNearbyLocked;
- private LatLng currentLatLng;
- private ExploreMapController exploreMapController;
-
- private static final ExploreMapContract.View DUMMY = (ExploreMapContract.View) Proxy
- .newProxyInstance(
- ExploreMapContract.View.class.getClassLoader(),
- new Class[]{ExploreMapContract.View.class}, (proxy, method, args) -> {
- if (method.getName().equals("onMyEvent")) {
- return null;
- } else if (String.class == method.getReturnType()) {
- return "";
- } else if (Integer.class == method.getReturnType()) {
- return Integer.valueOf(0);
- } else if (int.class == method.getReturnType()) {
- return 0;
- } else if (Boolean.class == method.getReturnType()) {
- return Boolean.FALSE;
- } else if (boolean.class == method.getReturnType()) {
- return false;
- } else {
- return null;
- }
- }
- );
- private ExploreMapContract.View exploreMapFragmentView = DUMMY;
-
- public ExploreMapPresenter(BookmarkLocationsDao bookmarkLocationDao) {
- this.bookmarkLocationDao = bookmarkLocationDao;
- }
-
- @Override
- public void updateMap(LocationChangeType locationChangeType) {
- Timber.d("Presenter updates map and list" + locationChangeType.toString());
- if (isNearbyLocked) {
- Timber.d("Nearby is locked, so updateMapAndList returns");
- return;
- }
-
- if (!exploreMapFragmentView.isNetworkConnectionEstablished()) {
- Timber.d("Network connection is not established");
- return;
- }
-
- /**
- * Significant changed - Markers and current location will be updated together
- * Slightly changed - Only current position marker will be updated
- */
- if (locationChangeType.equals(LOCATION_SIGNIFICANTLY_CHANGED)) {
- Timber.d("LOCATION_SIGNIFICANTLY_CHANGED");
- LatLng populateLatLng = exploreMapFragmentView.getMapCenter();
-
- //If "Show in Explore" was selected in Nearby, use the previous LatLng
- if (exploreMapFragmentView instanceof ExploreMapFragment) {
- ExploreMapFragment exploreMapFragment = (ExploreMapFragment)exploreMapFragmentView;
- if (exploreMapFragment.recentlyCameFromNearbyMap()) {
- //Ensure this LatLng will not be used again if user searches their GPS location
- exploreMapFragment.setRecentlyCameFromNearbyMap(false);
-
- populateLatLng = exploreMapFragment.getPreviousLatLng();
- }
- }
-
- lockUnlockNearby(true);
- exploreMapFragmentView.setProgressBarVisibility(true);
- exploreMapFragmentView.populatePlaces(populateLatLng);
- } else if (locationChangeType.equals(SEARCH_CUSTOM_AREA)) {
- Timber.d("SEARCH_CUSTOM_AREA");
- lockUnlockNearby(true);
- exploreMapFragmentView.setProgressBarVisibility(true);
- exploreMapFragmentView.populatePlaces(exploreMapFragmentView.getMapFocus());
- } else { // Means location changed slightly, ie user is walking or driving.
- Timber.d("Means location changed slightly");
- }
- }
-
- /**
- * Nearby updates takes time, since they are network operations. During update time, we don't
- * want to get any other calls from user. So locking nearby.
- *
- * @param isNearbyLocked true means lock, false means unlock
- */
- @Override
- public void lockUnlockNearby(boolean isNearbyLocked) {
- this.isNearbyLocked = isNearbyLocked;
- if (isNearbyLocked) {
- exploreMapFragmentView.disableFABRecenter();
- } else {
- exploreMapFragmentView.enableFABRecenter();
- }
- }
-
- @Override
- public void attachView(ExploreMapContract.View view) {
- exploreMapFragmentView = view;
- }
-
- @Override
- public void detachView() {
- exploreMapFragmentView = DUMMY;
- }
-
- /**
- * Sets click listener of FAB
- */
- @Override
- public void setActionListeners(JsonKvStore applicationKvStore) {
- exploreMapFragmentView.setFABRecenterAction(v -> {
- exploreMapFragmentView.recenterMap(currentLatLng);
- });
-
- }
-
- @Override
- public boolean backButtonClicked() {
- return exploreMapFragmentView.backButtonClicked();
- }
-
- public void onMapReady(ExploreMapController exploreMapController) {
- this.exploreMapController = exploreMapController;
- if (null != exploreMapFragmentView) {
- exploreMapFragmentView.addSearchThisAreaButtonAction();
- initializeMapOperations();
- }
- }
-
- public void initializeMapOperations() {
- lockUnlockNearby(false);
- updateMap(LOCATION_SIGNIFICANTLY_CHANGED);
- }
-
- public Observable loadAttractionsFromLocation(LatLng currentLatLng,
- LatLng searchLatLng, boolean checkingAroundCurrent) {
- return Observable
- .fromCallable(() -> exploreMapController
- .loadAttractionsFromLocation(currentLatLng, searchLatLng, checkingAroundCurrent));
- }
-
- /**
- * Populates places for custom location, should be used for finding nearby places around a
- * location where you are not at.
- *
- * @param explorePlacesInfo This variable has placeToCenter list information and distances.
- */
- public void updateMapMarkers(
- MapController.ExplorePlacesInfo explorePlacesInfo) {
- if (explorePlacesInfo.mediaList != null) {
- prepareNearbyBaseMarkers(explorePlacesInfo);
- } else {
- lockUnlockNearby(false); // So that new location updates wont come
- exploreMapFragmentView.setProgressBarVisibility(false);
- }
- }
-
- void prepareNearbyBaseMarkers(MapController.ExplorePlacesInfo explorePlacesInfo) {
- exploreMapController
- .loadAttractionsFromLocationToBaseMarkerOptions(explorePlacesInfo.currentLatLng,
- // Curlatlang will be used to calculate distances
- (List) explorePlacesInfo.explorePlaceList,
- exploreMapFragmentView.getContext(),
- this,
- explorePlacesInfo);
- }
-
- @Override
- public void onNearbyBaseMarkerThumbsReady(List baseMarkers,
- ExplorePlacesInfo explorePlacesInfo) {
- if (null != exploreMapFragmentView) {
- exploreMapFragmentView.addMarkersToMap(baseMarkers);
- lockUnlockNearby(false); // So that new location updates wont come
- exploreMapFragmentView.setProgressBarVisibility(false);
- }
- }
-
- public View.OnClickListener onSearchThisAreaClicked() {
- return v -> {
- // Lock map operations during search this area operation
- exploreMapFragmentView.setSearchThisAreaButtonVisibility(false);
-
- if (searchCloseToCurrentLocation()) {
- updateMap(LOCATION_SIGNIFICANTLY_CHANGED);
- } else {
- updateMap(SEARCH_CUSTOM_AREA);
- }
- };
- }
-
- /**
- * Returns true if search this area button is used around our current location, so that we can
- * continue following our current location again
- *
- * @return Returns true if search this area button is used around our current location
- */
- public boolean searchCloseToCurrentLocation() {
- if (null == exploreMapFragmentView.getLastMapFocus()) {
- return true;
- }
-
- Location mylocation = new Location("");
- Location dest_location = new Location("");
- dest_location.setLatitude(exploreMapFragmentView.getMapFocus().getLatitude());
- dest_location.setLongitude(exploreMapFragmentView.getMapFocus().getLongitude());
- mylocation.setLatitude(exploreMapFragmentView.getLastMapFocus().getLatitude());
- mylocation.setLongitude(exploreMapFragmentView.getLastMapFocus().getLongitude());
- Float distance = mylocation.distanceTo(dest_location);
-
- return !(distance > 2000.0 * 3 / 4);
- }
-
-}
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapPresenter.kt b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapPresenter.kt
new file mode 100644
index 000000000..002ff6044
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapPresenter.kt
@@ -0,0 +1,223 @@
+package fr.free.nrw.commons.explore.map
+
+import android.location.Location
+import android.view.View
+import fr.free.nrw.commons.BaseMarker
+import fr.free.nrw.commons.MapController.ExplorePlacesInfo
+import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao
+import fr.free.nrw.commons.explore.map.ExploreMapController.Companion.loadAttractionsFromLocationToBaseMarkerOptions
+import fr.free.nrw.commons.explore.map.ExploreMapController.NearbyBaseMarkerThumbCallback
+import fr.free.nrw.commons.kvstore.JsonKvStore
+import fr.free.nrw.commons.location.LatLng
+import fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType
+import fr.free.nrw.commons.nearby.Place
+import io.reactivex.Observable
+import timber.log.Timber
+import java.lang.reflect.Method
+import java.lang.reflect.Proxy
+import java.util.concurrent.Callable
+
+class ExploreMapPresenter(
+ var bookmarkLocationDao: BookmarkLocationsDao
+) : ExploreMapContract.UserActions, NearbyBaseMarkerThumbCallback {
+
+ private var isNearbyLocked = false
+ private val currentLatLng: LatLng? = null
+ private var exploreMapController: ExploreMapController? = null
+ private var exploreMapFragmentView: ExploreMapContract.View? = DUMMY
+
+ override fun updateMap(locationChangeType: LocationChangeType) {
+ Timber.d("Presenter updates map and list$locationChangeType")
+ if (isNearbyLocked) {
+ Timber.d("Nearby is locked, so updateMapAndList returns")
+ return
+ }
+
+ if (!exploreMapFragmentView!!.isNetworkConnectionEstablished()) {
+ Timber.d("Network connection is not established")
+ return
+ }
+
+ /**
+ * Significant changed - Markers and current location will be updated together
+ * Slightly changed - Only current position marker will be updated
+ */
+ if (locationChangeType == LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED) {
+ Timber.d("LOCATION_SIGNIFICANTLY_CHANGED")
+ var populateLatLng = exploreMapFragmentView!!.getMapCenter()
+
+ //If "Show in Explore" was selected in Nearby, use the previous LatLng
+ if (exploreMapFragmentView is ExploreMapFragment) {
+ val exploreMapFragment = exploreMapFragmentView as ExploreMapFragment
+ if (exploreMapFragment.recentlyCameFromNearbyMap()) {
+ //Ensure this LatLng will not be used again if user searches their GPS location
+ exploreMapFragment.setRecentlyCameFromNearbyMap(false)
+
+ populateLatLng = exploreMapFragment.previousLatLng
+ }
+ }
+
+ lockUnlockNearby(true)
+ exploreMapFragmentView!!.setProgressBarVisibility(true)
+ exploreMapFragmentView!!.populatePlaces(populateLatLng)
+ } else if (locationChangeType == LocationChangeType.SEARCH_CUSTOM_AREA) {
+ Timber.d("SEARCH_CUSTOM_AREA")
+ lockUnlockNearby(true)
+ exploreMapFragmentView!!.setProgressBarVisibility(true)
+ exploreMapFragmentView!!.populatePlaces(exploreMapFragmentView!!.getMapFocus())
+ } else { // Means location changed slightly, ie user is walking or driving.
+ Timber.d("Means location changed slightly")
+ }
+ }
+
+ /**
+ * Nearby updates takes time, since they are network operations. During update time, we don't
+ * want to get any other calls from user. So locking nearby.
+ *
+ * @param isNearbyLocked true means lock, false means unlock
+ */
+ override fun lockUnlockNearby(isNearbyLocked: Boolean) {
+ this.isNearbyLocked = isNearbyLocked
+ if (isNearbyLocked) {
+ exploreMapFragmentView!!.disableFABRecenter()
+ } else {
+ exploreMapFragmentView!!.enableFABRecenter()
+ }
+ }
+
+ override fun attachView(view: ExploreMapContract.View?) {
+ exploreMapFragmentView = view
+ }
+
+ override fun detachView() {
+ exploreMapFragmentView = DUMMY
+ }
+
+ /**
+ * Sets click listener of FAB
+ */
+ override fun setActionListeners(applicationKvStore: JsonKvStore?) {
+ exploreMapFragmentView!!.setFABRecenterAction {
+ exploreMapFragmentView!!.recenterMap(currentLatLng)
+ }
+ }
+
+ override fun backButtonClicked(): Boolean =
+ exploreMapFragmentView!!.backButtonClicked()
+
+ fun onMapReady(exploreMapController: ExploreMapController?) {
+ this.exploreMapController = exploreMapController
+ if (null != exploreMapFragmentView) {
+ exploreMapFragmentView!!.addSearchThisAreaButtonAction()
+ initializeMapOperations()
+ }
+ }
+
+ fun initializeMapOperations() {
+ lockUnlockNearby(false)
+ updateMap(LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED)
+ }
+
+ fun loadAttractionsFromLocation(
+ currentLatLng: LatLng?,
+ searchLatLng: LatLng?, checkingAroundCurrent: Boolean
+ ): Observable = Observable.fromCallable(Callable {
+ exploreMapController!!.loadAttractionsFromLocation(
+ currentLatLng,
+ searchLatLng,
+ checkingAroundCurrent
+ )
+ })
+
+ /**
+ * Populates places for custom location, should be used for finding nearby places around a
+ * location where you are not at.
+ *
+ * @param explorePlacesInfo This variable has placeToCenter list information and distances.
+ */
+ fun updateMapMarkers(
+ explorePlacesInfo: ExplorePlacesInfo
+ ) {
+ if (explorePlacesInfo.mediaList != null) {
+ prepareNearbyBaseMarkers(explorePlacesInfo)
+ } else {
+ lockUnlockNearby(false) // So that new location updates wont come
+ exploreMapFragmentView!!.setProgressBarVisibility(false)
+ }
+ }
+
+ private fun prepareNearbyBaseMarkers(explorePlacesInfo: ExplorePlacesInfo) {
+ loadAttractionsFromLocationToBaseMarkerOptions(
+ explorePlacesInfo.currentLatLng, // Curlatlang will be used to calculate distances
+ explorePlacesInfo.explorePlaceList,
+ exploreMapFragmentView!!.getContext()!!,
+ this,
+ explorePlacesInfo
+ )
+ }
+
+ override fun onNearbyBaseMarkerThumbsReady(
+ baseMarkers: List?,
+ explorePlacesInfo: ExplorePlacesInfo?
+ ) {
+ if (null != exploreMapFragmentView) {
+ exploreMapFragmentView!!.addMarkersToMap(baseMarkers)
+ lockUnlockNearby(false) // So that new location updates wont come
+ exploreMapFragmentView!!.setProgressBarVisibility(false)
+ }
+ }
+
+ fun onSearchThisAreaClicked(): View.OnClickListener {
+ return View.OnClickListener {
+ // Lock map operations during search this area operation
+ exploreMapFragmentView!!.setSearchThisAreaButtonVisibility(false)
+ updateMap(if (searchCloseToCurrentLocation()) {
+ LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED
+ } else {
+ LocationChangeType.SEARCH_CUSTOM_AREA
+ })
+ }
+ }
+
+ /**
+ * Returns true if search this area button is used around our current location, so that we can
+ * continue following our current location again
+ *
+ * @return Returns true if search this area button is used around our current location
+ */
+ private fun searchCloseToCurrentLocation(): Boolean {
+ if (null == exploreMapFragmentView!!.getLastMapFocus()) {
+ return true
+ }
+
+ val mylocation = Location("").apply {
+ latitude = exploreMapFragmentView!!.getLastMapFocus()!!.latitude
+ longitude = exploreMapFragmentView!!.getLastMapFocus()!!.longitude
+ }
+ val dest_location = Location("").apply {
+ latitude = exploreMapFragmentView!!.getMapFocus()!!.latitude
+ longitude = exploreMapFragmentView!!.getMapFocus()!!.longitude
+ }
+
+ val distance = mylocation.distanceTo(dest_location)
+
+ return !(distance > 2000.0 * 3 / 4)
+ }
+
+ companion object {
+ private val DUMMY = Proxy.newProxyInstance(
+ ExploreMapContract.View::class.java.classLoader,
+ arrayOf>(ExploreMapContract.View::class.java)
+ ) { _: Any?, method: Method, _: Array? ->
+ when {
+ method.name == "onMyEvent" -> null
+ String::class.java == method.returnType -> ""
+ Int::class.java == method.returnType -> 0
+ Int::class.javaPrimitiveType == method.returnType -> 0
+ Boolean::class.java == method.returnType -> java.lang.Boolean.FALSE
+ Boolean::class.javaPrimitiveType == method.returnType -> false
+ else -> null
+ }
+ } as ExploreMapContract.View
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesContentProvider.java b/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesContentProvider.java
deleted file mode 100644
index ab6dd7b05..000000000
--- a/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesContentProvider.java
+++ /dev/null
@@ -1,202 +0,0 @@
-package fr.free.nrw.commons.explore.recentsearches;
-
-import android.content.ContentValues;
-import android.content.UriMatcher;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteQueryBuilder;
-import android.net.Uri;
-import android.text.TextUtils;
-
-import androidx.annotation.NonNull;
-
-import javax.inject.Inject;
-
-import fr.free.nrw.commons.BuildConfig;
-import fr.free.nrw.commons.data.DBOpenHelper;
-import fr.free.nrw.commons.di.CommonsDaggerContentProvider;
-import timber.log.Timber;
-
-import static android.content.UriMatcher.NO_MATCH;
-import static fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao.Table.ALL_FIELDS;
-import static fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao.Table.COLUMN_ID;
-import static fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao.Table.TABLE_NAME;
-
-
-/**
- * This class contains functions for executing queries for
- * inserting, searching, deleting, editing recent searches in SqLite DB
- **/
-public class RecentSearchesContentProvider extends CommonsDaggerContentProvider {
-
- // For URI matcher
- private static final int RECENT_SEARCHES = 1;
- private static final int RECENT_SEARCHES_ID = 2;
- private static final String BASE_PATH = "recent_searches";
- public static final Uri BASE_URI = Uri.parse("content://" + BuildConfig.RECENT_SEARCH_AUTHORITY + "/" + BASE_PATH);
- private static final UriMatcher uriMatcher = new UriMatcher(NO_MATCH);
-
- static {
- uriMatcher.addURI(BuildConfig.RECENT_SEARCH_AUTHORITY, BASE_PATH, RECENT_SEARCHES);
- uriMatcher.addURI(BuildConfig.RECENT_SEARCH_AUTHORITY, BASE_PATH + "/#", RECENT_SEARCHES_ID);
- }
-
- public static Uri uriForId(int id) {
- return Uri.parse(BASE_URI.toString() + "/" + id);
- }
-
- @Inject DBOpenHelper dbOpenHelper;
-
- /**
- * This functions executes query for searching recent searches in SqLite DB
- **/
- @SuppressWarnings("ConstantConditions")
- @Override
- public Cursor query(@NonNull Uri uri, String[] projection, String selection,
- String[] selectionArgs, String sortOrder) {
- SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
- queryBuilder.setTables(TABLE_NAME);
-
- int uriType = uriMatcher.match(uri);
-
- SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
- Cursor cursor;
-
- switch (uriType) {
- case RECENT_SEARCHES:
- cursor = queryBuilder.query(db, projection, selection, selectionArgs,
- null, null, sortOrder);
- break;
- case RECENT_SEARCHES_ID:
- cursor = queryBuilder.query(db,
- ALL_FIELDS,
- "_id = ?",
- new String[]{uri.getLastPathSegment()},
- null,
- null,
- sortOrder
- );
- break;
- default:
- throw new IllegalArgumentException("Unknown URI" + uri);
- }
-
- cursor.setNotificationUri(getContext().getContentResolver(), uri);
-
- return cursor;
- }
-
- @Override
- public String getType(@NonNull Uri uri) {
- return null;
- }
-
- /**
- * This functions executes query for inserting a recentSearch object in SqLite DB
- **/
- @SuppressWarnings("ConstantConditions")
- @Override
- public Uri insert(@NonNull Uri uri, ContentValues contentValues) {
- int uriType = uriMatcher.match(uri);
- SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
- long id;
- switch (uriType) {
- case RECENT_SEARCHES:
- id = sqlDB.insert(TABLE_NAME, null, contentValues);
- break;
- default:
- throw new IllegalArgumentException("Unknown URI: " + uri);
- }
- getContext().getContentResolver().notifyChange(uri, null);
- return Uri.parse(BASE_URI + "/" + id);
- }
-
- /**
- * This functions executes query for deleting a recentSearch object in SqLite DB
- **/
- @Override
- public int delete(@NonNull Uri uri, String s, String[] strings) {
- int rows;
- int uriType = uriMatcher.match(uri);
- SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
- switch (uriType) {
- case RECENT_SEARCHES_ID:
- Timber.d("Deleting recent searches id %s", uri.getLastPathSegment());
- rows = db.delete(RecentSearchesDao.Table.TABLE_NAME,
- "_id = ?",
- new String[]{uri.getLastPathSegment()}
- );
- break;
- default:
- throw new IllegalArgumentException("Unknown URI" + uri);
- }
- getContext().getContentResolver().notifyChange(uri, null);
- return rows;
- }
-
- /**
- * This functions executes query for inserting multiple recentSearch objects in SqLite DB
- **/
- @SuppressWarnings("ConstantConditions")
- @Override
- public int bulkInsert(@NonNull Uri uri, @NonNull ContentValues[] values) {
- Timber.d("Hello, bulk insert! (RecentSearchesContentProvider)");
- int uriType = uriMatcher.match(uri);
- SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
- sqlDB.beginTransaction();
- switch (uriType) {
- case RECENT_SEARCHES:
- for (ContentValues value : values) {
- Timber.d("Inserting! %s", value);
- sqlDB.insert(TABLE_NAME, null, value);
- }
- break;
- default:
- throw new IllegalArgumentException("Unknown URI: " + uri);
- }
- sqlDB.setTransactionSuccessful();
- sqlDB.endTransaction();
- getContext().getContentResolver().notifyChange(uri, null);
- return values.length;
- }
-
- /**
- * This functions executes query for updating a particular recentSearch object in SqLite DB
- **/
- @SuppressWarnings("ConstantConditions")
- @Override
- public int update(@NonNull Uri uri, ContentValues contentValues, String selection,
- String[] selectionArgs) {
- /*
- SQL Injection warnings: First, note that we're not exposing this to the
- outside world (exported="false"). Even then, we should make sure to sanitize
- all user input appropriately. Input that passes through ContentValues
- should be fine. So only issues are those that pass in via concating.
-
- In here, the only concat created argument is for id. It is cast to an int,
- and will error out otherwise.
- */
- int uriType = uriMatcher.match(uri);
- SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
- int rowsUpdated;
- switch (uriType) {
- case RECENT_SEARCHES_ID:
- if (TextUtils.isEmpty(selection)) {
- int id = Integer.valueOf(uri.getLastPathSegment());
- rowsUpdated = sqlDB.update(TABLE_NAME,
- contentValues,
- COLUMN_ID + " = ?",
- new String[]{String.valueOf(id)});
- } else {
- throw new IllegalArgumentException(
- "Parameter `selection` should be empty when updating an ID");
- }
- break;
- default:
- throw new IllegalArgumentException("Unknown URI: " + uri + " with type " + uriType);
- }
- getContext().getContentResolver().notifyChange(uri, null);
- return rowsUpdated;
- }
-}
-
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesContentProvider.kt b/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesContentProvider.kt
new file mode 100644
index 000000000..c18f101ae
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesContentProvider.kt
@@ -0,0 +1,174 @@
+package fr.free.nrw.commons.explore.recentsearches
+
+import android.content.ContentValues
+import android.content.UriMatcher
+import android.database.Cursor
+import android.database.sqlite.SQLiteQueryBuilder
+import android.net.Uri
+import androidx.core.net.toUri
+import fr.free.nrw.commons.BuildConfig
+import fr.free.nrw.commons.di.CommonsDaggerContentProvider
+import fr.free.nrw.commons.explore.recentsearches.RecentSearchesTable.ALL_FIELDS
+import fr.free.nrw.commons.explore.recentsearches.RecentSearchesTable.COLUMN_ID
+import fr.free.nrw.commons.explore.recentsearches.RecentSearchesTable.TABLE_NAME
+
+/**
+ * This class contains functions for executing queries for
+ * inserting, searching, deleting, editing recent searches in SqLite DB
+ */
+class RecentSearchesContentProvider : CommonsDaggerContentProvider() {
+
+ /**
+ * This functions executes query for searching recent searches in SqLite DB
+ */
+ override fun query(
+ uri: Uri, projection: Array?, selection: String?,
+ selectionArgs: Array?, sortOrder: String?
+ ): Cursor {
+ val queryBuilder = SQLiteQueryBuilder().apply {
+ tables = TABLE_NAME
+ }
+
+ val uriType = uriMatcher.match(uri)
+
+ val cursor = when (uriType) {
+ RECENT_SEARCHES -> queryBuilder.query(
+ requireDb(), projection, selection, selectionArgs,
+ null, null, sortOrder
+ )
+
+ RECENT_SEARCHES_ID -> queryBuilder.query(
+ requireDb(),
+ ALL_FIELDS,
+ "$COLUMN_ID = ?",
+ arrayOf(uri.lastPathSegment),
+ null,
+ null,
+ sortOrder
+ )
+
+ else -> throw IllegalArgumentException("Unknown URI$uri")
+ }
+
+ cursor.setNotificationUri(context?.contentResolver, uri)
+
+ return cursor
+ }
+
+ override fun getType(uri: Uri): String? = null
+
+ /**
+ * This functions executes query for inserting a recentSearch object in SqLite DB
+ */
+ override fun insert(uri: Uri, contentValues: ContentValues?): Uri? {
+ val uriType = uriMatcher.match(uri)
+ val id: Long = when (uriType) {
+ RECENT_SEARCHES -> requireDb().insert(TABLE_NAME, null, contentValues)
+
+ else -> throw IllegalArgumentException("Unknown URI: $uri")
+ }
+ context?.contentResolver?.notifyChange(uri, null)
+ return "$BASE_URI/$id".toUri()
+ }
+
+ /**
+ * This functions executes query for deleting a recentSearch object in SqLite DB
+ */
+ override fun delete(uri: Uri, s: String?, strings: Array?): Int {
+ val rows: Int
+ val uriType = uriMatcher.match(uri)
+ when (uriType) {
+ RECENT_SEARCHES_ID -> {
+ rows = requireDb().delete(
+ TABLE_NAME,
+ "_id = ?",
+ arrayOf(uri.lastPathSegment)
+ )
+ }
+
+ else -> throw IllegalArgumentException("Unknown URI - $uri")
+ }
+ context?.contentResolver?.notifyChange(uri, null)
+ return rows
+ }
+
+ /**
+ * This functions executes query for inserting multiple recentSearch objects in SqLite DB
+ */
+ override fun bulkInsert(uri: Uri, values: Array): Int {
+ val uriType = uriMatcher.match(uri)
+ val sqlDB = requireDb()
+ sqlDB.beginTransaction()
+ when (uriType) {
+ RECENT_SEARCHES -> for (value in values) {
+ sqlDB.insert(TABLE_NAME, null, value)
+ }
+
+ else -> throw IllegalArgumentException("Unknown URI: $uri")
+ }
+ sqlDB.setTransactionSuccessful()
+ sqlDB.endTransaction()
+ context?.contentResolver?.notifyChange(uri, null)
+ return values.size
+ }
+
+ /**
+ * This functions executes query for updating a particular recentSearch object in SqLite DB
+ */
+ override fun update(
+ uri: Uri, contentValues: ContentValues?, selection: String?,
+ selectionArgs: Array?
+ ): Int {
+ /*
+ SQL Injection warnings: First, note that we're not exposing this to the
+ outside world (exported="false"). Even then, we should make sure to sanitize
+ all user input appropriately. Input that passes through ContentValues
+ should be fine. So only issues are those that pass in via concating.
+
+ In here, the only concat created argument is for id. It is cast to an int,
+ and will error out otherwise.
+ */
+ val uriType = uriMatcher.match(uri)
+ val rowsUpdated: Int
+ when (uriType) {
+ RECENT_SEARCHES_ID -> if (selection.isNullOrEmpty()) {
+ val id = uri.lastPathSegment!!.toInt()
+ rowsUpdated = requireDb().update(
+ TABLE_NAME,
+ contentValues,
+ "$COLUMN_ID = ?",
+ arrayOf(id.toString())
+ )
+ } else {
+ throw IllegalArgumentException(
+ "Parameter `selection` should be empty when updating an ID"
+ )
+ }
+
+ else -> throw IllegalArgumentException("Unknown URI: $uri with type $uriType")
+ }
+ context?.contentResolver?.notifyChange(uri, null)
+ return rowsUpdated
+ }
+
+ companion object {
+ // For URI matcher
+ private const val RECENT_SEARCHES = 1
+ private const val RECENT_SEARCHES_ID = 2
+ private const val BASE_PATH = "recent_searches"
+
+ @JvmField
+ val BASE_URI: Uri = "content://${BuildConfig.RECENT_SEARCH_AUTHORITY}/$BASE_PATH".toUri()
+
+ private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH)
+
+ init {
+ uriMatcher.addURI(BuildConfig.RECENT_SEARCH_AUTHORITY, BASE_PATH, RECENT_SEARCHES)
+ uriMatcher.addURI(BuildConfig.RECENT_SEARCH_AUTHORITY, "$BASE_PATH/#", RECENT_SEARCHES_ID)
+ }
+
+ @JvmStatic
+ fun uriForId(id: Int): Uri = "$BASE_URI/$id".toUri()
+ }
+}
+
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesDao.java b/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesDao.java
deleted file mode 100644
index cee8a25ae..000000000
--- a/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesDao.java
+++ /dev/null
@@ -1,275 +0,0 @@
-package fr.free.nrw.commons.explore.recentsearches;
-
-import android.annotation.SuppressLint;
-import android.content.ContentProviderClient;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.os.RemoteException;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import fr.free.nrw.commons.explore.models.RecentSearch;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-import javax.inject.Provider;
-
-import timber.log.Timber;
-
-/**
- * This class doesn't execute queries in database directly instead it contains the logic behind
- * inserting, deleting, searching data from recent searches database.
- **/
-public class RecentSearchesDao {
-
- private final Provider clientProvider;
-
- @Inject
- public RecentSearchesDao(@Named("recentsearch") Provider clientProvider) {
- this.clientProvider = clientProvider;
- }
-
- /**
- * This method is called on click of media/ categories for storing them in recent searches
- * @param recentSearch a recent searches object that is to be added in SqLite DB
- */
- public void save(RecentSearch recentSearch) {
- ContentProviderClient db = clientProvider.get();
- try {
- if (recentSearch.getContentUri() == null) {
- recentSearch.setContentUri(db.insert(RecentSearchesContentProvider.BASE_URI, toContentValues(recentSearch)));
- } else {
- db.update(recentSearch.getContentUri(), toContentValues(recentSearch), null, null);
- }
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- } finally {
- db.release();
- }
- }
-
- /**
- * This method is called on confirmation of delete recent searches.
- * It deletes all recent searches from the database
- */
- public void deleteAll() {
- Cursor cursor = null;
- ContentProviderClient db = clientProvider.get();
- try {
- cursor = db.query(
- RecentSearchesContentProvider.BASE_URI,
- Table.ALL_FIELDS,
- null,
- new String[]{},
- Table.COLUMN_LAST_USED + " DESC"
- );
- while (cursor != null && cursor.moveToNext()) {
- try {
- RecentSearch recentSearch = find(fromCursor(cursor).getQuery());
- if (recentSearch.getContentUri() == null) {
- throw new RuntimeException("tried to delete item with no content URI");
- } else {
- Timber.d("QUERY_NAME %s - delete tried", recentSearch.getContentUri());
- db.delete(recentSearch.getContentUri(), null, null);
- Timber.d("QUERY_NAME %s - query deleted", recentSearch.getQuery());
- }
- } catch (RemoteException e) {
- Timber.e(e, "query deleted");
- throw new RuntimeException(e);
- } finally {
- db.release();
- }
- }
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- }
-
- /**
- * Deletes a recent search from the database
- */
- public void delete(RecentSearch recentSearch) {
-
- ContentProviderClient db = clientProvider.get();
- try {
- if (recentSearch.getContentUri() == null) {
- throw new RuntimeException("tried to delete item with no content URI");
- } else {
- db.delete(recentSearch.getContentUri(), null, null);
- }
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- } finally {
- db.release();
- }
- }
-
-
- /**
- * Find persisted search query in database, based on its name.
- * @param name Search query Ex- "butterfly"
- * @return recently searched query from database, or null if not found
- */
- @Nullable
- public RecentSearch find(String name) {
- Cursor cursor = null;
- ContentProviderClient db = clientProvider.get();
- try {
- cursor = db.query(
- RecentSearchesContentProvider.BASE_URI,
- Table.ALL_FIELDS,
- Table.COLUMN_NAME + "=?",
- new String[]{name},
- null);
- if (cursor != null && cursor.moveToFirst()) {
- return fromCursor(cursor);
- }
- } catch (RemoteException e) {
- // This feels lazy, but to hell with checked exceptions. :)
- throw new RuntimeException(e);
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- db.release();
- }
- return null;
- }
-
- /**
- * Retrieve recently-searched queries, ordered by descending date.
- * @return a list containing recent searches
- */
- @NonNull
- public List recentSearches(int limit) {
- List items = new ArrayList<>();
- Cursor cursor = null;
- ContentProviderClient db = clientProvider.get();
- try {
- cursor = db.query( RecentSearchesContentProvider.BASE_URI, Table.ALL_FIELDS,
- null, new String[]{}, Table.COLUMN_LAST_USED + " DESC");
- // fixme add a limit on the original query instead of falling out of the loop?
- while (cursor != null && cursor.moveToNext() && cursor.getPosition() < limit) {
- items.add(fromCursor(cursor).getQuery());
- }
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- db.release();
- }
- return items;
- }
-
-
- /**
- * It creates an Recent Searches object from data stored in the SQLite DB by using cursor
- * @param cursor
- * @return RecentSearch object
- */
- @NonNull
- @SuppressLint("Range")
- RecentSearch fromCursor(Cursor cursor) {
- // Hardcoding column positions!
- return new RecentSearch(
- RecentSearchesContentProvider.uriForId(cursor.getInt(cursor.getColumnIndex(Table.COLUMN_ID))),
- cursor.getString(cursor.getColumnIndex(Table.COLUMN_NAME)),
- new Date(cursor.getLong(cursor.getColumnIndex(Table.COLUMN_LAST_USED)))
- );
- }
-
- /**
- * This class contains the database table architechture for recent searches,
- * It also contains queries and logic necessary to the create, update, delete this table.
- */
- private ContentValues toContentValues(RecentSearch recentSearch) {
- ContentValues cv = new ContentValues();
- cv.put(RecentSearchesDao.Table.COLUMN_NAME, recentSearch.getQuery());
- cv.put(RecentSearchesDao.Table.COLUMN_LAST_USED, recentSearch.getLastSearched().getTime());
- return cv;
- }
-
- /**
- * This class contains the database table architechture for recent searches,
- * It also contains queries and logic necessary to the create, update, delete this table.
- */
- public static class Table {
- public static final String TABLE_NAME = "recent_searches";
- public static final String COLUMN_ID = "_id";
- static final String COLUMN_NAME = "name";
- static final String COLUMN_LAST_USED = "last_used";
-
- // NOTE! KEEP IN SAME ORDER AS THEY ARE DEFINED UP THERE. HELPS HARD CODE COLUMN INDICES.
- public static final String[] ALL_FIELDS = {
- COLUMN_ID,
- COLUMN_NAME,
- COLUMN_LAST_USED,
- };
-
- static final String DROP_TABLE_STATEMENT = "DROP TABLE IF EXISTS " + TABLE_NAME;
-
- static final String CREATE_TABLE_STATEMENT = "CREATE TABLE " + TABLE_NAME + " ("
- + COLUMN_ID + " INTEGER PRIMARY KEY,"
- + COLUMN_NAME + " STRING,"
- + COLUMN_LAST_USED + " INTEGER"
- + ");";
-
- /**
- * This method creates a RecentSearchesTable in SQLiteDatabase
- * @param db SQLiteDatabase
- */
- public static void onCreate(SQLiteDatabase db) {
- db.execSQL(CREATE_TABLE_STATEMENT);
- }
-
- /**
- * This method deletes RecentSearchesTable from SQLiteDatabase
- * @param db SQLiteDatabase
- */
- public static void onDelete(SQLiteDatabase db) {
- db.execSQL(DROP_TABLE_STATEMENT);
- onCreate(db);
- }
-
- /**
- * This method is called on migrating from a older version to a newer version
- * @param db SQLiteDatabase
- * @param from Version from which we are migrating
- * @param to Version to which we are migrating
- */
- public static void onUpdate(SQLiteDatabase db, int from, int to) {
- if (from == to) {
- return;
- }
- if (from < 6) {
- // doesn't exist yet
- from++;
- onUpdate(db, from, to);
- return;
- }
- if (from == 6) {
- // table added in version 7
- onCreate(db);
- from++;
- onUpdate(db, from, to);
- return;
- }
- if (from == 7) {
- from++;
- onUpdate(db, from, to);
- return;
- }
- }
- }
-}
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesDao.kt b/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesDao.kt
new file mode 100644
index 000000000..d16d250dd
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesDao.kt
@@ -0,0 +1,188 @@
+package fr.free.nrw.commons.explore.recentsearches
+
+import android.annotation.SuppressLint
+import android.content.ContentProviderClient
+import android.content.ContentValues
+import android.database.Cursor
+import android.os.RemoteException
+import androidx.core.content.contentValuesOf
+import fr.free.nrw.commons.explore.models.RecentSearch
+import fr.free.nrw.commons.explore.recentsearches.RecentSearchesContentProvider.Companion.BASE_URI
+import fr.free.nrw.commons.explore.recentsearches.RecentSearchesContentProvider.Companion.uriForId
+import fr.free.nrw.commons.explore.recentsearches.RecentSearchesTable.ALL_FIELDS
+import fr.free.nrw.commons.explore.recentsearches.RecentSearchesTable.COLUMN_ID
+import fr.free.nrw.commons.explore.recentsearches.RecentSearchesTable.COLUMN_LAST_USED
+import fr.free.nrw.commons.explore.recentsearches.RecentSearchesTable.COLUMN_NAME
+import fr.free.nrw.commons.utils.getInt
+import fr.free.nrw.commons.utils.getLong
+import fr.free.nrw.commons.utils.getString
+import java.util.Date
+import javax.inject.Inject
+import javax.inject.Named
+import javax.inject.Provider
+
+/**
+ * This class doesn't execute queries in database directly instead it contains the logic behind
+ * inserting, deleting, searching data from recent searches database.
+ */
+class RecentSearchesDao @Inject constructor(
+ @param:Named("recentsearch") private val clientProvider: Provider
+) {
+ /**
+ * This method is called on click of media/ categories for storing them in recent searches
+ * @param recentSearch a recent searches object that is to be added in SqLite DB
+ */
+ fun save(recentSearch: RecentSearch) {
+ val db = clientProvider.get()
+ try {
+ val contentValues = toContentValues(recentSearch)
+ if (recentSearch.contentUri == null) {
+ recentSearch.contentUri = db.insert(BASE_URI, contentValues)
+ } else {
+ db.update(recentSearch.contentUri!!, contentValues, null, null)
+ }
+ } catch (e: RemoteException) {
+ throw RuntimeException(e)
+ } finally {
+ db.release()
+ }
+ }
+
+ /**
+ * This method is called on confirmation of delete recent searches.
+ * It deletes all recent searches from the database
+ */
+ fun deleteAll() {
+ var cursor: Cursor? = null
+ val db = clientProvider.get()
+ try {
+ cursor = db.query(
+ BASE_URI,
+ ALL_FIELDS,
+ null,
+ arrayOf(),
+ "$COLUMN_LAST_USED DESC"
+ )
+ while (cursor != null && cursor.moveToNext()) {
+ try {
+ val recentSearch = find(fromCursor(cursor).query)
+ if (recentSearch!!.contentUri == null) {
+ throw RuntimeException("tried to delete item with no content URI")
+ } else {
+ db.delete(recentSearch.contentUri!!, null, null)
+ }
+ } catch (e: RemoteException) {
+ throw RuntimeException(e)
+ } finally {
+ db.release()
+ }
+ }
+ } catch (e: RemoteException) {
+ throw RuntimeException(e)
+ } finally {
+ cursor?.close()
+ }
+ }
+
+ /**
+ * Deletes a recent search from the database
+ */
+ fun delete(recentSearch: RecentSearch) {
+ val db = clientProvider.get()
+ try {
+ if (recentSearch.contentUri == null) {
+ throw RuntimeException("tried to delete item with no content URI")
+ } else {
+ db.delete(recentSearch.contentUri!!, null, null)
+ }
+ } catch (e: RemoteException) {
+ throw RuntimeException(e)
+ } finally {
+ db.release()
+ }
+ }
+
+
+ /**
+ * Find persisted search query in database, based on its name.
+ * @param name Search query Ex- "butterfly"
+ * @return recently searched query from database, or null if not found
+ */
+ fun find(name: String): RecentSearch? {
+ var cursor: Cursor? = null
+ val db = clientProvider.get()
+ try {
+ cursor = db.query(
+ BASE_URI,
+ ALL_FIELDS,
+ "$COLUMN_NAME=?",
+ arrayOf(name),
+ null
+ )
+ if (cursor != null && cursor.moveToFirst()) {
+ return fromCursor(cursor)
+ }
+ } catch (e: RemoteException) {
+ // This feels lazy, but to hell with checked exceptions. :)
+ throw RuntimeException(e)
+ } finally {
+ cursor?.close()
+ db.release()
+ }
+ return null
+ }
+
+ /**
+ * Retrieve recently-searched queries, ordered by descending date.
+ * @return a list containing recent searches
+ */
+ fun recentSearches(limit: Int): List {
+ val items: MutableList = mutableListOf()
+ var cursor: Cursor? = null
+ val db = clientProvider.get()
+ try {
+ cursor = db.query(
+ BASE_URI, ALL_FIELDS,
+ null, arrayOf(), "$COLUMN_LAST_USED DESC"
+ )
+ // fixme add a limit on the original query instead of falling out of the loop?
+ while (cursor != null && cursor.moveToNext() && cursor.position < limit) {
+ items.add(fromCursor(cursor).query)
+ }
+ } catch (e: RemoteException) {
+ throw RuntimeException(e)
+ } finally {
+ cursor?.close()
+ db.release()
+ }
+ return items
+ }
+
+ /**
+ * It creates an Recent Searches object from data stored in the SQLite DB by using cursor
+ * @param cursor
+ * @return RecentSearch object
+ */
+ fun fromCursor(cursor: Cursor): RecentSearch {
+ var query = cursor.getString(COLUMN_NAME)
+
+ if (query == null) {
+ query = ""
+ }
+
+ return RecentSearch(
+ uriForId(cursor.getInt(COLUMN_ID)),
+ query,
+ Date(cursor.getLong(COLUMN_LAST_USED))
+ )
+ }
+
+ /**
+ * This class contains the database table architechture for recent searches,
+ * It also contains queries and logic necessary to the create, update, delete this table.
+ */
+ private fun toContentValues(recentSearch: RecentSearch): ContentValues = contentValuesOf(
+ COLUMN_NAME to recentSearch.query,
+ COLUMN_LAST_USED to recentSearch.lastSearched.time
+ )
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesFragment.java b/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesFragment.java
deleted file mode 100644
index 588f3a25f..000000000
--- a/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesFragment.java
+++ /dev/null
@@ -1,149 +0,0 @@
-package fr.free.nrw.commons.explore.recentsearches;
-
-import android.content.Context;
-import android.content.DialogInterface;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
-import android.widget.Toast;
-import androidx.annotation.NonNull;
-import androidx.appcompat.app.AlertDialog;
-import fr.free.nrw.commons.R;
-import fr.free.nrw.commons.databinding.FragmentSearchHistoryBinding;
-import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
-import fr.free.nrw.commons.explore.SearchActivity;
-import java.util.List;
-import java.util.Locale;
-import javax.inject.Inject;
-
-
-/**
- * Displays the recent searches screen.
- */
-public class RecentSearchesFragment extends CommonsDaggerSupportFragment {
-
- @Inject
- RecentSearchesDao recentSearchesDao;
- List recentSearches;
- ArrayAdapter adapter;
-
- private FragmentSearchHistoryBinding binding;
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- binding = FragmentSearchHistoryBinding.inflate(inflater, container, false);
-
- recentSearches = recentSearchesDao.recentSearches(10);
-
- if (recentSearches.isEmpty()) {
- binding.recentSearchesDeleteButton.setVisibility(View.GONE);
- binding.recentSearchesTextView.setText(R.string.no_recent_searches);
- }
-
- binding.recentSearchesDeleteButton.setOnClickListener(v -> {
- showDeleteRecentAlertDialog(requireContext());
- });
-
- adapter = new ArrayAdapter<>(requireContext(), R.layout.item_recent_searches,
- recentSearches);
- binding.recentSearchesList.setAdapter(adapter);
- binding.recentSearchesList.setOnItemClickListener((parent, view, position, id) -> (
- (SearchActivity) getContext()).updateText(recentSearches.get(position)));
- binding.recentSearchesList.setOnItemLongClickListener((parent, view, position, id) -> {
- showDeleteAlertDialog(requireContext(), position);
- return true;
- });
- updateRecentSearches();
-
- return binding.getRoot();
- }
-
- private void showDeleteRecentAlertDialog(@NonNull final Context context) {
- new AlertDialog.Builder(context)
- .setMessage(getString(R.string.delete_recent_searches_dialog))
- .setPositiveButton(android.R.string.yes,
- (dialog, which) -> setDeleteRecentPositiveButton(context, dialog))
- .setNegativeButton(android.R.string.no, null)
- .setCancelable(false)
- .create()
- .show();
- }
-
- private void setDeleteRecentPositiveButton(@NonNull final Context context,
- final DialogInterface dialog) {
- recentSearchesDao.deleteAll();
- if (binding != null) {
- binding.recentSearchesDeleteButton.setVisibility(View.GONE);
- binding.recentSearchesTextView.setText(R.string.no_recent_searches);
- Toast.makeText(getContext(), getString(R.string.search_history_deleted),
- Toast.LENGTH_SHORT).show();
- recentSearches = recentSearchesDao.recentSearches(10);
- adapter = new ArrayAdapter<>(context, R.layout.item_recent_searches,
- recentSearches);
- binding.recentSearchesList.setAdapter(adapter);
- adapter.notifyDataSetChanged();
- }
- dialog.dismiss();
- }
-
- private void showDeleteAlertDialog(@NonNull final Context context, final int position) {
- new AlertDialog.Builder(context)
- .setMessage(R.string.delete_search_dialog)
- .setPositiveButton(getString(R.string.delete).toUpperCase(Locale.ROOT),
- ((dialog, which) -> setDeletePositiveButton(context, dialog, position)))
- .setNegativeButton(android.R.string.cancel, null)
- .setCancelable(false)
- .create()
- .show();
- }
-
- private void setDeletePositiveButton(@NonNull final Context context,
- final DialogInterface dialog, final int position) {
- recentSearchesDao.delete(recentSearchesDao.find(recentSearches.get(position)));
- recentSearches = recentSearchesDao.recentSearches(10);
- adapter = new ArrayAdapter<>(context, R.layout.item_recent_searches,
- recentSearches);
- if (binding != null){
- binding.recentSearchesList.setAdapter(adapter);
- adapter.notifyDataSetChanged();
- }
- dialog.dismiss();
- }
-
- /**
- * This method is called on back press of activity so we are updating the list from database to
- * refresh the recent searches list.
- */
- @Override
- public void onResume() {
- updateRecentSearches();
- super.onResume();
- }
-
- /**
- * This method is called when search query is null to update Recent Searches
- */
- public void updateRecentSearches() {
- recentSearches = recentSearchesDao.recentSearches(10);
- adapter.notifyDataSetChanged();
-
- if (!recentSearches.isEmpty()) {
- if (binding!= null) {
- binding.recentSearchesDeleteButton.setVisibility(View.VISIBLE);
- binding.recentSearchesTextView.setText(R.string.search_recent_header);
- }
- }
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
-
- if (binding != null) {
- binding = null;
- }
- }
-}
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesFragment.kt b/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesFragment.kt
new file mode 100644
index 000000000..e7903c9ed
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesFragment.kt
@@ -0,0 +1,153 @@
+package fr.free.nrw.commons.explore.recentsearches
+
+import android.content.Context
+import android.content.DialogInterface
+import android.content.DialogInterface.OnClickListener
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.AdapterView
+import android.widget.AdapterView.OnItemClickListener
+import android.widget.AdapterView.OnItemLongClickListener
+import android.widget.ArrayAdapter
+import android.widget.Toast
+import androidx.appcompat.app.AlertDialog
+import fr.free.nrw.commons.R
+import fr.free.nrw.commons.databinding.FragmentSearchHistoryBinding
+import fr.free.nrw.commons.di.CommonsDaggerSupportFragment
+import fr.free.nrw.commons.explore.SearchActivity
+import javax.inject.Inject
+
+/**
+ * Displays the recent searches screen.
+ */
+class RecentSearchesFragment : CommonsDaggerSupportFragment() {
+ @JvmField
+ @Inject
+ var recentSearchesDao: RecentSearchesDao? = null
+
+ private var recentSearches: List = emptyList()
+ private lateinit var adapter: ArrayAdapter
+ private var binding: FragmentSearchHistoryBinding? = null
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ binding = FragmentSearchHistoryBinding.inflate(inflater, container, false)
+
+ recentSearches = recentSearchesDao!!.recentSearches(10)
+
+ if (recentSearches.isEmpty()) {
+ binding!!.recentSearchesDeleteButton.visibility = View.GONE
+ binding!!.recentSearchesTextView.setText(R.string.no_recent_searches)
+ }
+
+ binding!!.recentSearchesDeleteButton.setOnClickListener { v: View? ->
+ showDeleteRecentAlertDialog(requireContext())
+ }
+
+ adapter = ArrayAdapter(requireContext(), R.layout.item_recent_searches, recentSearches)
+ binding!!.recentSearchesList.adapter = adapter
+ binding!!.recentSearchesList.onItemClickListener =
+ OnItemClickListener { _: AdapterView<*>?, _: View?, position: Int, _: Long ->
+ (context as SearchActivity).updateText(recentSearches[position])
+ }
+ binding!!.recentSearchesList.onItemLongClickListener =
+ OnItemLongClickListener { _: AdapterView<*>?, _: View?, position: Int, _: Long ->
+ showDeleteAlertDialog(requireContext(), position)
+ true
+ }
+ updateRecentSearches()
+
+ return binding!!.root
+ }
+
+ private fun showDeleteRecentAlertDialog(context: Context) {
+ AlertDialog.Builder(context)
+ .setMessage(getString(R.string.delete_recent_searches_dialog))
+ .setPositiveButton(R.string.yes) { dialog: DialogInterface, _: Int ->
+ setDeleteRecentPositiveButton(context, dialog)
+ }
+ .setNegativeButton(R.string.no, null)
+ .setCancelable(false)
+ .create()
+ .show()
+ }
+
+ private fun setDeleteRecentPositiveButton(context: Context, dialog: DialogInterface) {
+ recentSearchesDao!!.deleteAll()
+ if (binding != null) {
+ binding!!.recentSearchesDeleteButton.visibility = View.GONE
+ binding!!.recentSearchesTextView.setText(R.string.no_recent_searches)
+ Toast.makeText(
+ getContext(), getString(R.string.search_history_deleted),
+ Toast.LENGTH_SHORT
+ ).show()
+ recentSearches = recentSearchesDao!!.recentSearches(10)
+ adapter = ArrayAdapter(context, R.layout.item_recent_searches, recentSearches)
+ binding!!.recentSearchesList.adapter = adapter
+ adapter.notifyDataSetChanged()
+ }
+ dialog.dismiss()
+ }
+
+ private fun showDeleteAlertDialog(context: Context, position: Int) {
+ AlertDialog.Builder(context)
+ .setMessage(R.string.delete_search_dialog)
+ .setPositiveButton(
+ getString(R.string.delete).uppercase(),
+ { dialog: DialogInterface, _: Int ->
+ setDeletePositiveButton(context, dialog, position)
+ }
+ )
+ .setNegativeButton(R.string.cancel, null)
+ .setCancelable(false)
+ .create()
+ .show()
+ }
+
+ private fun setDeletePositiveButton(context: Context, dialog: DialogInterface, position: Int) {
+ recentSearchesDao!!.delete(recentSearchesDao!!.find(recentSearches[position])!!)
+ recentSearches = recentSearchesDao!!.recentSearches(10)
+ adapter = ArrayAdapter(
+ context, R.layout.item_recent_searches,
+ recentSearches
+ )
+ if (binding != null) {
+ binding!!.recentSearchesList.adapter = adapter
+ adapter.notifyDataSetChanged()
+ }
+ dialog.dismiss()
+ }
+
+ /**
+ * This method is called on back press of activity so we are updating the list from database to
+ * refresh the recent searches list.
+ */
+ override fun onResume() {
+ updateRecentSearches()
+ super.onResume()
+ }
+
+ /**
+ * This method is called when search query is null to update Recent Searches
+ */
+ fun updateRecentSearches() {
+ recentSearches = recentSearchesDao!!.recentSearches(10)
+ adapter.notifyDataSetChanged()
+
+ if (recentSearches.isNotEmpty()) {
+ if (binding != null) {
+ binding!!.recentSearchesDeleteButton.visibility = View.VISIBLE
+ binding!!.recentSearchesTextView.setText(R.string.search_recent_header)
+ }
+ }
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ binding = null
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesTable.kt b/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesTable.kt
new file mode 100644
index 000000000..e32fc9fa4
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesTable.kt
@@ -0,0 +1,71 @@
+package fr.free.nrw.commons.explore.recentsearches
+
+import android.database.sqlite.SQLiteDatabase
+
+/**
+ * This class contains the database table architechture for recent searches, It also contains
+ * queries and logic necessary to the create, update, delete this table.
+ */
+object RecentSearchesTable {
+ const val TABLE_NAME: String = "recent_searches"
+ const val COLUMN_ID: String = "_id"
+ const val COLUMN_NAME: String = "name"
+ const val COLUMN_LAST_USED: String = "last_used"
+
+ // NOTE! KEEP IN SAME ORDER AS THEY ARE DEFINED UP THERE. HELPS HARD CODE COLUMN INDICES.
+ @JvmField
+ val ALL_FIELDS = arrayOf(
+ COLUMN_ID,
+ COLUMN_NAME,
+ COLUMN_LAST_USED,
+ )
+
+ const val DROP_TABLE_STATEMENT: String = "DROP TABLE IF EXISTS $TABLE_NAME"
+
+ const val CREATE_TABLE_STATEMENT: String = ("CREATE TABLE $TABLE_NAME ($COLUMN_ID INTEGER PRIMARY KEY,$COLUMN_NAME STRING,$COLUMN_LAST_USED INTEGER);")
+
+ /**
+ * This method creates a RecentSearchesTable in SQLiteDatabase
+ *
+ * @param db SQLiteDatabase
+ */
+ fun onCreate(db: SQLiteDatabase) = db.execSQL(CREATE_TABLE_STATEMENT)
+
+ /**
+ * This method deletes RecentSearchesTable from SQLiteDatabase
+ *
+ * @param db SQLiteDatabase
+ */
+ fun onDelete(db: SQLiteDatabase) {
+ db.execSQL(DROP_TABLE_STATEMENT)
+ onCreate(db)
+ }
+
+ /**
+ * This method is called on migrating from a older version to a newer version
+ *
+ * @param db SQLiteDatabase
+ * @param from Version from which we are migrating
+ * @param to Version to which we are migrating
+ */
+ fun onUpdate(db: SQLiteDatabase, from: Int, to: Int) {
+ if (from == to) {
+ return
+ }
+ if (from < 6) {
+ // doesn't exist yet
+ onUpdate(db, from + 1, to)
+ return
+ }
+ if (from == 6) {
+ // table added in version 7
+ onCreate(db)
+ onUpdate(db, from + 1, to)
+ return
+ }
+ if (from == 7) {
+ onUpdate(db, from + 1, to)
+ return
+ }
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/filepicker/FilePicker.kt b/app/src/main/java/fr/free/nrw/commons/filepicker/FilePicker.kt
index bb0a371e1..a7e3a671d 100644
--- a/app/src/main/java/fr/free/nrw/commons/filepicker/FilePicker.kt
+++ b/app/src/main/java/fr/free/nrw/commons/filepicker/FilePicker.kt
@@ -296,10 +296,19 @@ object FilePicker : Constants {
* https://github.com/commons-app/apps-android-commons/issues/6357
*/
private fun takePersistableUriPermissions(context: Context, result: ActivityResult) {
- result.data?.data?.also { uri ->
- val takeFlags: Int = (Intent.FLAG_GRANT_READ_URI_PERMISSION
- or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
- context.contentResolver.takePersistableUriPermission(uri, takeFlags)
+ result.data?.let { intentData ->
+ val takeFlags: Int = (Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ // Persist the URI permission for all URIs in the clip data
+ // if multiple images are selected,
+ // or for the single URI if only one image is selected
+ intentData.clipData?.let { clipData ->
+ for (i in 0 until clipData.itemCount) {
+ context.contentResolver.takePersistableUriPermission(
+ clipData.getItemAt(i).uri, takeFlags)
+ }
+ } ?: intentData.data?.let { uri ->
+ context.contentResolver.takePersistableUriPermission(uri, takeFlags)
+ }
}
}
@@ -358,6 +367,7 @@ object FilePicker : Constants {
callbacks: Callbacks
) {
if (result.resultCode == Activity.RESULT_OK && !isPhoto(result.data)) {
+ takePersistableUriPermissions(activity, result)
try {
val files = getFilesFromGalleryPictures(result.data, activity)
callbacks.onImagesPicked(files, ImageSource.GALLERY, restoreType(activity))
diff --git a/app/src/main/java/fr/free/nrw/commons/fileusages/FileUsagesUiModel.kt b/app/src/main/java/fr/free/nrw/commons/fileusages/FileUsagesUiModel.kt
index 63b0740d0..540c87e4c 100644
--- a/app/src/main/java/fr/free/nrw/commons/fileusages/FileUsagesUiModel.kt
+++ b/app/src/main/java/fr/free/nrw/commons/fileusages/FileUsagesUiModel.kt
@@ -1,18 +1,68 @@
package fr.free.nrw.commons.fileusages
+import android.net.Uri
+import timber.log.Timber
+
/**
- * Show where file is being used on Commons and oher wikis.
+ * Data model for displaying file usage information in the UI, including the title and link to the page.
*/
data class FileUsagesUiModel(
val title: String,
val link: String?
)
+/**
+ * Converts a FileUsage object to a UI model for Commons file usages.
+ * Creates a link to the file's page on Commons.
+ */
fun FileUsage.toUiModel(): FileUsagesUiModel {
- return FileUsagesUiModel(title = title, link = "https://commons.wikimedia.org/wiki/$title")
+ // Replace spaces with underscores and URL-encode the title for the link
+ val encodedTitle = Uri.encode(title.replace(" ", "_"))
+ return FileUsagesUiModel(
+ title = title,
+ link = "https://commons.wikimedia.org/wiki/$encodedTitle"
+ )
}
+/**
+ * Converts a GlobalFileUsage object to a UI model for file usages on other wikis.
+ * Generates a link to the page and prefixes the title with the wiki code (e.g., "(en) Title").
+ */
fun GlobalFileUsage.toUiModel(): FileUsagesUiModel {
- // link is associated with sub items under wiki group (which is not used ATM)
- return FileUsagesUiModel(title = wiki, link = null)
-}
+ // Log input values for debugging
+ Timber.d("Converting GlobalFileUsage: wiki=$wiki, title=$title")
+
+ // Check for invalid or empty inputs
+ if (wiki.isBlank() || title.isBlank()) {
+ Timber.w("Invalid input: wiki=$wiki, title=$title")
+ return FileUsagesUiModel(title = title, link = null)
+ }
+
+ // Extract wiki code for prefix (e.g., "en" from "en.wikipedia.org" or "enwiki")
+ val wikiCode = when {
+ wiki.contains(".") -> wiki.substringBefore(".") // e.g., "en" from "en.wikipedia.org"
+ wiki == "commonswiki" -> "commons"
+ wiki.endsWith("wiki") -> wiki.removeSuffix("wiki")
+ else -> wiki
+ }
+
+ // Create prefixed title, e.g., "(en) Changi East Depot"
+ val prefixedTitle = "($wikiCode) $title"
+
+ // Determine the domain for the URL
+ val domain = when {
+ wiki.contains(".") -> wiki // Already a full domain, e.g., "en.wikipedia.org"
+ wiki == "commonswiki" -> "commons.wikimedia.org"
+ wiki.endsWith("wiki") -> wiki.removeSuffix("wiki") + ".wikipedia.org"
+ else -> "$wiki.wikipedia.org" // Fallback for simple codes like "en"
+ }
+
+ // Normalize title: replace spaces with underscores and URL-encode
+ val encodedTitle = Uri.encode(title.replace(" ", "_"))
+
+ // Build the full URL
+ val url = "https://$domain/wiki/$encodedTitle"
+ Timber.d("Generated URL: $url")
+
+ return FileUsagesUiModel(title = prefixedTitle, link = url)
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/location/LocationPermissionsHelper.kt b/app/src/main/java/fr/free/nrw/commons/location/LocationPermissionsHelper.kt
index fefb59adb..47b4165ad 100644
--- a/app/src/main/java/fr/free/nrw/commons/location/LocationPermissionsHelper.kt
+++ b/app/src/main/java/fr/free/nrw/commons/location/LocationPermissionsHelper.kt
@@ -64,8 +64,8 @@ class LocationPermissionsHelper(
activity,
activity.getString(dialogTitleResource),
activity.getString(dialogTextResource),
- activity.getString(android.R.string.ok),
- activity.getString(android.R.string.cancel),
+ activity.getString(R.string.ok),
+ activity.getString(R.string.cancel),
{
ActivityCompat.requestPermissions(
activity,
diff --git a/app/src/main/java/fr/free/nrw/commons/locationpicker/LocationPickerActivity.kt b/app/src/main/java/fr/free/nrw/commons/locationpicker/LocationPickerActivity.kt
index a8b6ddf26..08dee587b 100644
--- a/app/src/main/java/fr/free/nrw/commons/locationpicker/LocationPickerActivity.kt
+++ b/app/src/main/java/fr/free/nrw/commons/locationpicker/LocationPickerActivity.kt
@@ -25,6 +25,7 @@ import androidx.core.content.ContextCompat
import androidx.core.content.IntentCompat
import androidx.core.os.BundleCompat
import androidx.core.text.HtmlCompat
+import androidx.core.view.WindowCompat
import com.google.android.material.floatingactionbutton.FloatingActionButton
import fr.free.nrw.commons.CameraPosition
import fr.free.nrw.commons.CommonsApplication
@@ -44,6 +45,9 @@ import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.Compani
import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.Companion.LAST_ZOOM
import fr.free.nrw.commons.utils.DialogUtil
import fr.free.nrw.commons.utils.MapUtils.ZOOM_LEVEL
+import fr.free.nrw.commons.utils.applyEdgeToEdgeBottomInsets
+import fr.free.nrw.commons.utils.applyEdgeToEdgeBottomPaddingInsets
+import fr.free.nrw.commons.utils.applyEdgeToEdgeTopPaddingInsets
import fr.free.nrw.commons.utils.handleGeoCoordinates
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
@@ -330,12 +334,19 @@ class LocationPickerActivity : BaseActivity(), LocationPermissionCallback {
*/
private fun getToolbarUI() {
val toolbar: ConstraintLayout = findViewById(R.id.location_picker_toolbar)
+ WindowCompat.getInsetsController(window, window.decorView)
+ .isAppearanceLightStatusBars = false
+ toolbar.applyEdgeToEdgeTopPaddingInsets()
largeToolbarText = findViewById(R.id.location_picker_toolbar_primary_text_view)
smallToolbarText = findViewById(R.id.location_picker_toolbar_secondary_text_view)
toolbar.setBackgroundColor(ContextCompat.getColor(this, R.color.primaryColor))
}
private fun setupMapView() {
+
+ val mapBottomLayout: ConstraintLayout = findViewById(R.id.map_bottom_layout)
+ mapBottomLayout.applyEdgeToEdgeBottomPaddingInsets()
+
requestLocationPermissions()
//If location metadata is available, move map to that location.
@@ -460,6 +471,7 @@ class LocationPickerActivity : BaseActivity(), LocationPermissionCallback {
*/
private fun addPlaceSelectedButton() {
placeSelectedButton = findViewById(R.id.location_chosen_button)
+ applyEdgeToEdgeBottomInsets(placeSelectedButton)
placeSelectedButton.setOnClickListener { placeSelected() }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.kt b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.kt
index d34c162dc..41e65ae4e 100644
--- a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.kt
+++ b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.kt
@@ -541,6 +541,7 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C
}
)
binding.progressBarEdit.visibility = View.GONE
+ binding.descriptionEdit.visibility = View.VISIBLE
}
override fun onConfigurationChanged(newConfig: Configuration) {
@@ -1026,12 +1027,12 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C
val message: String = if (result) {
context.getString(
R.string.send_thank_success_message,
- media!!.displayTitle
+ media!!.user
)
} else {
context.getString(
R.string.send_thank_failure_message,
- media!!.displayTitle
+ media!!.user
)
}
@@ -2128,22 +2129,17 @@ fun FileUsagesContainer(
val uriHandle = LocalUriHandler.current
Column(modifier = modifier) {
-
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
-
Text(
text = stringResource(R.string.usages_on_commons_heading),
textAlign = TextAlign.Center,
style = MaterialTheme.typography.titleSmall
)
-
- IconButton(onClick = {
- isCommonsListExpanded = !isCommonsListExpanded
- }) {
+ IconButton(onClick = { isCommonsListExpanded = !isCommonsListExpanded }) {
Icon(
imageVector = if (isCommonsListExpanded) Icons.Default.KeyboardArrowUp
else Icons.Default.KeyboardArrowDown,
@@ -2157,11 +2153,8 @@ fun FileUsagesContainer(
MediaDetailViewModel.FileUsagesContainerState.Loading -> {
LinearProgressIndicator()
}
-
is MediaDetailViewModel.FileUsagesContainerState.Success -> {
-
val data = commonsContainerState.data
-
if (data.isNullOrEmpty()) {
ListItem(headlineContent = {
Text(
@@ -2181,7 +2174,7 @@ fun FileUsagesContainer(
headlineContent = {
Text(
modifier = Modifier.clickable {
- uriHandle.openUri(usage.link!!)
+ usage.link?.let { uriHandle.openUri(it) }
},
text = usage.title,
style = MaterialTheme.typography.titleSmall.copy(
@@ -2189,11 +2182,11 @@ fun FileUsagesContainer(
textDecoration = TextDecoration.Underline
)
)
- })
+ }
+ )
}
}
}
-
is MediaDetailViewModel.FileUsagesContainerState.Error -> {
ListItem(headlineContent = {
Text(
@@ -2203,12 +2196,10 @@ fun FileUsagesContainer(
)
})
}
-
MediaDetailViewModel.FileUsagesContainerState.Initial -> {}
}
}
-
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
@@ -2219,10 +2210,7 @@ fun FileUsagesContainer(
textAlign = TextAlign.Center,
style = MaterialTheme.typography.titleSmall
)
-
- IconButton(onClick = {
- isOtherWikisListExpanded = !isOtherWikisListExpanded
- }) {
+ IconButton(onClick = { isOtherWikisListExpanded = !isOtherWikisListExpanded }) {
Icon(
imageVector = if (isOtherWikisListExpanded) Icons.Default.KeyboardArrowUp
else Icons.Default.KeyboardArrowDown,
@@ -2236,11 +2224,8 @@ fun FileUsagesContainer(
MediaDetailViewModel.FileUsagesContainerState.Loading -> {
LinearProgressIndicator()
}
-
is MediaDetailViewModel.FileUsagesContainerState.Success -> {
-
val data = globalContainerState.data
-
if (data.isNullOrEmpty()) {
ListItem(headlineContent = {
Text(
@@ -2259,16 +2244,20 @@ fun FileUsagesContainer(
},
headlineContent = {
Text(
+ modifier = Modifier.clickable {
+ usage.link?.let { uriHandle.openUri(it) }
+ },
text = usage.title,
style = MaterialTheme.typography.titleSmall.copy(
+ color = Color(0xFF5A6AEC),
textDecoration = TextDecoration.Underline
)
)
- })
+ }
+ )
}
}
}
-
is MediaDetailViewModel.FileUsagesContainerState.Error -> {
ListItem(headlineContent = {
Text(
@@ -2278,10 +2267,8 @@ fun FileUsagesContainer(
)
})
}
-
MediaDetailViewModel.FileUsagesContainerState.Initial -> {}
}
}
-
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.kt b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.kt
index b66c888aa..92cca611e 100644
--- a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.kt
+++ b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.kt
@@ -166,7 +166,7 @@ class MediaDetailPagerFragment : CommonsDaggerSupportFragment(), OnPageChangeLis
val mediaDetailFragment = adapter!!.currentMediaDetailFragment
when (item.itemId) {
R.id.menu_bookmark_current_image -> {
- val bookmarkExists = bookmarkDao!!.updateBookmark(bookmark)
+ val bookmarkExists = bookmarkDao!!.updateBookmark(bookmark!!)
val snackbar = if (bookmarkExists) Snackbar.make(
requireView(),
R.string.add_bookmark,
@@ -436,7 +436,7 @@ ${m.pageTitle.canonicalUri}"""
bookmark = Bookmark(
m.filename,
m.getAuthorOrUser(),
- BookmarkPicturesContentProvider.uriForName(m.filename)
+ BookmarkPicturesContentProvider.uriForName(m.filename!!)
)
updateBookmarkState(menu.findItem(R.id.menu_bookmark_current_image))
val contributionState = provider.getContributionStateAt(position)
diff --git a/app/src/main/java/fr/free/nrw/commons/navtab/MoreBottomSheetFragment.kt b/app/src/main/java/fr/free/nrw/commons/navtab/MoreBottomSheetFragment.kt
index 2d92855fc..a4f08f241 100644
--- a/app/src/main/java/fr/free/nrw/commons/navtab/MoreBottomSheetFragment.kt
+++ b/app/src/main/java/fr/free/nrw/commons/navtab/MoreBottomSheetFragment.kt
@@ -114,13 +114,13 @@ class MoreBottomSheetFragment : BottomSheetDialogFragment() {
val level = store.getString("userAchievementsLevel", "0")
if (level == "0"){
binding?.moreProfile?.text = getString(
- R.string.profileLevel,
+ R.string.profile_withoutLevel,
getUserName(),
getString(R.string.see_your_achievements) // Second argument
)
} else {
binding?.moreProfile?.text = getString(
- R.string.profileLevel,
+ R.string.profile_withLevel,
getUserName(),
level
)
diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyFilterSearchRecyclerViewAdapter.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyFilterSearchRecyclerViewAdapter.java
index b5f760c9f..53e9970a6 100644
--- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyFilterSearchRecyclerViewAdapter.java
+++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyFilterSearchRecyclerViewAdapter.java
@@ -91,6 +91,7 @@ public class NearbyFilterSearchRecyclerViewAdapter
label.setSelected(!label.isSelected());
holder.placeTypeLayout.setSelected(label.isSelected());
+ NearbyFilterState.setSelectedLabels(new ArrayList<>(selectedLabels));
callback.filterByMarkerType(selectedLabels, 0, false, false);
});
}
@@ -152,6 +153,7 @@ public class NearbyFilterSearchRecyclerViewAdapter
label.setSelected(false);
selectedLabels.remove(label);
}
+ NearbyFilterState.setSelectedLabels(new ArrayList<>(selectedLabels));
notifyDataSetChanged();
}
@@ -163,6 +165,7 @@ public class NearbyFilterSearchRecyclerViewAdapter
selectedLabels.add(label);
}
}
+ NearbyFilterState.setSelectedLabels(new ArrayList<>(selectedLabels));
notifyDataSetChanged();
}
diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyFilterState.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyFilterState.java
index d3ece9bfa..d0aec96af 100644
--- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyFilterState.java
+++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyFilterState.java
@@ -9,7 +9,7 @@ public class NearbyFilterState {
private int checkBoxTriState;
private ArrayList
+ android:text="@string/upload_categories_dont_show_this_message_again" />
\ No newline at end of file
diff --git a/app/src/main/res/layout/dialog_select_language.xml b/app/src/main/res/layout/dialog_select_language.xml
index b63d3ceac..dcef302fc 100644
--- a/app/src/main/res/layout/dialog_select_language.xml
+++ b/app/src/main/res/layout/dialog_select_language.xml
@@ -12,17 +12,17 @@
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
- android:hint="Type Language Name"
+ android:hint="@string/language_search_type_language_name"
android:padding="12dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent">
+ app:layout_constraintTop_toTopOf="parent" />
-
@@ -33,9 +32,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/filter_padding"
- android:gravity="center_vertical"
+ android:gravity="center"
tools:text="testing2"
- android:textAlignment="center"
android:textSize="22sp"/>
diff --git a/app/src/main/res/layout/fragment_upload_media_detail_fragment.xml b/app/src/main/res/layout/fragment_upload_media_detail_fragment.xml
index f803a2616..d6a08066f 100644
--- a/app/src/main/res/layout/fragment_upload_media_detail_fragment.xml
+++ b/app/src/main/res/layout/fragment_upload_media_detail_fragment.xml
@@ -14,6 +14,7 @@
app:actualImageScaleType="fitXY" />
+ android:orientation="horizontal" />
-
-
diff --git a/app/src/main/res/layout/layout_upload_categories_item.xml b/app/src/main/res/layout/layout_upload_categories_item.xml
index 1c432ca88..146d55af3 100644
--- a/app/src/main/res/layout/layout_upload_categories_item.xml
+++ b/app/src/main/res/layout/layout_upload_categories_item.xml
@@ -3,6 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/category_layout"
+ android:layoutDirection="locale"
android:layout_width="match_parent"
android:layout_height="wrap_content">
@@ -10,26 +11,31 @@
android:id="@+id/upload_category_checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:minWidth="48dp"
+ android:minHeight="48dp"
android:checkMark="?android:attr/textCheckMark"
android:checked="false"
android:gravity="center_vertical"
android:padding="@dimen/tiny_gap"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/category_image"
- app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
@@ -9,26 +10,31 @@
android:id="@+id/depict_checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:minWidth="48dp"
+ android:minHeight="48dp"
android:checkMark="?android:attr/textCheckMark"
android:checked="false"
android:gravity="center_vertical"
android:padding="@dimen/tiny_gap"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/depicted_image"
- app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
+ android:text="@string/depicts_description" />
diff --git a/app/src/main/res/layout/pic_of_day_app_widget.xml b/app/src/main/res/layout/pic_of_day_app_widget.xml
index 46e00a0f4..53e54a7cf 100644
--- a/app/src/main/res/layout/pic_of_day_app_widget.xml
+++ b/app/src/main/res/layout/pic_of_day_app_widget.xml
@@ -35,7 +35,6 @@
android:layout_height="wrap_content"
android:id="@+id/appwidget_title"
android:textAlignment="center"
- android:layout_gravity="bottom"
android:textColor="@color/white"
android:layout_marginTop="@dimen/filter_padding"
android:layout_marginStart="@dimen/tiny_padding"
@@ -47,6 +46,6 @@
android:id="@+id/appwidget_image"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:contentDescription="@string/appwidget_img" />
+ android:contentDescription="@string/appwidget_img" />
diff --git a/app/src/main/res/layout/toolbar_location_picker.xml b/app/src/main/res/layout/toolbar_location_picker.xml
index 846bb703b..78c1c5fcf 100644
--- a/app/src/main/res/layout/toolbar_location_picker.xml
+++ b/app/src/main/res/layout/toolbar_location_picker.xml
@@ -5,7 +5,8 @@
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/location_picker_toolbar"
android:layout_width="match_parent"
- android:layout_height="78dp"
+ android:layout_height="wrap_content"
+ android:paddingVertical="@dimen/small_gap"
tools:background="@color/primaryColor">
diff --git a/app/src/main/res/layout/upload_depicts_fragment.xml b/app/src/main/res/layout/upload_depicts_fragment.xml
index 9fa621396..ff4f78b3e 100644
--- a/app/src/main/res/layout/upload_depicts_fragment.xml
+++ b/app/src/main/res/layout/upload_depicts_fragment.xml
@@ -22,7 +22,6 @@
android:layout_marginEnd="@dimen/standard_gap"
android:layout_marginRight="@dimen/standard_gap"
android:layout_alignParentStart="true"
- android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:orientation="horizontal">
+ android:id="@+id/button_divider"
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:layout_above="@+id/navigation_buttons_container"
+ android:background="@color/divider_grey" />
-
-
-
+ android:gravity="end">
+
+
+
diff --git a/app/src/main/res/values-ab/strings.xml b/app/src/main/res/values-ab/strings.xml
index 9ff1b19b4..af2cf86b0 100644
--- a/app/src/main/res/values-ab/strings.xml
+++ b/app/src/main/res/values-ab/strings.xml
@@ -51,10 +51,10 @@
Иҭыгатәуп
Алицензиа
Уахынлатәи арежим
- Attribution-ShareAlike 4.0
- Attribution 4.0
- Attribution-ShareAlike 3.0
- Attribution 3.0
+ Attribution-ShareAlike 4.0
+ Attribution 4.0
+ Attribution-ShareAlike 3.0
+ Attribution 3.0
CC0
CC BY-SA 3.0
CC BY 3.0
diff --git a/app/src/main/res/values-af/strings.xml b/app/src/main/res/values-af/strings.xml
index 51aaea77e..b09af0b4f 100644
--- a/app/src/main/res/values-af/strings.xml
+++ b/app/src/main/res/values-af/strings.xml
@@ -122,10 +122,10 @@
Standaard lisensie
Gebruik vorige titel en beskrywing
Nag-modus
- Erkenning-DeelAlle 4.0
- Erkenning 4.0
- Erkenning-DeelAlik 3.0
- Erkenning 3.0
+ Erkenning-DeelAlle 4.0
+ Erkenning 4.0
+ Erkenning-DeelAlik 3.0
+ Erkenning 3.0
Op Wikimediuma Commons staan die meeste afbeeldings dit word in Wikipedia gebruik.
Uw afbeeldings help mense dwarsoor wêreld met kennis opdoen!
Upload asseblief alleen afbeeldings dit volledig deur uzelf gemaak wees:
@@ -369,5 +369,6 @@
Versoek kategorie-toets vir %1$s
Nabygeleë plek gevind
Is dit \'n foto van Plek %1$s?
+ Opsionele toestemming: kry die huidige ligging vir kategorievoorstelle
Brug to fingre for at zoome ind og ud.
diff --git a/app/src/main/res/values-anp/strings.xml b/app/src/main/res/values-anp/strings.xml
index 2eaf1c64d..1525043c1 100644
--- a/app/src/main/res/values-anp/strings.xml
+++ b/app/src/main/res/values-anp/strings.xml
@@ -60,10 +60,10 @@
रद्द करौ
डाउनलोड करौ
डिफॉल्ट लाइसेन्स
- एट्रीब्यूशन-शेयरअलाइक 3.0
- एट्रिब्यूशन 4.0
- एट्रीब्यूशन-शेयरअलाइक 3.0
- एट्रिब्यूशन 4.0
+ एट्रीब्यूशन-शेयरअलाइक 3.0
+ एट्रिब्यूशन 4.0
+ एट्रीब्यूशन-शेयरअलाइक 3.0
+ एट्रिब्यूशन 4.0
कृपया अपलोड नाय करौ:
हाँव!
बेसी जानकारी
diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml
index d3af03892..f91af9858 100644
--- a/app/src/main/res/values-ar/strings.xml
+++ b/app/src/main/res/values-ar/strings.xml
@@ -16,6 +16,7 @@
* Lolekek
* Meno25
* Mido
+* Mohammed Qays
* Monrokhoury
* Mr. Ibrahem
* NEHAOUA
@@ -207,15 +208,18 @@
لم ترفع بعد أية صور.
إعادة المحاولة
إلغاء
+ نوع اللغة الاسم
+ أحدث عمليات البحث
+ كل اللغات
بتقديم هذه الصورة، أعلن أن هذا عملي الخاص، وأنه لا يحتوي على مواد محفوظة الحقوق أو صور شخصية، وغير ذلك يلتزم <a href=\"https://commons.wikimedia.org/wiki/Commons:Policies_and_guidelines\">بسياسات ويكيميديا كومنز</a>.
نزّل
الرخصة الافتراضية
استخدم العنوان والوصف السابق
الوضع الليلي
- النسبة-الترخيص بالمثل 4.0
- الإحالة 4.0
- النسبة-الترخيص بالمثل 3.0
- الإحالة 3.0
+ النسبة-الترخيص بالمثل 4.0
+ الإحالة 4.0
+ النسبة-الترخيص بالمثل 3.0
+ الإحالة 3.0
CC0
النسبة-الترخيص بالمثل 3.0
النسبة 3.0
@@ -268,6 +272,7 @@
الوصف
نقاش
المؤلف
+ الرافع
تاريخ الرفع
الترخيص
الإحداثيات
@@ -318,6 +323,7 @@
تتطلب عملية التحميل الوصول إلى الإنترنت. يرجى التحقق من اتصال الشبكة الخاص بك.
تم العثور على مشاكل في الصورة
يُرجَى فقط رفع الصور التي التقطتها بنفسك، لا ترفع الصور التي قمت بتنزيلها من الإنترنت.
+ المرفوعات
حفظ اللقطات داخل-التطبيق In-app
حفظ الصور الملتقطة بالكاميرا داخل-التطبيق In-app على جهازك
تسجيل الدخول إلى حسابك
@@ -422,11 +428,11 @@
الملف الشخصي
أوسمة
إحصاءات
- تم تلقي الشكر
+ شكراً جزيلاً
الصور المختارة
صور عبر \"الأماكن المجاورة\"
المستوى %d
- %s (المستوى %s)
+ %s (المستوى %s)
الصور المرفوعة
لم يتم إرجاع الصور
الصور المستخدمة
@@ -491,6 +497,7 @@
ينتهي في:
عرض الحملات
انظر الحملات الجارية
+ أظهر زر الحذف
اسمح للتطبيق بجلب الموقع في حالة عدم تسجيله بالكاميرا. بعض كاميرات الأجهزة لا تسجل الموقع. في مثل هذه الحالات، فإن السماح للتطبيق بجلب الموقع وإرفاقه به يجعل مساهمتك أكثر فائدة. يمكنك تغيير هذا في أي وقت من خلال الإعدادات
السماح
رفض
@@ -737,7 +744,7 @@
إظهار في تطبيق الخريطة
أضف الموقع
عرض صورة منتقي الموقع
- ظل عرض الصورة لمنتقي الموقع
+ ظل عرض الصورة لمنتقي الموقع
مكان الصورة
تحقق مما إذا كان الموقع صحيحًا
التسمية
@@ -765,6 +772,8 @@
Wiki Loves Monuments هي مسابقة صور دولية للآثار تنظمها ويكيميديا
تحتاج إذن
تحتاج الخرائط المجاورة لقراءة PHONE STATE لتعمل بشكل صحيح
+ يرجى تشغيل خدمات الموقع لعرض الأماكن القريبة.
+ صلاحية اختيارية: احصل على الموقع الحالي لاقتراحات التصنيفات
مساهمات المستخدم: %s
إنجازات المستخدم: %s
اعرض صفحة المستخدم
@@ -822,7 +831,7 @@
الإذن مطلوب لهذه الوظيفة
تعلم كيفية كتابة وصف مفيد
تعلم كيفية كتابة تعليق مفيد
- شاهد إنجازاتك
+ عرض إنجازاتك
تعديل الصورة
تعديل الموقع
تم تحديث الموقع!
@@ -851,7 +860,7 @@
يرجى تذكر أن جميع الصور في التحميل المتعدد تحصل على نفس الفئات والأوصاف. إذا لم تتشارك الصور في الأوصاف والفئات، فيرجى إجراء عدة عمليات تحميل منفصلة.
ملاحظة حول التحميلات المتعددة
- الإبلاغ عن مشكلة حول هذا العنصر إلى Wikidata
+ الإبلاغ عن مشكلة حول هذا العنصر إلى ويكي بيانات
الرجاء إدخال بعض التعليقات
نقاش
اكتب شيئًا عن العنصر \'%1$s\'. سيكون مرئيًا للعامة.
diff --git a/app/src/main/res/values-ast/strings.xml b/app/src/main/res/values-ast/strings.xml
index 591c21395..1ab6ae80e 100644
--- a/app/src/main/res/values-ast/strings.xml
+++ b/app/src/main/res/values-ast/strings.xml
@@ -150,10 +150,10 @@
Llicencia predeterminada
Usar un títulu y descripción anterior
Tema
- Reconocimientu-CompartirIgual 4.0
- Reconocimientu 4.0
- Reconocimientu-CompartirIgual 3.0
- Reconocimientu 3.0
+ Reconocimientu-CompartirIgual 4.0
+ Reconocimientu 4.0
+ Reconocimientu-CompartirIgual 3.0
+ Reconocimientu 3.0
CC0
CC BY-SA 3.0
CC BY 3.0
@@ -442,8 +442,8 @@
¿Ta correchamente categorizáu?
¿Axústase a la tema?
¿Quies dar les gracies al collaborador?
- Pulsia NON para nomar esta imaxe pa desaniciar si nun ye pa nada útil.
- Los logotipos, les imáxenes de pantalla y los cartelos de películes son davezu infracciones a los derechos d\'autor.\nPulsia NON para nomar esta imaxe pa desaniciar
+ Calca NON para nomar esta imaxe pa desaniciar si nun ye pa nada útil.
+ Los logotipos, les imáxenes de pantalla y los cartelos de películes son davezu infracciones a los derechos d\'autor.\nCalca NON para nomar esta imaxe pa desaniciar
El to agradecimientu animara a %1$s
¡Ai, esto nun ta siquier categorizao!
Esta imaxe ta baxo %1$s categoríes.
@@ -537,4 +537,5 @@
Nun s\'alcontraron llugares, tenta cambiar los criterios de gueta.
Confirmar
Instrucciones
+ Permisu opcional: llograr l\'allugamientu actual pa suxerir categoríes
diff --git a/app/src/main/res/values-az/error.xml b/app/src/main/res/values-az/error.xml
index 6e9503c43..53a971367 100644
--- a/app/src/main/res/values-az/error.xml
+++ b/app/src/main/res/values-az/error.xml
@@ -5,7 +5,7 @@
* Nemoralis
-->
- Nasazlıq
+ Commons çökdü
Uups. Nəsə düzgün çalışmır!
Nə etdiyinizi bizə deyin, sonra e-poçt vasitəsilə bizimlə paylaşın. Bu, bizə bunu düzəltməyə kömək edəcək!
Təşəkkürlər!
diff --git a/app/src/main/res/values-az/strings.xml b/app/src/main/res/values-az/strings.xml
index b5f736415..d912832a6 100644
--- a/app/src/main/res/values-az/strings.xml
+++ b/app/src/main/res/values-az/strings.xml
@@ -131,6 +131,7 @@
Kateqoriyalarda axtar
Medianızın təsvir etdiyi elementləri axtarın (dağ, Tac Mahal və s.)
Yadda saxla
+ Daşan menyu
Yenilə
Siyahı
(Hələ yükləmə yoxdur)
@@ -159,11 +160,18 @@
Siz hələ heç bir şəkil yükləməmisiniz.
Yenidən cəhd edin
İmtina
+ Dilin adını yaz
+ Son axtarışlar
+ Bütün dillər
Bu şəkli yükləməklə bildirirəm ki, bu mənim şəxsi işimdir, onda müəllif hüququ ilə qorunan material və ya selfilər yoxdur və <a href=\"https://commons.wikimedia.org/wiki/Commons:Policies_and_guidelines\">Vikianbar qaydalarına</a> riayət edir.
Endir
Defolt lisenziya
Əvvəlki başlıq və təsvirdən istifadə et
Tema
+ Attribution-ShareAlike 4.0
+ Attribution 4.0
+ Attribution-ShareAlike 3.0
+ Attribution 3.0
CC BY 3.0
Vikianbar Vikipediyada istifadə olunan şəkillərin əksəriyyətinə ev sahibliyi edir.
Şəkilləriniz bütün dünyada insanları maarifləndirməyə kömək edir!
@@ -212,6 +220,7 @@
Təsvir
Müzakirə
Müəllif
+ Yükləyici
Yüklənmə tarixi
Lisenziya
Koordinatlar
@@ -262,6 +271,7 @@
Yükləmə prosesi aktiv internetə çıxış tələb edir. Şəbəkə bağlantınızı yoxlayın.
Şəkildə tapılan problemlər
Zəhmət olmasa, yalnız özünüz çəkdiyiniz şəkilləri yükləyin. İnternetdən tapdığınız şəkilləri yükləməyin.
+ Yükləmələr
Tətbiqdaxili kadrları yadda saxla
Tətbiqdaxili kamera ilə çəkilmiş şəkilləri cihazın yaddaşında saxla
Hesabınıza daxil olun
@@ -333,6 +343,7 @@
Sorğu
Bu şəkli yükləmək olar?
Sual
+ Əmin deyiləm
Nəticə
Silinməyi tələb edilən şəkilləri yükləməyə davam etsəniz, hesabınız blok olunacaq. Sorğunu bitirmək istədiyinizə əminsiniz?
Yüklədiyiniz şəkillərin %1$s+ ədədi silinib. Silinməyi tələb edilən şəkilləri yükləməyə davam etsəniz, hesabınız blok olunacaq.\n\nTəlimata yenidən baxmaq və sonra hansı növ şəkilləri yükləməli və ya yükləməməli olduğunuzu öyrənməyə kömək etmək üçün testdən keçmək istəyirsiniz?
@@ -342,6 +353,7 @@
Vikianbarın məqsədlərindən biri keyfiyyətli şəkillər toplamaqdır. Ona görə də bulanıq şəkillər yüklənməməlidir. Həmişə yaxşı işıqlandırma ilə gözəl şəkillər çəkməyə çalışın.
Texnologiya və ya mədəniyyəti təsvir edən şəkillər üçün Vikianbarda həmişə yer var.
Cavabların %1$s ədədi düzgündür. Təbriklər!
+ Davam et
Suala cavab vermək üçün iki variantdan birini seçin
Giriş müddəti bitib. Zəhmət olmasa, yenidən daxil olun.
Bunu dostlarınızla paylaşın!
@@ -370,7 +382,7 @@
Seçilmiş şəkillər
\"Yaxınlıqdakı yerlər\" vasitəsilə şəkillər
Səviyyə %d
- %s (Səviyyə %s)
+ %s (Səviyyə %s)
Yüklənən şəkillər
Geri qaytarılan şəkillər
İstifadə olunan şəkillər
@@ -395,10 +407,90 @@
Yaddaş icazəsi
Şəkilləri yükləmək üçün cihazınızın xarici yaddaşına giriş icazəsi lazımdır.
Artıq şəkillərə ehtiyacı olan ən yaxın yeri görməyəcəksiniz. Bununla belə, istəyirsinizsə, bu bildirişi Parametrlərdə yenidən aktivləşdirə bilərsiniz.
+ Addım %1$d/%2$d: %3$s
+ Növbəti
+ Əvvəlki
+ %1$s adlı fayl artıq mövcuddur. Davam etmək istədiyinizə əminsiniz?\n\nQeyd: Fayl adına uyğun şəkilçi avtomatik olaraq əlavə olunacaq.
+ Cihazınızda heç bir uyğun xəritə proqramı tapılmadı. Bu funksiyadan istifadə etmək üçün xəritə tətbiqini quraşdırın.
+ Şəkillər
+ Məkanlar
+ Kateqoriyalar
+ Əlfəcinlərə əlavə et/çıxar
+ Əlfəcinlər
+ Siz heç bir əlfəcin əlavə etməmisiniz
+ Əlfəcinlər
+ Jurnalların toplanması başladı. Zəhmət olmasa, proqramı YENİDƏN BAŞLADIN, daxil olmaq istədiyiniz əməliyyatı yerinə yetirin və sonra yenidən \"Jurnal faylını göndər\" düyməsinə klikləyin
+ Səhvən yükləmişəm
+ Bunun ictimai olaraq görünəcəyini bilmirdim
+ Bunun məxfiliyim üçün pis olduğunu anladım
+ Fikrimi dəyişdim, daha ictimaiyyətə görünməsini istəmirəm
+ Təəssüf ki, bu şəkil ensiklopediya üçün maraqlı deyil
+ %1$s tarixində yükləmişəm, ən azı %2$d məqalədə istifadə olunub.
+ Commons-a xoş gəlmisiniz!\n\nƏlavə düyməsini klikləməklə ilk medianızı yükləyin.
+ Heç bir kateqoriya seçilməyib
+ Kateqoriyaları olmayan şəkillər nadir hallarda istifadə olunur. Kateqoriya seçmədən davam etmək istədiyinizə əminsiniz?
+ Heç bir təsvir seçilməyib
+ Nəyi təsvir etdiyi qeyd olunan şəkillər daha asan tapılır və istifadə olunma ehtimalı daha yüksəkdir. Bunsuz davam etmək istədiyinizə əminsiniz?
+ Yükləməni ləğv et
+ Geri düyməsini istifadə etmək bu yükləməni ləğv edəcək və siz irəliləyişinizi itirəcəksiniz
+ Yükləməyə davam et
+ (Dəstədəki bütün şəkillər üçün)
+ Bu ərazini axtar
+ İcazə sorğusu
+ Şəkillərə ehtiyacı olan ən yaxın yeri göstərmək üçün cari məkanınızdan istifadə etməyimizi istəyirsiniz?
+ Məkan icazəsi olmadan şəkillərə ehtiyacı olan ən yaxın yeri göstərmək mümkün deyil
+ Bunu bir daha heç vaxt soruşma
+ Məkan icazəsi istə
+ Yaxınlıqdakı bildiriş kartına baxmaq funksiyası üçün lazım olduqda məkan icazəsi istəyin.
+ Xəta baş verdi, nailiyyətləri əldə edə bilmədik
+ Siz bizim nailiyyətləri hesablama sistemimizin öhdəsindən gələ bilməyəcək qədər çox töhfə vermisiniz. Bu möhtəşəm nailiyyətdir.
+ Bitmə:
+ Kampaniyaları göstər
+ Davam edən kampaniyalara bax
+ Silmə düyməsini göstər
+ Fərdi seçicidə \"Qovluğu Sil\" düyməsini aktiv et
+ Kameranın qeyd etmədiyi halda, tətbiqə məkanı əldə etməyə icazə verin. Bəzi cihaz kameraları məkanı qeyd etmir. Belə hallarda, tətbiqin əldə edilməsinə və ona məkan əlavə edilməsinə icazə vermək töhfənizi daha faydalı edir. Bunu istənilən vaxt Parametrlərdən dəyişə bilərsiniz
+ İcazə ver
+ Bağla
+ Zəhmət olmasa, parametrlərdən məkan icazəsini aktiv edin və yenidən cəhd edin. \n\nQeyd: Əgər proqram qısa müddət ərzində cihazdan yeri əldə edə bilmirsə, yükləmənin məkan məlumatı olmaya bilər.
+ Məkan EXIF-də mövcud olmadığı halda, tətbiqdaxili kamera onu şəkillərinizə əlavə etmək üçün məkan icazəsi tələb edir. Zəhmət olmasa, tətbiqin məkanınıza daxil olmasına icazə verin və yenidən cəhd edin.\n\nQeyd: Əgər proqram qısa müddət ərzində cihazdan yeri əldə edə bilmirsə, yükləmənin məkan məlumatı olmaya bilər.
+ Məkan icazəsi olmadığı üçün proqram çəkilişlərlə birlikdə məkanı qeyd etməyəcək
+ GPS söndürüldüyü üçün proqram çəkilişlərlə birlikdə məkanı qeyd etməyəcək
+ Sənəd əsaslı foto seçicidən istifadə et
+ Yeni Android foto seçicisi məkan məlumatını itirmək riski daşıyır. Əgər istifadə edirsinizsə, aktivləşdirin.
+ Bunun söndürülməsi yeni Android foto seçicisini işə sala bilər. Bu, məkan məlumatını itirmək riski daşıyır.\n\nƏtraflı məlumat üçün \"Ətraflı məlumat\" üzərinə klikləyin.
+ Siz artıq kampaniyaları görməyəcəksiniz. Bununla belə, istəyirsinizsə, bu bildirişi Parametrlərdə yenidən aktivləşdirə bilərsiniz.
+ Bu funksiya şəbəkə bağlantısı tələb edir. Bağlantı parametrlərinizi yoxlayın.
+ Şəkili emal edərkən xəta baş verdi. Zəhmət olmasa, yenidən cəhd edin!
+ Redaktə üçün token əldə edilir
+ Kateqoriya yoxlaması üçün şablon əlavə edilir
+ %1$s üçün kateqoriya yoxlanışı tələb olunur
+ Kateqoriya yoxlanışı tələb olunur
+ Kateqoriya yoxlanışı tələb olundu
+ Kateqoriya yoxlama sorğusu işləmədi
+ %1$s üçün kateqoriya yoxlanışı tələb olundu
+ %1$s üçün kateqoriya yoxlanışı tələb oluna bilmədi
+ %1$s üçün kateqoriya yoxlanışı tələb olunur
+ Oldu
Təşəkkür uğurla göndərildi
+ %1$s istifadəçisinə uğurla təşəkkür göndərildi
Təşəkkür göndərilə bilmədi %1$s
Təşəkkür göndərilir: Xəta
%1$s üçün təşəkkür göndərişi
+ Bu, müəllif hüququ qaydalarına uyğundur?
+ Bu, düzgün kateqoriyalaşdırılıb?
+ Bu, layihə əhatəsindədir?
+ Töhfə verənə təşəkkür etmək istəyirsiniz?
+ Bu şəklin heç bir faydası yoxdursa, silinməyə namizəd etmək üçün NO düyməsini klikləyin.
+ Loqolar, skrinşotlar, film afişaları çox vaxt müəllif hüquqları pozuntusudur.\nBu şəklin silinməsinə namizəd göstərmək üçün NO düyməsini basın
+ %1$s təşəkkürlərinizlə həvəsləndiriləcək
+ Ah, bu heç kateqoriyalaşdırılmayıb!
+ Bu şəkil %1$s kateqoriyasındadır.
+ Əhatə dairəsindən kənardadır, çünki
+ Müəllif hüququ pozuntusudur, çünki
+ Növbəti şəkil
+ Bəli, niyə də yox
+ Bu düyməni klikləsəniz, sizə Wikimedia Commons-dan bu yaxınlarda yüklənmiş başqa bir şəkil veriləcək
Şəklin koordinatları yenilənməyib
Bildiriş oxunmuş olaraq işarələndi
Zəhmət olmasa tətbiqin cari məkanınızı göstərmək üçün məkan xidmətlərini aktiv edin
diff --git a/app/src/main/res/values-b+roa+tara/strings.xml b/app/src/main/res/values-b+roa+tara/strings.xml
index 3edb3e0c5..bac080ca6 100644
--- a/app/src/main/res/values-b+roa+tara/strings.xml
+++ b/app/src/main/res/values-b+roa+tara/strings.xml
@@ -65,10 +65,10 @@
Sus a
Pruève arrete
Annulle
- Attribbuzione-Condivide a \'u stesse mode 4.0
- Attribbuzione 4.0
- Attribbuzione-Condivide a \'u stesse mode 3.0
- Attribbuzione 3.0
+ Attribbuzione-Condivide a \'u stesse mode 4.0
+ Attribbuzione 4.0
+ Attribbuzione-Condivide a \'u stesse mode 3.0
+ Attribbuzione 3.0
CC0
CC BY-SA 3.0
CC BY 3.0
diff --git a/app/src/main/res/values-b+sr+Latn/strings.xml b/app/src/main/res/values-b+sr+Latn/strings.xml
index 459f75852..d06b1aa13 100644
--- a/app/src/main/res/values-b+sr+Latn/strings.xml
+++ b/app/src/main/res/values-b+sr+Latn/strings.xml
@@ -146,10 +146,10 @@
Podrazumevana licenca
Koristi prethodan naslov i opis
Tema
- Autorstvo-Deliti pod istim uslovima 4.0
- Autorstvo 4.0
- Autorstvo-Deliti pod istim uslovima 3.0
- Autorstvo 3.0
+ Autorstvo-Deliti pod istim uslovima 4.0
+ Autorstvo 4.0
+ Autorstvo-Deliti pod istim uslovima 3.0
+ Autorstvo 3.0
CC0
CC BY-SA 3.0
CC BY 3.0
@@ -647,6 +647,7 @@
Viki voli spomenike
Viki voli spomenike je međunarodno takmičenje fotografija spomenika, koje organizuje Vikimedija
Potrebna je dozvola
+ Neobavezna dozvola: preuzmi trenutnu lokaciju za predloge kategorija
Doprinosi korisnika/ce: %s
Dostignuća korisnika/ce: %s
Prikaži korisničku stranicu
diff --git a/app/src/main/res/values-b+tt+Cyrl/strings.xml b/app/src/main/res/values-b+tt+Cyrl/strings.xml
new file mode 100644
index 000000000..51aa0b13d
--- /dev/null
+++ b/app/src/main/res/values-b+tt+Cyrl/strings.xml
@@ -0,0 +1,233 @@
+
+
+
+ Викиҗыентыкның Facebook бите
+ Викиҗыентыкның гитхабтагы башлангыч кодлары
+ Викиҗыентык логотипы
+ Викиҗыентыкның веб-сайты
+ Урынны сайлау тәрәзәсеннән чыгарга
+ Сакларга
+ Башка тасвирлама өстәргә
+ Яңа кертем өстәргә
+ Камерадан яңа кертем өстәргә
+ Камерадан яңа кертем өстәргә
+ Алдагы кертемнәр галереясыннан фото өстәргә
+ Язмалар
+ Тел тасвирламасы
+ Язма
+ Тасвирлама
+ Сурәт
+ Барысы
+ Күчертергә
+ Күренешне эзлә
+ Урын халәте
+ Көн сурәте
+
+ - %1$d файл йөкләнә
+ - %1$d файл йөкләнә
+ - %1$d файл йөкләнә
+ - %1$d файл йөкләнә
+
+
+ - (%1$d)
+ - (%1$d)
+ - (%1$d)
+ - (%1$d)
+
+ Йөкләү башлана
+
+ - %d йөкләүне эшкәртү
+ - %d йөкләүне эшкәртү
+ - %d йөкләүне эшкәртү
+ - %d йөкләүне эшкәртү
+
+
+ - %d йөкләү
+ - %d йөкләү
+ - %d йөкләү
+ - %d йөкләү
+
+ Тикшерергә
+ Күренеш
+ Гомуми
+ Кире элемтә
+ Шәхсилек
+ Викиҗыентык
+ Көйләнмәләр
+ Викиҗыентыкка йөкләргә
+ Йөкләү бара...
+ Кулланучы исеме
+ Серсүз
+ Commons Beta хисапъязмагызга керегез
+ Керү
+ Серсүзне оныттыгызмы?
+ Теркәлү
+ Керү бара…
+ Бераз көтегезче...
+ Язмалар һәм тасвирламалар яңартыла
+ Бераз көтегезче...
+ Керү уңышлы башкарылды!
+ Системага кереп булмады!
+ Файл табылмады. Башка файлны кулланып карагызчы.
+ Кабатлаулар саны нык артып китте! Йөкләүне кире кагыгыз яки яңадан кабатлагыз.
+ Батәринең оптималь кулланылышын сүндерергәме?
+ Йөкләү башланды!
+ %1$s төялде!
+ Төялгән файлыгызны карау өчен басыгыз
+ Файлны төяү: %s
+ %1$s йөкләнә
+ %1$s йөкләве тәмамлана
+ %1$s йөкләнә алмады
+ %1$s йөкләнүе туктатылып калды
+ Карау өчен басыгыз
+ Карау өчен басыгыз
+ Минем соңгы төяүләрем
+ Төяү хатасы
+ Төяү бара
+ Галереядан
+ Фото ясарга
+ Якында
+ Минем төяүләрем
+ Сылтаманы күчереп ал
+ Сылтама алмашу буферына күчереп алынды
+ Уртаклашырга
+ Файл битен күрсәтергә
+ Язма (Зарур)
+ Бу файлның исемен билгеләгезче
+ Тасвирлама
+ Язма
+ Кереп булмый - челтәр хатасы
+ Гафу итегез, мондый исемле кулланучы Викиҗыентыкта блокланган булган.
+ Системага кереп булмады!
+ Төяү
+ Бу файллар төркеме өчен исемне кертегез
+ Төя
+ Төркемнәрне сайла
+ Сакла
+ Яңарту
+ Исемлек
+ Төялгән файллар юк әле!
+ Төркемнәр
+ Көйләнмәләр
+ Теркәл
+ Сакланган сурәтләр
+ Кулланучы селекторы
+ Төркем
+ Тикшер
+ Кушымта турында
+ Яшеренлек сәясәте
+ Төзүчеләр
+ Кушымта турында
+ Почта клиенты урнаштырылмаган
+ Күптән түгел кулланылган төркемнәр
+ Беренче синхронлаштыруны көтү...
+ Сез әле бер сурәтне дә төямәдегез.
+ Кабатла
+ Кире как
+ Иңләргә
+ Гадәттәге рөхсәтнамә килешүе
+ Алдагы исемне һәм тамвирламаны куллан
+ Күренеш
+ Attribution-ShareAlike 4.0
+ Attribution 4.0
+ Attribution-ShareAlike 3.0
+ Attribution 3.0
+ Викиҗыентыктагы сурәтләр Википедиянең күпчелек күләмендә кулланыла.
+ Төягән сурәтләрегез бөтен дөньядагы кешеләргә белем алырга ярдәм итә ала!
+ Зинһар, бары тик үзегез ясаган яки төшергән сурәтләрне генә төягез:
+ Табигать объектлары (мәсәлән, чәчәкләр, хайваннар, таулар)
+ Файдалы җисемнәр (мәсәлән, велосипедлар, вокзаллар)
+ Билгеле кешеләр (мәсәлән, мэрыгыз, сез очраткан олимпияче-спортсменнар)
+ Зинһар, боларны ТӨЯМӘГЕЗ:
+ Селфилар яки дусларыгызның фотолары
+ Интернеттан иңләгән фотолар
+ Ирекле булмаган программаларның скриншотлары
+ Төяү мисалы:
+ Атамасы: Сидней опера театры
+ Сурәтләрегезне төягез. Википедия мәкаләләрен кызыклырак ясарга булышыгыз!
+ Википедиядә кулланылучы сурәтләр Викиҗыентыкта саклана.
+ Төягән сурәтләрегез бөтен дөньядагы кешеләргә белем алырга ярдәм итә ала.
+ Авторлык хокуклары белән сакланган материалларны, мәсәлән, Интернетта табылган плакатлар, китап тышлары һ. б. ш. сурәтләрне кулланмаска тырышыгыз.
+ Бу сезгә аңлашыламы?
+ Әйе!
+ Тулырак мәгълүмат
+ Төркемнәр
+ Йөкләнә...
+ Бернәрсә дә сайланмаган
+ Язмасыз
+ Тасвирламасыз
+ Фикерләшү юк
+ Билгесез лицензия
+ Яңарту
+ Тышкы саклагычны куллану рөхсәтен сорау
+ Локациягезне табуны сорау
+ Кушымтада ясалган фотолар өчен локацияне яздыр
+ Ярар
+ Игътибар
+ Кабатлана торган файл исеме табылды
+ Төя
+ Әйе
+ Юк
+ Язма
+ Исем
+ Тасвирланган феномен
+ Тасвирлама
+ Фикер алышу
+ Автор
+ Төяп куючы
+ Төяү вакыты
+ Лицензия
+ Координатлар
+ Билгеләнмәгән
+ Бета-тестерга әйләнергә
+ Сез чыннан да чыгарга телисезме?
+ Астөркемнәр табылмады.
+ Баш тарту
+ Ачарга
+ Ябарга
+ Баш бит
+ Төяргә
+ Якын-тирәдә
+ Кушымта турында
+ Көйләнмәләр
+ Кире элемтә
+ GitHub аркылы кире элемтә
+ Чыгарга
+ Кулланма
+ Белдермәләр
+ Тикшерү
+ тасвирлама табылмады
+ Файлның Викиҗыентыктагы бите
+ Викимәгълүмат элементы
+ Википедия мәкаләсе
+ Сурәт артык караңгы.
+ Бу сурәт Викиҗыентыкта бар инде.
+ Бу сурәт башка урында ясалган булган.
+ Барыбер бу сурәтне төяргә телисезме?
+ Тоташтыру хатасы
+ Сурәттә читенлекләр табылды
+ Кушымтада ясалган фотоларны сакла
+ Җайланма камерасы ярдәмендә эшләнгән фотоларны җайланмада сакла
+ Хата! Сылтама табылмады
+ Бетерергә тәкъдим ит
+ Бу сурәтне бетерергә тәкъдим ителде.
+ Күбрәк мәгълүмат өчен битне карагыз
+ Калдырып үт
+ Керү
+ Викитекст алмашу буферына күчереп алынды
+ Юнәлешләр
+ Викимәгълүмат
+ Википедия
+ Викиҗыентык
+ Безне бәяләгез
+ Еш бирелгән сораулар (ЕБС, ЧаВо)
+ Бору
+ Саклауга рөхсәтләр кире кагылды
+ Бу объект белән уртаклашу мөмкин түгел
+
diff --git a/app/src/main/res/values-ba/strings.xml b/app/src/main/res/values-ba/strings.xml
index 0fc68329f..6b351d277 100644
--- a/app/src/main/res/values-ba/strings.xml
+++ b/app/src/main/res/values-ba/strings.xml
@@ -140,10 +140,10 @@
Программа көйләгән лицензия
Элекке атаманы һәм тасуирламаны ҡулланыу
Биҙәү темаһы
- Attribution-ShareAlike 4.0
- Attribution 4.0
- Attribution-ShareAlike 3.0
- Attribution 3.0
+ Attribution-ShareAlike 4.0
+ Attribution 4.0
+ Attribution-ShareAlike 3.0
+ Attribution 3.0
CC0
CC BY-SA 3.0
CC BY 3.0
@@ -257,4 +257,5 @@
Телдәр
Кире алырға
Ҡабатларға
+ Мотлаҡ булмаған рөхсәт: категория тәҡдиме өсөн ошо урынды алыу
diff --git a/app/src/main/res/values-ban/strings.xml b/app/src/main/res/values-ban/strings.xml
index fa34d7f68..47890caa8 100644
--- a/app/src/main/res/values-ban/strings.xml
+++ b/app/src/main/res/values-ban/strings.xml
@@ -23,7 +23,7 @@
Makejang
Alih Duur
Cingakan Panyelehan
- Genah Negara
+ Genah Pernyataan
Gambar rahina mangkin
- %1$d berkas kaunggah
@@ -136,10 +136,10 @@
Lisénsi baku
Anggén murda miwah pidarta sadurungné
Téma
- Atribusi-MawagiSarupa 4.0
- Atribusi 4.0
- Atribusi-MawagiSarupa 3.0
- Atribusi 3.0
+ Atribusi-MawagiSarupa 4.0
+ Atribusi 4.0
+ Atribusi-MawagiSarupa 3.0
+ Atribusi 3.0
CC0
CC BY-SA 3.0
CC BY 3.0
@@ -270,7 +270,7 @@
Statistik
Haturan Suksma Katampi
Gambar Pilihan
- Tingkat
+ Tingkat %d
Gambar Kaupload
Gambar Kaanggén
Pituut
diff --git a/app/src/main/res/values-bcl/strings.xml b/app/src/main/res/values-bcl/strings.xml
index 7b43481a8..b8df68947 100644
--- a/app/src/main/res/values-bcl/strings.xml
+++ b/app/src/main/res/values-bcl/strings.xml
@@ -100,8 +100,8 @@
Kargahon
Lisensiya
Maggamit nin dating titulo/deskripsiyon
- Attribution-ShareAlike 3.0
- Attribution 3.0
+ Attribution-ShareAlike 3.0
+ Attribution 3.0
CC0
Mag-ambag kan saimong mga imahe. Magtabang sa mga artikulo nin Wikipedia na magka-igwa nin buhay!
An mga imahe sa Wikipedia naggigikan sa mga Karugaringan nin Wikimedia.
diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml
index 6ee931542..d92b403b4 100644
--- a/app/src/main/res/values-bg/strings.xml
+++ b/app/src/main/res/values-bg/strings.xml
@@ -116,10 +116,10 @@
Лиценз по подразбиране
Използване на предишното заглавие и описание
Облик
- Признание-Споделяне на споделеното 4.0
- Признание 4.0
- Признание-Споделяне на споделеното 3.0
- Признание 3.0
+ Признание-Споделяне на споделеното 4.0
+ Признание 4.0
+ Признание-Споделяне на споделеното 3.0
+ Признание 3.0
CC0
CC BY-SA 3.0
CC BY 3.0
@@ -271,6 +271,7 @@
При качването използвайте персонализирано авторско име вместо потребителското си име
Персонализирано авторско име
Наблизо
+ Известия
Известия (прочетени)
Списък
Следваща
@@ -308,4 +309,5 @@
напълно размазано
Наблизо
Прочетете повече
+ Отказ
diff --git a/app/src/main/res/values-blk/strings.xml b/app/src/main/res/values-blk/strings.xml
index 03bd8e747..fd7c8bc9f 100644
--- a/app/src/main/res/values-blk/strings.xml
+++ b/app/src/main/res/values-blk/strings.xml
@@ -90,10 +90,10 @@
မူလလုဲင်သိဉ်
ထူႏသုင်ꩻ ကတူႏစဲဉ်ႏတောမ်ႏ အွောန်ႏနယ်ချက် ကရီးခါ
Theme
- Attribution-ShareAlike 4.0
- Attribution 4.0
- Attribution-ShareAlike 3.0
- Attribution 3.0
+ Attribution-ShareAlike 4.0
+ Attribution 4.0
+ Attribution-ShareAlike 3.0
+ Attribution 3.0
မွေး!
ထဲင်းယင်း သꩻတင်ꩻအချက်လက်
ကဏ္ဍဖုံႏ
diff --git a/app/src/main/res/values-bn/strings.xml b/app/src/main/res/values-bn/strings.xml
index 4dd1e508d..56ffbaaf4 100644
--- a/app/src/main/res/values-bn/strings.xml
+++ b/app/src/main/res/values-bn/strings.xml
@@ -177,10 +177,10 @@
পূর্বনির্ধারিত লাইসেন্স
পূর্ববর্তী শিরোনাম ও বিবরণ ব্যবহার করুন
থিম
- অ্যাট্রিবিউশন-শেয়ারঅ্যালাইক ৪.০
- অ্যাট্রিবিউশন ৪.০
- অ্যাট্রিবিউশন-শেয়ারঅ্যালাইক ৩.০
- সিসিইউ০০এ০এঅ্যাট্রিবিউসনইউ০০এ০৩.০
+ অ্যাট্রিবিউশন-শেয়ারঅ্যালাইক ৪.০
+ অ্যাট্রিবিউশন ৪.০
+ অ্যাট্রিবিউশন-শেয়ারঅ্যালাইক ৩.০
+ সিসিইউ০০এ০এঅ্যাট্রিবিউসনইউ০০এ০৩.০
সিসি০
সিসি বাই-এস ৩.০
সিসি বাই ৩.০
@@ -372,7 +372,7 @@
নির্বাচিত ছবি
\"কাছাকাছি স্থান\" এর মাধ্যমে ছবি
স্তর %d
- %s (স্তর %s )
+ %s (স্তর %s )
আপলোডকৃত চিত্র
ছবিগুলো প্রত্যাবর্তন করা হয়নি
ব্যবহৃত ছবি
@@ -534,6 +534,7 @@
আরও জানুন
উইকি লাভস মনুমেন্টস
অনুমতি দরকার
+ ঐচ্ছিক অনুমতি: বিষয়শ্রেণী পরামর্শের জন্য বর্তমান অবস্থান নেয়
ব্যবহারকারীর অবদান: %s
ব্যবহারকারীর অর্জন: %s
ব্যবহারকারীর পাতা দেখুন
diff --git a/app/src/main/res/values-br/strings.xml b/app/src/main/res/values-br/strings.xml
index 85dd880ad..c6e1ec17c 100644
--- a/app/src/main/res/values-br/strings.xml
+++ b/app/src/main/res/values-br/strings.xml
@@ -146,10 +146,10 @@
Aotre-implijout dre ziouer
Ober gant an titl hag an deskrivadur kent
Tem
- Deroadur-RannañHeñvel 4.0
- Deroadur 4.0
- Deroadur-RannañHeñvel 3.0
- Deroadur 3.0
+ Deroadur-RannañHeñvel 4.0
+ Deroadur 4.0
+ Deroadur-RannañHeñvel 3.0
+ Deroadur 3.0
CC0
CC BY-SA 3.0
CC BY 3.0
@@ -435,6 +435,7 @@
Distreiñ
Fiskal
GOUZOUT HIROC\'H
+ Aotre diret : kaout al lec\'hiadur red evit kinnig rummadoù
Degasadennoù an implijer: %s
Taolioù-kaer an implijer: %s
Gwelet profil an implijer
@@ -446,7 +447,7 @@
Stumm Android
Seurt rouedad
Disklêriañ an implijer/ez-mañ
- Gwelet ho taolioù-kaer
+ Gwelet ho taolioù-kaer
Kemmañ ar skeudenn
Kemmañ al lec\'hiadur
Lec\'hiadur hizivaet!
diff --git a/app/src/main/res/values-bs/strings.xml b/app/src/main/res/values-bs/strings.xml
index 91860b1e1..e5c5981a5 100644
--- a/app/src/main/res/values-bs/strings.xml
+++ b/app/src/main/res/values-bs/strings.xml
@@ -107,10 +107,10 @@
Predodređena licenca
Koristi prethodni naziv i opis
Tema
- Autorstvo-Dijeliti pod istim uslovima 4.0
- Autorstvo 4.0
- Autorstvo-Dijeliti pod istim uvjetima 3.0
- Autorstvo 3.0
+ Autorstvo-Dijeliti pod istim uslovima 4.0
+ Autorstvo 4.0
+ Autorstvo-Dijeliti pod istim uvjetima 3.0
+ Autorstvo 3.0
CC0
CC BY-SA 3.0
CC BY 3.0
@@ -184,4 +184,5 @@
ČPP
Prevedi
Jezici
+ Neobavezna dozvola: Dobavljanje trenutne lokacije za predlaganje kategorija
diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml
index e99e997b0..d5e448a04 100644
--- a/app/src/main/res/values-ca/strings.xml
+++ b/app/src/main/res/values-ca/strings.xml
@@ -1,6 +1,7 @@
@@ -201,10 +203,10 @@
Licence par défaut
Utiliser le titre ou la description précédent
Thème
- Attribution-ShareAlike 4.0
- Attribution 4.0
- Attribution – Partage des conditions 3.0
- Attribution 3.0
+ Attribution-ShareAlike 4.0
+ Attribution 4.0
+ Attribution – Partage des conditions 3.0
+ Attribution 3.0
CC0
CC BY-SA 3.0
CC BY 3.0
@@ -257,6 +259,7 @@
Description
Discussion
Auteur
+ Téléverseur
Date de téléversement
Licence
Coordonnées
@@ -415,7 +418,7 @@
Images remarquables
Images par « Lieux à proximité »
Niveau %d
- %s (niveau %s)
+ %s (niveau %s)
Images téléversées
Images non annulées
Images utilisées
@@ -745,6 +748,8 @@
« Wiki aime les monuments » est un concours international de photographie des monuments, organisé par Wikimédia.
Nécessite une permission
Les cartes de proximité doivent pouvoir lire l’ÉTAT DU TÉLÉPHONE pour fonctionner correctement
+ Veuillez activer les services de localisation pour afficher les lieux à proximité.
+ Autorisation facultative : obtenir l’emplacement actuel pour des suggestions de catégorie
Contributions de l’utilisateur : %s
Réussites de l’utilisateur : %s
Afficher le profil de l\'utilisateur
@@ -802,7 +807,7 @@
Des autorisations sont nécessaires pour la fonctionnalité
Apprendre à écrire une description utile
Apprendre à écrire une légende utile
- Voir vos réalisations
+ Consultez vos réalisations
Modifier l’image
Modifier l’emplacement
Emplacement mis à jour !
diff --git a/app/src/main/res/values-frr/strings.xml b/app/src/main/res/values-frr/strings.xml
index c2f2cc30e..920c81047 100644
--- a/app/src/main/res/values-frr/strings.xml
+++ b/app/src/main/res/values-frr/strings.xml
@@ -89,10 +89,10 @@
Lisens
Ual tiitel/beskriiwang brük
Naacht muude
- Attribution-ShareAlike 4.0
- Attribution 4.0
- Attribution-ShareAlike 3.0
- Attribution 3.0
+ Attribution-ShareAlike 4.0
+ Attribution 4.0
+ Attribution-ShareAlike 3.0
+ Attribution 3.0
CC0
CC BY-SA 3.0
CC-BY 3.0
@@ -139,4 +139,5 @@
Ufmelde
Commons-artiikel
WikiData-artiikel
+ Mögelk rocht: Rept di aktuel plak för kategoriiföörslacher ap.
diff --git a/app/src/main/res/values-gcr/strings.xml b/app/src/main/res/values-gcr/strings.xml
index 38fc4a560..44a4e70a4 100644
--- a/app/src/main/res/values-gcr/strings.xml
+++ b/app/src/main/res/values-gcr/strings.xml
@@ -91,8 +91,8 @@
Annilé
Téléchajé
Tenm
- Attribution-ShareAlike 4.0
- Attribution 4.0
+ Attribution-ShareAlike 4.0
+ Attribution 4.0
Enren !
Plis lenfòrmasyon
Katégori-ya
diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml
index f2a606ab9..ffde278d4 100644
--- a/app/src/main/res/values-gl/strings.xml
+++ b/app/src/main/res/values-gl/strings.xml
@@ -152,10 +152,10 @@
Licenza por defecto
Usar o título e descrición anteriores
Tema
- Recoñecemento-CompartirIgual 4.0
- Recoñecemento 4.0
- Recoñecemento-CompartirIgual 3.0
- Recoñecemento 3.0
+ Recoñecemento-CompartirIgual 4.0
+ Recoñecemento 4.0
+ Recoñecemento-CompartirIgual 3.0
+ Recoñecemento 3.0
CC0
CC BY-SA 3.0
CC BY 3.0
@@ -271,9 +271,9 @@
Texto wiki copiado ó portapapeis
A localización non está dispoñible. A identificación de sitios próximos pode non funcionar correctamente.
Precísase permiso para amosar unha lista de lugares preto de aquí
- COMO CHEGAR
- WIKIDATA
- WIKIPEDIA
+ Indicacións
+ Wikidata
+ Wikipedia
COMMONS
Avalíenos
FAQ
@@ -354,6 +354,7 @@
Imaxes destacadas
Imaxes vía \"Lugares próximos\"
Nivel %d
+ %s (nivel %s)
Imaxes cargadas
Imaxes non revertidas
Imaxes usadas
@@ -530,4 +531,5 @@
Enviar
A miña clasificación
Activouse o modo de conexión limitadoǃ
+ Permiso opcionalː obter a localización actual para suxerir categorías
diff --git a/app/src/main/res/values-got/error.xml b/app/src/main/res/values-got/error.xml
new file mode 100644
index 000000000..7838a0fbf
--- /dev/null
+++ b/app/src/main/res/values-got/error.xml
@@ -0,0 +1,10 @@
+
+
+
+ 𐌲𐌰𐌼𐌰𐌹𐌽𐌰 𐌱𐍂𐌰𐌺
+ 𐍉. 𐌰𐌹𐍂𐌶𐌴𐌹 𐍅𐌰𐍂𐌸!
+ 𐌲𐌰𐍄𐌰𐌹𐌷 𐌿𐌽𐍃 𐍈𐌰 𐌸𐌰𐍄𐌴𐌹 𐍄𐌰𐍅𐌹𐌳𐌴𐍃, 𐌸𐌰𐌽𐌿𐌷 𐌸𐌰𐌽 𐌳𐌰𐌹𐌻𐌴𐌹 𐌸𐌰𐌹𐍂𐌷 𐌴-𐌱𐍉𐌺𐍉𐍃. 𐌷𐌹𐌻𐍀𐌹𐌸 𐌿𐌽𐍃𐌰𐍂𐌰 𐌲𐌰𐌱𐍉𐍄𐌾𐌰𐌽!
+ 𐌰𐍅𐌹𐌻𐌹𐌿𐌳𐍉𐌼 𐌸𐌿𐍃!
+
diff --git a/app/src/main/res/values-got/strings.xml b/app/src/main/res/values-got/strings.xml
new file mode 100644
index 000000000..8eb0615e6
--- /dev/null
+++ b/app/src/main/res/values-got/strings.xml
@@ -0,0 +1,375 @@
+
+
+
+ 𐌲𐌰𐌼𐌰𐌹𐌽𐌰 𐌻𐌰𐌿𐍆𐍃 𐍆𐌴𐍃𐌱𐌿𐌺𐍃
+ 𐌲𐌰𐌼𐌰𐌹𐌽𐌰 𐌲𐌹𐍄𐌷𐌰𐌱𐍃 𐌺𐍉𐌳𐌿𐍃 𐌱𐍂𐌿𐌽𐌽𐌹𐌽𐍃
+ 𐌲𐌰𐌼𐌰𐌹𐌽𐌰 𐌻𐍉𐌲𐍉
+ 𐌲𐌰𐌼𐌰𐌹𐌽𐌰 𐌽𐌰𐍄𐌾𐌰𐍃𐍄𐌰𐌸𐍃
+ 𐍃𐍄𐌰𐌳𐌹𐍃 𐌽𐌹𐌼𐌰𐌽𐌳 𐌰𐍆𐌻𐌴𐌹𐌸𐌰𐌽
+ 𐍃𐌰𐌽𐌳𐌾𐌰𐌽
+ 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐍃
+ 𐌰𐌻𐌻
+ 𐌹𐌿𐍀 𐌹𐌽𐌼𐌰𐌹𐌳𐌾𐌰𐌽
+ 𐍃𐌹𐌿𐌽𐍃 𐍃𐍉𐌺𐌽𐌰𐌹𐍃
+ 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐍃 𐌳𐌰𐌲𐌹𐍃
+ 𐌹𐌽𐌽𐌰𐍄𐌱𐌰𐌿𐍂𐌰 𐌳𐌿𐌲𐌹𐌽𐌽𐌰𐌽𐌳
+
+ - 𐌰𐌹𐌽=%d 𐌹𐌽𐌽𐌰𐍄𐌱𐌰𐌿𐍂
+
+ 𐌱𐌹𐌲𐌹𐍄𐌰𐌽
+ 𐌲𐌰𐌼𐌰𐌹𐌽
+ 𐍃𐌿𐌽𐌳𐍂𐌰𐌻𐌴𐌹𐌺𐌴𐌹
+ 𐌲𐌰𐌼𐌰𐌹𐌽𐌰
+ 𐍃𐌰𐍄𐌴𐌹𐌽𐍉𐍃
+ 𐌹𐌽𐌽𐌰𐍄𐌱𐌰𐌹𐍂𐌰𐌽 𐌳𐌿 𐌲𐌰𐌼𐌰𐌹𐌽𐌰𐌹𐌼
+ 𐌹𐌽𐌽𐌰𐍄𐌱𐌰𐌿𐍂 𐍅𐌰𐌹𐍂𐌸𐌹𐌸
+ 𐌱𐍂𐌿𐌺𐌾𐌰𐌽𐌰𐌼𐍉
+ 𐍂𐌿𐌽𐌰𐍅𐌰𐌿𐍂𐌳
+ 𐌰𐍄𐌲𐌰𐌲𐌲𐌰𐌽 𐌳𐌿 𐌲𐌰𐌼𐌰𐌹𐌽𐌰𐌹 𐌱𐌰𐌹𐍄𐌰 𐍂𐌰𐌷𐌽𐌴𐌹𐌽𐌰𐌹 𐌸𐌴𐌹𐌽𐌰𐌹
+ 𐌰𐍄𐌲𐌰𐌲𐌲𐌰𐌽
+ 𐌿𐍆𐌰𐍂𐌼𐌿𐌽𐌽𐍉𐌳𐌴𐍃 𐍂𐌿𐌽𐌰𐍅𐌰𐌿𐍂𐌳𐌰?
+ 𐌰𐌽𐌰𐌼𐌴𐌻𐌾𐌰𐌽
+ 𐌰𐍄𐌲𐌰𐌲𐌲𐌰𐌽𐌳𐍉…
+ 𐌱𐌹𐌳𐌾𐌰𐌼 𐌸𐌿𐌺 𐌱𐌴𐌹𐌳𐌰𐌽...
+ 𐌱𐌹𐌳𐌾𐌰𐌼 𐌸𐌿𐌺 𐌱𐌴𐌹𐌳𐌰𐌽...
+ 𐌰𐍄𐌲𐌰𐌲𐌲 𐍅𐌰𐍂𐌸!
+ 𐌽𐌹 𐌼𐌰𐌷𐍄𐌰 𐌰𐍄𐌲𐌰𐌲𐌲𐌰𐌽!
+ 𐍆𐌰𐌾𐌻 𐌱𐌹𐌲𐌹𐍄𐌰𐌽 𐌽𐌹𐍃𐍄. 𐌱𐌹𐌳𐌾𐌰𐌼 𐌸𐌿𐌺 𐍃𐍉𐌺𐌾𐌰𐌽 𐌰𐌽𐌸𐌰𐍂 𐍆𐌰𐌾𐌻.
+ 𐌹𐌽𐌽𐌰𐍄𐌱𐌰𐌿𐍂 𐌳𐌿𐌲𐌰𐌽𐌽!
+ 𐌹𐌽𐌽𐌰𐍄𐌱𐌰𐌿𐍂 𐌱𐌴𐌹𐌳𐌹𐌸 (𐌰𐌽𐌰𐌻𐌰𐌲𐌹𐌸 𐌹𐍃𐍄 𐌼𐍉𐌳𐌿𐍃 𐌲𐌰𐍅𐌹𐍃𐍃𐌰𐌹𐍃 𐌲𐌰𐌼𐌰𐍂𐌺𐍉𐌸)
+ %1$s 𐌹𐌽𐌽𐌰𐍄𐌱𐌰𐌿𐍂𐌰𐌽!
+ 𐌸𐍂𐌿𐌺𐌾𐌰𐌽 𐌳𐌿 𐍃𐌰𐌹𐍈𐌰𐌽 𐌹𐌽𐌽𐌰𐍄𐌱𐌰𐌿𐍂 𐌸𐌴𐌹𐌽
+ 𐌹𐌽𐌽𐌰𐍄𐌱𐌰𐌹𐍂𐌰𐌳𐌰 𐍆𐌰𐌾𐌻: %s
+ %1$s 𐌹𐌽𐌽𐌰𐍄𐌱𐌰𐌹𐍂𐌰𐌳𐌰
+ 𐌸𐍂𐌿𐌺𐌾𐌰𐌽 𐌳𐌿 𐍃𐌰𐌹𐍈𐌰𐌽
+ 𐌸𐍂𐌿𐌺𐌾𐌰𐌽 𐌳𐌿 𐍃𐌰𐌹𐍈𐌰𐌽
+ 𐌰𐍆𐍄𐌿𐌼𐌹𐍃𐍄𐌰 𐌹𐌽𐌽𐌰𐍄𐌱𐌰𐌿𐍂𐌰 𐌼𐌴𐌹𐌽𐌰
+ 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐌹𐌷𐌰𐌻𐌻𐍉𐍃
+ 𐌽𐌹𐌼 𐌻𐌹𐌿𐌷𐌰𐌳𐌰𐌼𐌴𐌻𐌹
+ 𐌽𐌴𐍈𐌰
+ 𐌹𐌽𐌽𐌰𐍄𐌱𐌰𐌿𐍂𐌰 𐌼𐌴𐌹𐌽𐌰
+ 𐌳𐌰𐌹𐌻𐌾𐌰𐌽
+ 𐍆𐌰𐌾𐌻𐌹𐍃 𐌻𐌰𐌿𐍆 𐍃𐌰𐌹𐍈𐌰𐌽
+ 𐌿𐍆𐌼𐌴𐌻𐌴𐌹𐌽𐍃 (𐌸𐌰𐌿𐍂𐍆𐍄𐍃 𐌹𐍃𐍄)
+ 𐌿𐍆𐌼𐌴𐌻𐌴𐌹𐌽𐍃
+ 𐌽𐌹 𐌼𐌰𐌷𐍄𐌰 𐌰𐍄𐌲𐌰𐌲𐌲𐌰𐌽
+ 𐌰𐍄𐌱𐌰𐌹𐍂𐌰𐌽
+ 𐌷𐌰𐌹𐍄 𐌸𐌰𐍄𐌰 𐌷𐌿𐌶𐌳
+ 𐌹𐌽𐌼𐌰𐌹𐌳𐌴𐌹𐌽𐍉𐍃
+ 𐌹𐌽𐌽𐌰𐍄𐌱𐌰𐌹𐍂𐌰𐌽
+ 𐍃𐍉𐌺𐌽𐌰𐌹𐍃 𐌺𐌿𐌽𐌾𐌰
+ 𐌲𐌰𐍆𐌰𐍃𐍄𐌰𐌽
+ 𐌰𐍆𐍄𐍂𐌰𐌰𐌽𐌰𐌽𐌹𐌿𐌾𐌰𐌽
+ 𐍅𐌹𐌺𐍉
+ (𐌽𐌹 𐌽𐌰𐌿𐌷 𐌹𐌽𐌽𐌰𐍄𐌱𐌰𐌿𐍂𐌰)
+ 𐌽𐌹 𐌺𐌿𐌽𐌾𐌰 𐌲𐌰𐌻𐌴𐌹𐌺𐍉𐌽𐌳𐍉𐌽𐌰 %1$s 𐌱𐌹𐌲𐌹𐍄𐌰𐌽𐌰
+ 𐌺𐌿𐌽𐌾𐌰
+ 𐌻𐌰𐌲𐌴𐌹𐌽𐍉𐍃
+ 𐌰𐌽𐌰𐌼𐌴𐌻𐌾𐌰𐌽
+ 𐌺𐌿𐌽𐌹
+ 𐌱𐌹
+ 𐌱𐌹
+ 𐌰𐍆𐍄𐍂𐌰 𐍃𐍉𐌺𐌾𐌰𐌽
+ 𐍃𐍅𐌴𐌹𐌱𐌰𐌽
+ 𐌰𐍄𐌳𐍂𐌰𐌲𐌰𐌽
+ 𐌸𐌰𐌹𐌼𐌰
+ 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐌴𐌹𐍃 𐌸𐌴𐌹𐌽𐍉𐍃 𐌷𐌹𐌻𐍀𐌰𐌽𐌳 𐌻𐌰𐌹𐍃𐌴𐌹𐌽𐌰𐌹𐍃 𐌼𐌰𐌽𐌽𐌴 𐌹𐌽 𐍆𐌰𐌹𐍂𐍈𐌰𐌿!
+ 𐍅𐌹𐍃𐍄𐌴𐌹𐌲𐍉𐍃 𐍅𐌰𐌹𐌷𐍄𐍃 (𐌱𐌻𐍉𐌼𐌰𐌽𐍃, 𐌳𐌹𐌿𐌶𐌰, 𐍆𐌰𐌹𐍂𐌲𐌿𐌽𐌾𐌰)
+ 𐌱𐌹𐌳𐌾𐌰𐌼 𐌸𐌿𐌺 𐌽𐌹 𐌹𐌽𐌽𐌰𐍄𐌱𐌰𐌹𐍂𐌰𐌽:
+ 𐍃𐌹𐌻𐌱𐌴𐌹𐌽𐍃 𐌸𐌰𐌿 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐌴𐌹𐍃 𐍆𐍂𐌹𐌾𐍉𐌽𐌳𐌴 𐌸𐌴𐌹𐌽𐌰𐌹𐌶𐌴
+ 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐌴𐌹𐍃 𐌸𐍉𐌶𐌴𐌹 𐌰𐍄𐌳𐍂𐌰𐌲𐍄 𐌰𐍆 𐌲𐌰𐌽𐌰𐍄𐌾𐌰
+ 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐍃 𐌹𐌽𐌽𐌰𐍄𐌱𐌰𐌿𐍂𐌹𐍃:
+ 𐌿𐍆𐌰𐍂𐌼𐌴𐌻𐌹: 𐍃𐌹𐌳𐌽𐌴𐌹 𐍉𐍀𐌰𐌹𐍂𐌰𐌲𐌰𐍂𐌳𐍃
+ 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐌴𐌹𐍃 𐌸𐌴𐌹𐌽𐍉𐍃 𐌱𐌹𐌰𐌹𐌺𐌰𐌽. 𐌷𐌹𐌻𐍀 𐌻𐌰𐌿𐌱𐌴 𐍅𐌹𐌺𐌹𐍀𐌰𐌹𐌳𐌾𐍉𐍃 𐌲𐌰𐌵𐌹𐌿𐌾𐌰𐌽!
+ 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐌴𐌹𐍃 𐌰𐌽𐌰 𐍅𐌹𐌺𐌹𐍀𐌰𐌹𐌳𐌾𐌰𐌹 𐌵𐌹𐌼𐌰𐌽𐌳𐌴𐌹𐌽𐍃 𐍆𐍂𐌰𐌼 𐍅𐌹𐌺𐌹𐌼𐌰𐌹𐌳𐌾𐌰𐌹 𐌲𐌰𐌼𐌰𐌹𐌽𐌰𐌹.
+ 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐌴𐌹𐍃 𐌸𐌴𐌹𐌽𐍉𐍃 𐌷𐌹𐌻𐍀𐌰𐌽𐌳 𐌻𐌰𐌹𐍃𐌴𐌹𐌽𐌰𐌹𐍃 𐌼𐌰𐌽𐌽𐌴 𐌹𐌽 𐍆𐌰𐌹𐍂𐍈𐌰𐌿.
+ 𐌸𐌰𐌲𐌺𐌾𐌹𐍃 𐌸𐌰𐍄𐌴𐌹 𐍆𐍂𐍉𐍃𐍄?
+ 𐌾𐌰𐌹!
+ 𐌼𐌰𐌹𐍃 𐌺𐌿𐌽𐌸𐌹
+ 𐌺𐌿𐌽𐌾𐌰
+ 𐌽𐌹 𐍅𐌰𐌹𐌷𐍄 𐌲𐌰𐍅𐌰𐌻𐌹𐌸
+ 𐌽𐌹 𐌲𐌰𐍅𐌰𐌿𐍂𐌳𐌹
+ 𐌰𐍆𐍄𐍂𐌰𐌰𐌽𐌰𐌽𐌹𐌿𐌾𐌰𐌽
+ 𐍅𐌰𐌹𐌻𐌰
+ 𐍈𐍉𐍄𐌰
+ 𐍃𐌰𐌼𐍉 𐌽𐌰𐌼𐍉 𐍆𐌰𐌾𐌻𐌹𐍃 𐌱𐌹𐌲𐌹𐍄𐌰𐌽
+ 𐌹𐌽𐌽𐌰𐍄𐌱𐌰𐌹𐍂𐌰𐌽
+ 𐌾𐌰𐌹
+ 𐌽𐌴
+ 𐌿𐍆𐌼𐌴𐌻𐌴𐌹𐌽𐍃
+ 𐌿𐍆𐌰𐍂𐌼𐌴𐌻𐌹
+ 𐌲𐌰𐍅𐌰𐌿𐍂𐌳𐌹
+ 𐌱𐍉𐌺𐌰𐍂𐌴𐌹𐍃
+ 𐌹𐌽𐌽𐌰𐍄𐌱𐌰𐌹𐍂𐌰𐌽𐌳𐍃
+ 𐌲𐌰𐍄𐌴𐍅𐍉𐍃
+ 𐌽𐌹 𐌲𐌹𐌱𐌰𐌽
+ 𐍅𐌰𐌹𐍂𐌸 𐌱𐌰𐌹𐍄𐌰 𐍃𐍉𐌺𐌾𐌰𐌽𐌳𐍃
+ 2𐍆𐌰 𐌺𐍉𐌳𐌿𐍃
+ 𐌱𐌹 𐍃𐌿𐌽𐌾𐌰𐌹 𐍅𐌹𐌻𐌴𐌹𐌶𐌿 𐌰𐍆𐌻𐌴𐌹𐌸𐌰𐌽?
+ 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐍃 𐌼𐌰𐌹𐌳𐌾𐌴 𐌱𐍂𐌰𐌺
+ 𐌽𐌹 𐌱𐌹𐌲𐌹𐍄𐌰𐌽𐌰 𐌿𐍆𐌺𐌿𐌽𐌾𐌰
+ 𐍆𐌰𐌹𐍂𐌲𐌿𐌽𐌹 𐌶𐌰𐍉
+ 𐌻𐌻𐌰𐌼𐌰𐍃
+ 𐌱𐍂𐌿𐌳𐌳𐌹 𐍂𐌹𐌲𐌽𐌰𐌱𐌿𐌲𐌹𐌽𐍃
+ 𐍄𐍅𐌻𐌹𐍀
+ 𐍅𐌹𐌻𐌾𐌰𐌵𐌿𐌼𐌰 𐍅𐌹𐌺𐌹𐍀𐌰𐌹𐌳𐌾𐍉𐍃
+ 𐍃𐍅𐌴𐌹𐌱𐌰𐌽
+ 𐌿𐍃𐌻𐌿𐌺𐌰𐌽
+ 𐌻𐌿𐌺𐌰𐌽
+ 𐌲𐌰𐍂𐌳𐍃
+ 𐌰𐍄𐌱𐌰𐌹𐍂𐌰𐌽
+ 𐌽𐌴𐍈𐌰
+ 𐌱𐌹
+ 𐌻𐌰𐌲𐌴𐌹𐌽𐍉𐍃
+ 𐌰𐍆𐌻𐌴𐌹𐌸𐌰𐌽
+ 𐌿𐍃𐌸𐍂𐍉𐌸𐌴𐌹𐌽𐍃 𐌻𐌰𐌹𐍃𐌴𐌹𐌽𐌰𐌹𐍃
+ 𐌻𐌰𐌿𐍆𐍃 𐍆𐌰𐌾𐌻𐌹𐍃 𐌲𐌰𐌼𐌰𐌹𐌽𐍃
+ 𐌻𐌰𐌿𐍆𐍃 𐍅𐌹𐌺𐌹𐍀𐌰𐌹𐌳𐌾𐍉𐍃
+ 𐌼𐌰𐌷𐍄𐌴𐌹𐌲𐍉𐍃 𐌰𐌹𐍂𐌶𐌴𐌹𐌽𐍃 𐌸𐌹𐌶𐌰𐌹 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐌰𐌹:
+ 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐍃 𐌿𐍆𐌰𐍂𐍂𐌹𐌵𐌹𐌶𐌰 𐌹𐍃𐍄.
+ 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐍃 𐍃𐌺𐌴𐌹𐍂𐌰 𐌽𐌹𐍃𐍄.
+ 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐍃 𐌾𐌿 𐌹𐍃𐍄 𐌰𐌽𐌰 𐌲𐌰𐌼𐌰𐌹𐌽𐌰𐌹𐌼.
+ 𐍃𐍉 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐍃 𐌰𐍄 𐌰𐌽𐌸𐌰𐍂𐌰𐌼𐌼𐌰 𐍃𐍄𐌰𐌳𐌰 𐌽𐌿𐌼𐌰𐌽𐌰 𐍅𐌰𐍃.
+ 𐌱𐌹𐌳𐌾𐌰𐌼 𐌸𐌿𐌺 𐌸𐌰𐍄𐌰𐌹𐌽𐌴𐌹 𐌹𐌽𐌽𐌰𐍄𐌱𐌰𐌹𐍂𐌰𐌽 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐌹𐌽𐍃 𐌽𐌿𐌼𐌰𐌽𐍉𐍃 𐍆𐍂𐌰𐌼 𐌸𐌿𐍃 𐍃𐌹𐌻𐌱𐌹𐌽. 𐌽𐌹 𐌹𐌽𐌽𐌰𐍄𐌱𐌰𐌹𐍂 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐌹𐌽𐍃 𐌸𐍉𐌶𐌴𐌹 𐌱𐌹𐌲𐌰𐍃𐍄 𐌰𐌽𐌰 𐍆𐌴𐍃𐌱𐌿𐌺 𐍂𐌰𐌷𐌽𐌴𐌹𐌽𐌹𐌼 𐌰𐌽𐌸𐌰𐍂𐌰𐌹𐌶𐌴.
+ 𐍅𐌹𐌻𐌴𐌹𐌶𐌿 𐌽𐌰𐌿𐌷 𐌸𐍉 𐍆𐍂𐌹𐍃𐌰𐌷𐍄 𐌹𐌽𐌽𐌰𐍄𐌱𐌰𐌹𐍂𐌰𐌽?
+ 𐌰𐌹𐍂𐌶𐌴𐌹 𐍅𐌰𐍂𐌸 𐌲𐌰𐍅𐌹𐍃𐍃𐌰𐌹
+ 𐌰𐌹𐍂𐌶𐌴𐌹𐌽𐍃 𐌱𐌹𐌲𐌹𐍄𐌰𐌽𐍉𐍃 𐌹𐌽 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐌰𐌹
+ 𐌹𐌽-𐌰𐍀𐍀 𐍃𐌺𐌰𐌿𐍄𐌰𐌽𐍃 𐌲𐌰𐍆𐌰𐍃𐍄𐌰𐌽
+ 𐌰𐍄𐌲𐌰𐌲𐌲𐌰𐌽 𐌳𐌿 𐍂𐌰𐌷𐌽𐌴𐌹𐌽𐌰𐌹 𐌸𐌴𐌹𐌽𐌰𐌹
+ 𐌻𐌰𐌿𐌲 𐍆𐌰𐌾𐌻 𐍃𐌰𐌽𐌳𐌾𐌰𐌽
+ 𐌰𐌹𐍂𐌶𐌴𐌹! 𐌿𐍂𐌻 𐌽𐌹 𐌱𐌹𐌲𐌹𐍄𐌰𐌽
+ 𐌲𐌰𐌳𐍉𐌼𐌾𐌰𐌽 𐍆𐍂𐌰𐌵𐌹𐍃𐍄𐌴𐌹𐌽𐌰𐌹
+ 𐍃𐍉 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐍃 𐌲𐌰𐌳𐍉𐌼𐌹𐌳𐌰 𐌹𐍃𐍄 𐍆𐍂𐌰𐌵𐌹𐍃𐍄𐌴𐌹𐌽𐌰𐌹
+ 𐍃𐌰𐌹𐍈 𐌽𐌰𐍄𐌾𐌰𐍃𐍄𐌰𐌸 𐌼𐌰𐌹𐌶𐍉 𐌺𐌿𐌽𐌸𐌾𐌰.
+ 𐍄𐍂𐌹𐍀𐍀𐍉𐌽
+ 𐌰𐍄𐌲𐌰𐌲𐌲𐌰𐌽
+ 𐌱𐌹 𐍃𐌿𐌽𐌾𐌰𐌹 𐍅𐌹𐌻𐌴𐌹𐌶𐌿 𐍄𐍂𐌹𐍀𐍀𐍉𐌽 𐌰𐍄𐌲𐌰𐌲𐌲?
+ 𐍃𐌺𐌰𐌻𐍄 𐌰𐍄𐌲𐌰𐌲𐌲𐌰𐌽 𐌳𐌿 𐌹𐌽𐌽𐌰𐍄𐌱𐌰𐌹𐍂𐌰𐌽 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐌹𐌽𐍃 𐌹𐌽 𐌰𐌽𐌰𐍅𐌰𐌹𐍂𐌸𐌰.
+ 𐌱𐌹𐌳𐌾𐌰𐌼 𐌸𐌿𐌺 𐌰𐍄𐌲𐌰𐌲𐌲 𐌳𐌿 𐌱𐍂𐌿𐌺𐌾𐌰𐌽 𐌸𐌹𐌶𐍉𐍃 𐌼𐌰𐌷𐍄𐌰𐌹𐍃
+ 𐌽𐌴𐍈𐌰 𐌼𐌰𐌲𐌹 𐌽𐌹 𐍅𐌰𐌹𐌻𐌰 𐍅𐌰𐌿𐍂𐌺𐌾𐌰𐌽, 𐍃𐍄𐌰𐌸𐍃 𐌽𐌹𐍃𐍄.
+ 𐍅𐌹𐌺𐌹𐌳𐌰𐍄𐌰
+ 𐍅𐌹𐌺𐌹𐍀𐌰𐌹𐌳𐌾𐌰
+ 𐌲𐌰𐌼𐌰𐌹𐌽𐌰
+ 𐌲𐌹𐍆 𐌿𐌽𐍃 𐍅𐌰𐌹𐍂𐌸
+ 𐍆𐌹𐌻𐌿 𐍆𐍂𐌰𐌹𐌷𐌽𐌰𐌽𐍉𐍃 𐌱𐌹𐌳𐍉𐍃
+ 𐌷𐌰𐌽𐌳𐌿𐌱𐍉𐌺𐍉𐍃 𐌱𐍂𐌿𐌺𐌾𐌹𐌽𐍃
+ 𐌲𐌰𐌽𐌰𐍄𐌹 𐌽𐌹𐍃𐍄
+ 𐍃𐌺𐌴𐌹𐍂𐌾𐌰𐌽
+ 𐍂𐌰𐌶𐌳𐍉𐍃
+ 𐍅𐌰𐌻𐌴𐌹 𐍂𐌰𐌶𐌳𐌰 𐌸𐌹𐌶𐌰𐌹𐌴𐌹 𐍅𐌹𐌻𐌴𐌹𐍃 𐌹𐌽𐍃𐌰𐌽𐌳𐌾𐌰𐌽 𐍃𐌺𐌴𐌹𐍂𐌴𐌹𐌽𐌹𐌽𐍃
+ 𐌸𐌰𐌹𐍂𐌷𐍅𐌹𐍃
+ 𐍃𐍅𐌴𐌹𐌱𐌰𐌽
+ 𐌰𐍆𐍄𐍂𐌰 𐍃𐍉𐌺𐌾𐌰𐌽
+ 𐍃𐌰 𐍃𐍄𐌰𐌸𐍃 𐌸𐌰𐍂𐍆 𐌻𐌹𐌿𐌷𐌰𐌳𐌰𐌼𐌴𐌻𐌾𐌹𐍃.
+ 𐍃𐌰 𐍃𐍄𐌰𐌸𐍃 𐌾𐌿𐌸𐌰𐌽 𐌷𐌰𐌱𐌰𐌹𐌸 𐌻𐌹𐌿𐌷𐌰𐌳𐌰𐌼𐌴𐌻𐌹.
+ 𐍃𐌰 𐍃𐍄𐌰𐌸𐍃 𐌸𐌰𐌽𐌰𐌼𐌰𐌹𐍃 𐌽𐌹𐍃𐍄.
+ 𐌽𐌹 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐌴𐌹𐍃 𐌱𐌹𐌲𐌹𐍄𐌰𐌽𐍉𐍃!
+ 𐌰𐌹𐍂𐌶𐌴𐌹 𐍅𐌰𐍂𐌸 𐌰𐍄𐌱𐌰𐌹𐍂𐌰𐌽 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐌹𐌽𐍃
+ 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐍃 𐌳𐌰𐌲𐌹𐍃
+ 𐍃𐍉𐌺𐌽𐍃
+ 𐌲𐌰𐌼𐌰𐌹𐌽𐌰 𐍃𐍉𐌺𐌾𐌰𐌽
+ 𐍃𐍉𐌺𐌾𐌰𐌽
+ 𐌰𐍆𐍄𐌿𐌼𐌹𐍃𐍄𐍉𐍃 𐍃𐍉𐌺𐌽𐌴𐌹𐍃:
+ 𐌼𐌰𐌹𐌳𐌾𐌰
+ 𐌺𐌿𐌽𐌾𐌰
+ 𐌺𐌰𐍂𐍄𐌰
+ 𐌵𐌿𐌹𐌶
+ 𐌹𐍃𐍄 𐍃𐍉 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐍃 𐌲𐍉𐌳𐌰 𐌳𐌿 𐌹𐌽𐌽𐌰𐍄𐌱𐌰𐌹𐍂𐌰𐌽?
+ 𐌱𐌴𐌹𐌳𐌰
+ 𐍄𐌰𐌿𐌹
+ 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐌴𐌹𐍃 𐌱𐌰𐌽𐌳𐍅𐌾𐌰𐌽𐌳𐌴𐌹𐌽𐍃 𐍄𐍉𐌾𐌰𐌻𐌴𐌹𐍃𐌴𐌹𐌽 𐌸𐌰𐌿 𐌿𐍃𐌼𐌴𐍄 𐌲𐌰𐌹𐍂𐌽𐌾𐌰𐌱𐌰 𐍅𐌹𐌻𐌴𐌹𐌼𐌰 𐌰𐌽𐌰 𐌲𐌰𐌼𐌰𐌹𐌽𐌰𐌹𐌼.
+ %1$s 𐌰𐌽𐌳𐌰𐌷𐌰𐍆𐍄𐌴 𐌸𐌴𐌹𐌽𐌰𐌹𐌶𐍉 𐍂𐌰𐌹𐌷𐍄𐍉𐍃 𐍅𐌴𐍃𐌿𐌽.\n𐍅𐌰𐌹𐌻𐌰 𐌲𐌰𐍄𐌰𐍅𐌹𐌳𐌴𐍃!
+ 𐍅𐌰𐌻𐌴𐌹 𐍃𐌿𐌼𐌰 𐍄𐍅𐌰𐌳𐌳𐌾𐍉 𐌲𐌰𐍅𐌰𐌻𐌴𐌹𐌽𐍉 𐌳𐌿 𐌰𐌽𐌳𐌷𐌰𐍆𐌾𐌰𐌽 𐌸𐌹𐌶𐌰𐌹 𐌱𐌹𐌳𐌰𐌹.
+ 𐌰𐍄𐌲𐌰𐌲𐌲 𐌿𐍃𐍄𐌰𐌿𐌷. 𐌱𐌹𐌳𐌾𐌰𐌼 𐌸𐌿𐌺 𐌰𐍆𐍄𐍂𐌰 𐌰𐍄𐌲𐌰𐌲𐌲𐌰𐌽.
+ 𐌵𐌿𐌹𐌶 𐌸𐌴𐌹𐌽 𐌲𐌰𐌳𐌰𐌹𐌻𐌾𐌰𐌽 𐍆𐍂𐌹𐌾𐍉𐌽𐌳𐌰𐌼!
+ 𐌸𐌰𐌹𐍂𐌷𐍅𐌹𐍃𐌰𐌽
+ 𐌰𐌽𐌳𐌰𐌷𐌰𐍆𐍄𐍃 𐍂𐌰𐌹𐌷𐍄𐌰
+ 𐌰𐌽𐌳𐌰𐌷𐌰𐍆𐍄𐍃 𐌿𐌽𐍂𐌰𐌹𐌷𐍄𐌰
+ 𐍃𐌰 𐍃𐌺𐌰𐌹𐍂𐌼𐌰𐍃𐌺𐌰𐌿𐍄𐍃 𐌲𐍉𐌸𐍃 𐌹𐍃𐍄 𐌳𐌿 𐌹𐌽𐌽𐌰𐍄𐌱𐌰𐌹𐍂𐌰𐌽?
+ 𐌰𐍀𐍀 𐌳𐌰𐌹𐌻𐌾𐌰𐌽
+ 𐌽𐌹 𐌼𐌰𐌷𐍄𐌰 𐌰𐍄𐌱𐌰𐌹𐍂𐌰𐌽 𐍃𐍄𐌰𐌳𐌹𐌽𐍃 𐌽𐌴𐍈𐌰
+ 𐌽𐌹 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐌴𐌹𐍃 𐌹𐌽 𐌸𐌰𐌼𐌼𐌰 𐍃𐍄𐌰𐌳𐌰
+ 𐌽𐌹 𐍃𐌹𐌽𐌳 𐍃𐍄𐌰𐌳𐌴𐌹𐍃 𐌽𐌴𐍈𐌰 𐌱𐌹𐍃𐌿𐌽𐌾𐌰𐌽𐌴
+ 𐌰𐌹𐍂𐌶𐌴𐌹 𐍅𐌰𐍂𐌸 𐌽𐌹𐌼𐌰𐌽 𐌰𐌿𐍂𐌰𐌷𐌾𐍉𐍃 𐌽𐌴𐍈𐌰.
+ 𐌽𐌹 𐍃𐍉𐌺𐌽𐌴𐌹𐍃 𐌰𐍆𐍄𐌿𐌼𐌹𐍃𐍄𐍉𐍃
+ 𐌱𐌹 𐍃𐌿𐌽𐌾𐌰𐌹 𐍅𐌹𐌻𐌴𐌹𐌶𐌿 𐍃𐍉𐌺𐌽𐌰𐌹𐍃 𐍃𐍀𐌹𐌻𐌻 𐌰𐍆𐌷𐍂𐌰𐌹𐌽𐌾𐌰𐌽?
+ 𐍅𐌹𐌻𐌴𐌹𐌶𐌿 𐌸𐌹𐌶𐌰𐌹 𐍃𐍉𐌺𐌽𐌰𐌹 𐌿𐍃𐌵𐌹𐍃𐍄𐌾𐌰𐌽?
+ 𐍃𐍉𐌺𐌽𐌰𐌹𐍃 𐍃𐍀𐌹𐌻𐌻𐌰𐍅𐌹𐍄𐌿𐌱𐌽𐌹 𐌿𐍃𐌵𐌹𐍃𐍄𐌹𐌸
+ 𐌲𐌰𐌳𐍉𐌼𐌾𐌰𐌽 𐍆𐍂𐌰𐌵𐌹𐍃𐍄𐌴𐌹𐌽𐌰𐌹
+ 𐌿𐍃𐌵𐌹𐍃𐍄𐌾𐌰𐌽
+ 𐍂𐌰𐌸𐌾𐍉𐌽𐍃
+ 𐌰𐍅𐌹𐌻𐌹𐌿𐌸 𐌰𐌽𐌳𐌽𐌿𐌼𐌰𐌽
+ 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐌴𐌹𐍃 𐍅𐌰𐌹𐌻𐌰𐌼𐌴𐍂𐌾𐍉𐍃
+ 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐌴𐌹𐍃 𐌸𐌰𐌹𐍂𐌷 \"𐍃𐍄𐌰𐌳𐌹𐌽𐍃 𐌽𐌴𐍈𐌰\"
+ 𐌷𐌰𐌿𐌷𐌹𐌸𐌰 %d
+ %s (𐌷𐌰𐌿𐌷𐌹𐌸𐌰 %s)
+ 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐌴𐌹𐍃 𐌹𐌽𐌽𐌰𐍄𐌱𐌰𐌿𐍂𐌰𐌽𐍉𐍃
+ 𐌱𐍂𐌿𐌷𐍄𐍉𐍃 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐌴𐌹𐍃
+ 𐌸𐌰𐍄𐌰 𐌼𐌹𐌽𐌽𐌹𐍃𐍄𐍉 𐌽𐌰𐌿𐌳𐌹𐌸𐌰𐌿𐍂𐍆𐍄𐌰𐌹:
+ 𐍂𐌰𐌸𐌾𐍉 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐌴 𐌸𐍉𐌶𐌴𐌹 𐌹𐌽𐌽𐌰𐍄𐌱𐌰𐍂𐍄 𐌳𐌿 𐌲𐌰𐌼𐌰𐌹𐌽𐌰𐌼, 𐌸𐌰𐌹𐍂𐌷 𐌷𐌽𐌰𐍃𐌵𐌿𐍅𐌰𐍂𐌰 𐌳𐌿 𐌹𐌽𐌽𐌰𐍄𐌱𐌰𐌹𐍂𐌰𐌽
+ 𐍂𐌰𐌸𐌾𐍉 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐌴 𐌹𐌽𐌽𐌰𐍄𐌱𐌰𐌿𐍂𐌰𐌽𐌰𐌹𐌶𐍉 𐌳𐌿 𐌲𐌰𐌼𐌰𐌹𐌽𐌰𐌹𐌼 𐌸𐍉𐌶𐌴𐌹 𐌱𐍂𐌿𐌷𐍄𐍉𐍃 𐍃𐌹𐌽𐌳 𐌹𐌽 𐌻𐌰𐌿𐌱𐌰𐌼 𐍅𐌹𐌺𐌹𐌼𐌰𐌹𐌳𐌾𐌴
+ 𐌰𐌹𐍂𐌶𐌴𐌹 𐍅𐌰𐍂𐌸!
+ 𐌽𐌰𐌼𐍉 𐌱𐍉𐌺𐌰𐍂𐌴𐌹𐍃 𐌱𐌹𐌿𐌷𐍄
+ 𐌱𐌹𐌰𐌿𐌺𐌰𐌹𐌽𐌴𐌹𐍃
+ 𐌽𐌴𐍈𐌰
+ 𐍅𐌹𐌺𐍉
+ 𐌽𐌹 𐍃𐌰𐌹𐍈𐌹𐍃 𐌸𐌰𐌽𐌰𐍃𐌴𐌹𐌸𐍃 𐌸𐌰𐌽𐌰 𐌽𐌴𐍈𐌹𐍃𐍄𐌰𐌽 𐍃𐍄𐌰𐌸 𐍃𐌰𐌴𐌹 𐌸𐌰𐍂𐍆 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐌴. 𐌰𐌸𐌸𐌰𐌽 𐌼𐌰𐌲𐍄 𐌰𐍆𐍄𐍂𐌰-𐌻𐌰𐌲𐌾𐌰𐌽 𐌸𐍉 𐌼𐌴𐍂𐌴𐌹𐌽 𐌹𐌽 𐌻𐌰𐌲𐌴𐌹𐌽𐌹𐌼 𐌾𐌰𐌱𐌰𐌹 𐍅𐌹𐌻𐌴𐌹𐍃.
+ 𐌹𐍆𐍄𐌿𐌼
+ 𐍃𐍀𐌴𐌳𐌹𐍃𐍄
+ 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐌴𐌹𐍃
+ 𐍃𐍄𐌰𐌳𐌴𐌹𐍃
+ 𐌺𐌿𐌽𐌾𐌰
+ 𐌱𐍉𐌺𐌰𐌼𐌰𐍂𐌺𐍉𐍃
+ 𐌽𐌹 𐌽𐌰𐌿𐌷 𐌱𐌹𐌰𐌹𐌰𐌿𐌺𐍄 𐌱𐍉𐌺𐌰𐌼𐌰𐍂𐌺𐍉𐍃.
+ 𐌱𐍉𐌺𐌰𐌼𐌰𐍂𐌺𐍉𐍃
+ 𐌽𐌹 𐍅𐌹𐍃𐍃𐌰 𐌸𐌰𐍄𐌴𐌹 𐌰𐌽𐌳𐌰𐌿𐌲𐌾𐍉 𐌰𐌽𐌰𐍃𐌹𐌿𐌽 𐍅𐌴𐍃𐌹
+ 𐌿𐍆𐌺𐌿𐌽𐌸𐌰 𐌸𐌰𐍄𐌴𐌹 𐌲𐍉𐌸 𐌽𐌹𐍃𐍄 𐍃𐌿𐌽𐌳𐍂𐌰𐌻𐌴𐌹𐌺𐌴𐌹𐌽 𐌼𐌴𐌹𐌽𐌰𐌹
+ 𐌷𐌰𐌱𐌰𐌹 𐌿𐌽𐍃 𐍆𐌰𐌿𐍂𐌵𐌹𐌸𐌰𐌽𐌰𐌹, 𐍃𐍅𐌰𐌻𐌴𐌹𐌺𐌰 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐍃 𐌰𐌹𐌲𐌺𐌴𐌹𐌺𐌻𐌰𐌿𐍀𐌰𐌹𐌳𐌾𐌰𐌹 𐌲𐌰𐍄𐌹𐌻𐌰 𐌽𐌹𐍃𐍄
+ 𐍅𐌹𐌻𐌾𐌰𐌵𐌿𐌼𐌰 𐌳𐌿 𐌲𐌰𐌼𐌰𐌹𐌽𐌰𐌼!\n\n𐌼𐌰𐌲𐍄 𐌼𐌰𐌹𐌳𐌾𐌰 𐍆𐍂𐌿𐌼𐌹𐍃𐍄𐌰 𐌸𐌴𐌹𐌽𐌰 𐌹𐌽𐌽𐌰𐍄𐌱𐌰𐌹𐍂𐌰𐌽 𐌸𐍂𐌿𐌺𐌾𐌰𐌽𐌳𐍃 𐌰𐌽𐌰 𐌷𐌰𐌿𐌱𐌹𐌳𐌹𐌻𐍉𐌽 𐌱𐌹𐌰𐌹𐌺𐌰𐌽.
+ 𐌽𐌹 𐌺𐌿𐌽𐌾𐌰 𐌲𐌰𐍅𐌰𐌻𐌹𐌳𐌰
+ 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐌴 𐌹𐌽𐌿𐌷 𐌺𐌿𐌽𐌾𐌰 𐌽𐌹 𐌿𐍆𐍄𐌰 𐌱𐍂𐌿𐌺𐌾𐌰𐌽𐌳𐌰. 𐌱𐌹 𐍃𐌿𐌽𐌾𐌰𐌹 𐍅𐌹𐌻𐌴𐌹𐌶𐌿 𐌸𐌰𐌹𐍂𐌷𐍅𐌹𐍃𐌰𐌽 𐌽𐌹𐌱𐌰 𐌲𐌰𐍅𐌰𐌻𐌹𐌳𐌰 𐌺𐌿𐌽𐌾𐌰?
+ 𐌹𐌽𐌽𐌰𐍄𐌱𐌰𐌿𐍂 𐍃𐍅𐌴𐌹𐌱𐌰𐌽
+ 𐌱𐍂𐌿𐌺𐌴𐌹𐌽𐍃 𐌷𐌰𐌿𐌱𐌹𐌳𐌹𐌻𐍉𐌽𐍃 𐌳𐌿 𐌲𐌰𐍅𐌰𐌽𐌳𐌾𐌰𐌽 𐌸𐌿𐌺 𐍃𐍅𐌴𐌹𐌱𐌹𐌸 𐌸𐌰𐍄𐌰 𐌹𐌽𐌽𐌰𐍄𐌱𐌰𐌿𐍂 𐌾𐌰𐌷 𐍆𐍂𐌰𐌻𐌹𐌿𐍃𐌹𐍃 𐍆𐍂𐌰𐌼𐌲𐌰𐌷𐍄𐌰𐌹 𐌸𐌴𐌹𐌽𐌰𐌹
+ 𐌹𐌽𐌽𐌰𐍄𐌱𐌰𐌿𐍂 𐌸𐌰𐌹𐍂𐌷𐍅𐌹𐍃𐌰𐌽
+ 𐌹𐌽 𐌸𐌰𐌼𐌼𐌰 𐌲𐌰𐌿𐌾𐌰 𐍃𐍉𐌺𐌾𐌰𐌽
+ 𐌱𐌹𐌳𐌰 𐌰𐌽𐌳𐌻𐌴𐍄𐌹𐍃
+ 𐍅𐌹𐌻𐌴𐌹𐌶𐌿 𐌰𐌽𐌳𐍅𐌰𐌹𐍂𐌸𐌰𐌼𐌼𐌰 𐍃𐍄𐌰𐌳𐌰 𐌸𐌴𐌹𐌽𐌰𐌼𐌼𐌰 𐌱𐍂𐌿𐌺𐌾𐌰𐌽 𐌳𐌿 𐌱𐌰𐌽𐌳𐍅𐌾𐌰𐌽 𐌸𐌰𐌽𐌰 𐍃𐍄𐌰𐌸 𐌽𐌴𐍈𐌹𐍃𐍄𐌰𐌽 𐍃𐌰𐌴𐌹 𐌸𐌰𐍂𐍆 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐌴?
+ 𐌽𐌹 𐌼𐌰𐌷𐍄𐌰 𐌱𐌰𐌽𐌳𐍅𐌾𐌰𐌽 𐌸𐌰𐌽𐌰 𐍃𐍄𐌰𐌸 𐌽𐌴𐍈𐌹𐍃𐍄𐌰𐌽 𐍃𐌰𐌴𐌹 𐌸𐌰𐍂𐍆 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐌴 𐌹𐌽𐌿𐌷 𐌰𐌽𐌳𐌻𐌴𐍄𐌰 𐌻𐍉𐌲𐌹𐍃
+ 𐌸𐌰𐍄𐌰 𐌽𐌹 𐍆𐍂𐌰𐌹𐌷𐌽𐌰𐌽 𐌰𐍆𐍄𐍂𐌰 𐌰𐌹𐍅
+ 𐍆𐍂𐌰𐌹𐌷𐌽𐌰𐌽 𐌰𐌽𐌳𐌻𐌴𐍄 𐍃𐍄𐌰𐌳𐌹𐍃
+ 𐌿𐍃𐍄𐌹𐌿𐌷𐌹𐌸:
+ 𐌰𐌽𐌳𐌻𐌴𐍄𐌰𐌽
+ 𐌰𐌹𐍂𐌶𐌴𐌹 𐍅𐌰𐍂𐌸 𐍅𐌰𐌿𐍂𐌺𐌾𐌰𐌽 𐌸𐍉 𐍆𐍂𐌹𐍃𐌰𐌷𐍄. 𐌱𐌹𐌳𐌾𐌰𐌼 𐌸𐌿𐌺, 𐍃𐍉𐌺𐌴𐌹 𐌰𐍆𐍄𐍂𐌰!
+ 𐌽𐌹𐌼𐌹𐌸 𐍄𐌰𐌹𐌺𐌽 𐌹𐌽𐌼𐌰𐌹𐌳𐌴𐌹𐌽𐌰𐌹
+ 𐌲𐌰𐍄𐌰𐍅𐌹𐌸
+ 𐍃𐌰𐌽𐌳𐌾𐌰𐌽𐌳𐌰 𐌰𐍅𐌹𐌻𐌹𐌿𐌳𐌰: 𐍅𐌰𐌿𐍂𐌷𐍄
+ 𐍅𐌰𐌹𐌻𐌰 𐌹𐌽𐍃𐌰𐌽𐌳𐌹𐌳𐌰 𐌰𐍅𐌹𐌻𐌹𐌿𐌸 𐌳𐌿 %1$s
+ 𐌽𐌹 𐌼𐌰𐌷𐍄𐌰 𐍃𐌰𐌽𐌳𐌾𐌰𐌽 𐌰𐍅𐌹𐌻𐌹𐌿𐌳𐌰 %1$s
+ 𐍃𐌰𐌽𐌳𐌾𐌰𐌽𐌳𐌰 𐌰𐍅𐌹𐌻𐌹𐌿𐌳𐌰: 𐌽𐌹 𐍅𐌰𐌿𐍂𐌷𐍄𐌰
+ 𐌰𐍅𐌹𐌻𐌹𐌿𐌳 𐍃𐌰𐌽𐌳𐌾𐌰𐌳𐌰 𐍆𐌰𐌿𐍂 %1$s
+ 𐌹𐍃𐍄 𐌸𐌰𐍄𐌰 𐌹𐌽 𐌺𐌿𐌽𐌾𐌰 𐍂𐌰𐌹𐌷𐍄𐌰𐌼𐌼𐌰?
+ 𐍅𐌹𐌻𐌴𐌹𐌶𐌿 𐌰𐍅𐌹𐌻𐌹𐌿𐌳𐍉 𐌸𐌰𐌼𐌼𐌰 𐌱𐌹𐌰𐌿𐌺𐌰𐌽𐌳?
+ 𐌰𐍄𐍄𐌴𐌺 \'𐌽𐌴\' 𐌳𐌿 𐌳𐍉𐌼𐌾𐌰𐌽 𐌸𐍉 𐍆𐍂𐌹𐍃𐌰𐌷𐍄 𐍆𐍂𐌰𐌵𐌹𐍃𐍄𐌴𐌹𐌽𐌰𐌹 𐌾𐌰𐌱𐌰𐌹 𐌽𐌹𐍃𐍄 𐌰𐌻𐌻𐌹𐍃 𐌱𐍂𐌿𐌺𐌰.
+ 𐍉, 𐌸𐌰𐍄𐌰 𐌽𐌹 𐌷𐌰𐌱𐌰𐌹𐌸 𐌺𐌿𐌽𐌹!
+ 𐍃𐍉 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐍃 𐌷𐌰𐌱𐌰𐌹𐌸 %1$s 𐌺𐌿𐌽𐌾𐌰.
+ 𐌹𐍆𐍄𐌿𐌼𐌰 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐍃
+ 𐌾𐌰𐌹, 𐌳𐌿𐍈𐌴 𐌽𐌹
+ 𐌽𐌹 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐌴𐌹𐍃 𐌱𐍂𐌿𐌷𐍄𐍉𐍃
+ 𐌰𐌽𐌰𐌺𐌿𐌽𐌽𐌰𐌽 𐍃𐌰𐌹𐍈𐌰𐌽
+ 𐌿𐌽𐌰𐌽𐌰𐌺𐌿𐌽𐌽𐌰𐌽 𐍃𐌰𐌹𐍈𐌰𐌽
+ 𐌰𐌹𐍂𐌶𐌴𐌹 𐍅𐌰𐍂𐌸 𐌽𐌿𐌼𐍄𐌰𐌹 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐌴
+ 𐌱𐌹𐌳𐌾𐌰𐌼 𐌸𐌿𐌺 𐌱𐌴𐌹𐌳𐌰𐌽...
+ 𐌸𐍉 𐍆𐍂𐌹𐍃𐌰𐌷𐍄 𐍄𐍂𐌹𐍀𐍀𐍉𐌽
+ EXIF 𐍃𐍉𐌺𐌽𐌹𐍅𐌰𐌿𐍂𐌳𐌰 𐌰𐍄𐌲𐌰𐍂𐌰𐌹𐌷𐍄𐌾𐌰𐌽
+ 𐌱𐍉𐌺𐌰𐍂𐌴𐌹𐍃
+ 𐍃𐍄𐌰𐌸𐍃
+ 𐌷𐌽𐌰𐍃𐌵𐌿𐍅𐌰𐍂𐌰
+ 𐌰𐍄𐌲𐌰𐌲𐌲 𐌳𐌿 𐍃𐍄𐌰𐌳𐌰 𐌼𐌰𐌹𐌳𐌾𐌹𐍃 𐌽𐌹 𐌷𐌰𐌱𐌰𐌹𐍃
+ 𐌰𐍀𐍀 𐌳𐌰𐌹𐌻𐌾𐌰𐌽 𐌸𐌰𐌹𐍂𐌷...
+ 𐌺𐌿𐌽𐌸𐌹 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐌰𐌹𐍃
+ 𐌽𐌹 𐌺𐌿𐌽𐌾𐌰 𐌱𐌹𐌲𐌹𐍄𐌰𐌽𐌰
+ 𐌳𐌰𐍄𐌰 𐌽𐌹 𐍃𐌹𐌽𐌳 𐍃𐍀𐌴𐌳𐌹𐍃𐍄𐌰𐌹𐌶𐍉𐍃 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐌰𐌹𐍃 𐌿𐍆𐌰𐍂𐌼𐌴𐌻𐌾𐌰 𐌸𐌰𐌿 𐌲𐌰𐍃𐌺𐌴𐌹𐍂𐌴𐌹𐌽𐌹𐌼
+ 𐌳𐌿𐍈𐌴 𐍃𐌺𐌿𐌻𐌳 𐌹𐍃𐍄 %1$s 𐌿𐍃𐌵𐌹𐍃𐍄𐌾𐌰𐌽?
+ 𐍅𐌰𐌹𐌻𐌰 𐌲𐌰𐍄𐌰𐍅𐌹𐌸
+ 𐌲𐌰𐌳𐍉𐌼𐌹𐌳𐌰 %1$s 𐍆𐍂𐌰𐌵𐌹𐍃𐍄𐌴𐌹𐌽𐌰𐌹.
+ 𐌱𐍂𐌰𐌺
+ 𐌽𐌹 𐌼𐌰𐌷𐍄𐌰 𐌱𐌹𐌳𐌾𐌰𐌽 𐍆𐍂𐌰𐌵𐌹𐍃𐍄𐌴𐌹𐌽𐌰𐌹𐍃.
+ 𐍃𐌹𐌻𐌱𐌴𐌹 𐌽𐌹 𐌱𐍂𐌿𐌷𐍄𐌰 𐌹𐌽 𐌻𐌰𐌿𐌱𐌰
+ 𐌳𐍅𐌰𐌻𐌰𐍅𐌰𐌿𐍂𐌳𐌴𐌹, 𐌱𐌹 𐍃𐌿𐌽𐌾𐌰𐌹 𐌿𐌽𐌱𐍂𐌿𐌺 𐌹𐍃𐍄 𐌻𐌰𐌿𐌱𐌰
+ 𐌻𐍉𐌲𐍉
+ 𐌿𐌽𐍄𐌴 𐌹𐍃𐍄
+ 𐌽𐌹 𐌼𐌰𐌷𐍄𐌰 𐌺𐌿𐌽𐌾𐌰 𐌱𐌹𐌰𐌹𐌺𐌰𐌽.
+ 𐌺𐌿𐌽𐌾𐌰 𐌰𐌽𐌰𐌽𐌹𐌿𐌾𐌰𐌽
+ 𐌺𐌹𐌿𐍃𐌹𐌸 𐌰𐍆𐍄𐍂𐌰𐌰𐌽𐌰𐌽𐌹𐌿𐌾𐌰𐌽 𐌲𐌰𐍄𐌴𐍅𐍉𐍃.
+ 𐌰𐍆𐍄𐍂𐌰𐌰𐌽𐌰𐌽𐌹𐌿𐌴𐌹𐌽𐍃 𐌲𐌰𐍄𐌴𐍅𐍉
+ 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐌰𐌹𐍃 𐌲𐌰𐍄𐌴𐍅𐍉𐍃 𐌽𐌹 𐌰𐍆𐍄𐍂𐌰𐌰𐌽𐌰𐌽𐌹𐌿𐌾𐌹𐌳𐍉𐍃
+ 𐍆𐍂𐌹𐍃𐌰𐌷𐍄 𐌳𐌰𐌹𐌻𐌾𐌰𐌽 𐌸𐌰𐌹𐍂𐌷
+ 𐌰𐌺𐌰𐍅𐌽𐍄 𐌲𐌰𐍃𐌺𐌰𐍀𐌰𐌽!
+ 𐍃𐌿𐌼𐌰 𐌰𐌹𐍂𐌶𐌴𐌹 𐍅𐌰𐍂𐌸!
+ 𐌹𐍃𐍄
+ 𐌸𐌰𐍂𐍆 𐌻𐌹𐌿𐌷𐌰𐌳𐌰𐌼𐌴𐌻𐌾𐌹𐍃
+ 𐌱𐍂𐌿𐌳𐌳𐌹, 𐌼𐌿𐍃𐌴𐌹𐍉, 𐍃𐌰𐌻𐌹𐌸𐍅𐍉𐍃 𐌾𐌰𐌷 𐍃𐍅𐌴 𐍆𐍂𐌰𐌼𐍅𐌹𐌲𐌹𐍃
+ 𐌼𐌰𐌹𐌳𐌾𐌰
+ 𐌿𐍆𐌺𐌿𐌽𐌾𐌰
+ 𐍃𐍄𐌰𐌸𐍃 𐌽𐌴𐍈𐌰 𐌱𐌹𐌲𐌰𐍄
+ 𐍃𐌹𐌽𐌳𐌿 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐌴𐌹𐍃 𐍅𐌰𐌹𐌷𐍄𐌰𐌹𐍃 %1$s?
+ 𐌹𐍃𐍄𐌿 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐍃 𐍅𐌰𐌹𐌷𐍄𐌰𐌹𐍃 %1$s?
+ 𐌱𐍉𐌺𐌰𐌼𐌰𐍂𐌺𐍉𐍃
+ 𐍃𐌰𐍄𐌴𐌹𐌽𐍉𐍃
+ 𐍂𐌹𐌵𐌹𐍃
+ 𐌻𐌹𐌿𐌷𐌰𐌸
+ 𐌱𐌰𐍃𐍄𐌹𐍃𐍄𐍉𐌽𐌰𐌼 𐍄𐍉𐌾𐌰𐌼, 𐌱𐍂𐌿𐌺𐌴𐌹 𐌼𐍉𐌳𐌰𐌿𐍃 𐌷𐌰𐌿𐌷𐌲𐌻𐌰𐌲𐌲𐍅𐌹𐍃.
+ 𐌻𐍉𐌲 𐌰𐌽𐌰𐌻𐌰𐌲𐌾𐌰𐌽?
+ 𐌼𐌰𐌹𐍃 𐌰𐍄𐌱𐍂𐌹𐌲𐌲𐌰𐌽
+ 𐌽𐌹 𐍃𐍄𐌰𐌳𐌴𐌹𐍃 𐌱𐌹𐌲𐌹𐍄𐌰𐌽𐌰𐌹, 𐌲𐌰𐌺𐌰𐌿𐍃𐌴𐌹 𐌼𐌹𐍄𐌰𐌳𐌾𐍉𐌽𐍃 𐌹𐌽𐌼𐌰𐌹𐌳𐌴𐌹𐌽𐌰𐌹𐍃 𐍃𐍉𐌺𐌽𐌰𐌹𐍃.
+ 𐌲𐌰𐌱𐍉𐍄𐌴𐌹𐌽𐍉𐍃 𐌰𐌽𐌰𐍆𐌹𐌻𐌷𐌰𐌽𐍉𐍃:
+ - 𐌺𐌿𐌽𐌾𐌰 𐌱𐌹𐌰𐌿𐌺𐌰𐌽 𐌳𐌿 𐌸𐌹𐌶𐌰𐌹 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐌰𐌹 𐌲𐌰𐌱𐍉𐍄𐌾𐌰𐌽 𐌱𐍂𐌿𐌺𐌴𐌹𐌽.
+ 𐍆𐍂𐌹𐍃𐌰𐌷𐍄 𐌱𐌹𐌰𐌿𐌺𐌰𐌽 𐌳𐌿 𐍅𐌹𐌺𐌹𐍀𐌰𐌹𐌳𐌾𐌰𐌹
+ 𐌲𐌰𐍄𐌿𐌻𐌲𐌾𐌰𐌽
+ 1. 𐌱𐍂𐌿𐌺𐌴𐌹 𐌹𐍆𐍄𐌿𐌼𐌰𐌹𐌶𐍉 𐍅𐌹𐌺𐌹𐌱𐍉𐌺𐍉:
+ 𐌸𐌰𐌹𐍂𐌷𐍅𐌹𐍃𐌰𐌽
+ 𐌼𐌰𐌹𐍃
+ 𐌱𐍉𐌺𐌰𐌼𐌰𐍂𐌺𐍉𐍃
+ 𐍃𐌹𐌲𐌹𐍃𐌱𐌰𐌿𐍂𐌳
+ 𐍂𐌰𐌸𐌾𐍉:
+ 𐌱𐍂𐌿𐌺𐌾𐌰
+ 𐍂𐌰𐌸𐌾𐍉
+ 𐌹𐌽 𐌾𐌴𐍂𐌰𐌼
+ 𐌹𐌽 𐍅𐌹𐌺𐍉𐌼
+ 𐍃𐌹𐌽𐍄𐌴𐌹𐌽𐍉
+ 𐌽𐌴𐍈𐌰
+ 𐌱𐍂𐌿𐌷𐍄
+ 𐌺𐌿𐌽𐌾𐌹𐍃 𐌻𐌰𐌿𐍆 𐍃𐌰𐌹𐍈𐌰𐌽
+ 𐍅𐌰𐌹𐌷𐍄𐌰𐌹𐍃 𐌻𐌰𐌿𐍆 𐍃𐌰𐌹𐍈𐌰𐌽
+ 𐍂𐌰𐌶𐌳𐌰 𐌰𐍀𐍀𐌻𐌴𐌹𐌺𐌰𐍄𐍃𐌾𐍉𐌽𐍃 𐌱𐍂𐌿𐌺𐌾𐌹𐌽𐍃 𐌹𐌽𐍄𐌰𐌹𐍂𐍆𐌰𐌺𐌹𐍉𐍃
+ 𐌼𐌰𐌹𐍃 𐌰𐌽𐌰𐌺𐌿𐌽𐌽𐌰𐌽
+ 𐌹𐌽 𐌰𐌻𐌻𐌰𐌹𐌼 𐍂𐌰𐌶𐌳𐍉𐌼
+ 𐍃𐍄𐌰𐌸 𐍅𐌰𐌻𐌾𐌰𐌽
+ 𐍃𐍄𐌰𐌸 𐍅𐌰𐌻𐌾𐌰𐌽
+ 𐌹𐌽 𐌻𐌰𐌽𐌳𐌰𐍇𐌰𐍂𐍄𐍉𐍃 𐌰𐍀𐍀 𐌱𐌰𐌽𐌳𐍅𐌾𐌰𐌽
+ 𐍃𐍄𐌰𐌸 𐌹𐌽𐌼𐌰𐌹𐌳𐌾𐌰𐌽
+ 𐌽𐌹 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐌴𐌹𐍃
+ 𐌲𐌰𐍄𐌰𐍅𐌹𐌸
+ 𐌲𐌰𐍅𐌰𐌽𐌳𐌾𐌰𐌽 𐌸𐌿𐌺
+ 𐌻𐌰𐌹𐍃𐌾𐌰𐌽 𐌸𐌿𐌺 𐌼𐌰𐌹𐍃
+ 𐌸𐌰𐌿𐍂𐌱𐌰𐌽𐌳 𐌰𐌽𐌳𐌻𐌴𐍄𐌰𐌹𐍃
+ 𐌰𐍆𐍄𐍂𐌰𐌻𐌰𐌲𐌾𐌰𐌽
+ 𐍃𐍄𐌰𐌸 𐌱𐌹𐌰𐌿𐌺𐌰𐌽
+ 𐌿𐍃𐌼𐌴𐍂𐌹 𐌰𐌽𐌳𐍂𐌰𐌿𐌴𐌹𐌳𐍃
+ 𐌼𐌰𐌷𐍄𐌴𐌹𐌲 𐌽𐌹𐍃𐍄 𐍅𐌰𐌻𐌾𐌰𐌽 𐌸𐍉 𐍆𐍂𐌹𐍃𐌰𐌷𐍄 𐌳𐌿 𐌹𐌽𐌽𐌰𐍄𐌱𐌰𐌹𐍂𐌰𐌽
+ 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐍃 𐌲𐌰𐍅𐌰𐌻𐌹𐌳𐌰
+ 𐌽𐌹 𐌼𐌰𐌷𐍄𐌰 𐌳𐌰𐌹𐌻𐌾𐌰𐌽 𐌸𐍉 𐍅𐌰𐌹𐌷𐍄
+ 𐍆𐍂𐌹𐍃𐌰𐌷𐍄 𐌹𐌽𐌼𐌰𐌹𐌳𐌾𐌰𐌽
+ 𐍃𐍄𐌰𐌸 𐌹𐌽𐌼𐌰𐌹𐌳𐌾𐌰𐌽
+ 𐍃𐍄𐌰𐌸𐍃 𐌰𐍆𐍄𐍂𐌰𐌰𐌽𐌰𐌽𐌹𐌿𐌾𐌹𐌸!
+ 𐌻𐍉𐌲 𐌿𐍃𐌽𐌹𐌼𐌰𐌽
+ 𐌻𐍉𐌲𐌹𐍃 𐍈𐍉𐍄𐌰 𐌿𐍃𐌽𐌹𐌼𐌰𐌽
+ 𐌻𐍉𐌲 𐌿𐍃𐌽𐌿𐌼𐌰𐌽!
+ 𐌱𐍉𐌺𐌰𐍂𐌾𐌰 𐌰𐍅𐌹𐌻𐌹𐌿𐌳𐍉𐌽
+ 𐌰𐌹𐍂𐌶𐌴𐌹 𐍅𐌰𐍂𐌸 𐌹𐌽 𐍃𐌰𐌽𐌳𐌴𐌹𐌽𐌰𐌹 𐌰𐍅𐌹𐌻𐌹𐌿𐌳𐌹𐍃 𐌳𐌿 𐌱𐍉𐌺𐌰𐍂𐌾𐌰.
+ 𐌰𐍄𐌲𐌰𐌲𐌲 𐌿𐍃𐍄𐌰𐌿𐌷. 𐌱𐌹𐌳𐌾𐌰𐌼 𐌸𐌿𐌺 𐌰𐍆𐍄𐍂𐌰 𐌰𐍄𐌲𐌰𐌲𐌲𐌰𐌽.
+ 𐍅𐌹𐌻𐌴𐌹𐌶𐌿 𐌲𐍀𐍇 𐍆𐌰𐌾𐌻 𐌿𐍃𐌻𐌿𐌺𐌰𐌽?
+ 𐍅𐌹𐌻𐌴𐌹𐌶𐌿 𐌺𐌼𐌻 𐍆𐌰𐌾𐌻 𐌿𐍃𐌻𐌿𐌺𐌰𐌽?
+ 𐌽𐌹 𐌼𐌰𐌷𐍄𐌰 𐌺𐌼𐌻 𐍆𐌰𐌾𐌻 𐌲𐌰𐍆𐌰𐍃𐍄𐌰𐌽.
+ 𐌽𐌹 𐌼𐌰𐌷𐍄𐌰 𐌲𐍀𐍇 𐍆𐌰𐌾𐌻 𐌲𐌰𐍆𐌰𐍃𐍄𐌰𐌽.
+ 𐌱𐌹𐌳𐌾𐌰𐌼 𐌸𐌿𐌺, 𐌼𐌴𐌻𐌴𐌹 𐍅𐌰𐌿𐍂𐌳𐌰 𐍃𐌿𐌼𐌰
+ 𐌲𐌰𐍅𐌰𐌿𐍂𐌳𐌹
+ \'%1$s\' 𐌸𐌰𐌽𐌰𐍃𐌴𐌹𐌸𐍃 𐌽𐌹𐍃𐍄, 𐌽𐌹 𐍆𐍂𐌹𐍃𐌰𐌷𐍄𐍃 𐌸𐌹𐌶𐌴𐌹 𐌼𐌰𐌷𐍄𐌴𐌹𐌲 𐌹𐍃𐍄 𐌰𐌹𐍅 𐌽𐌹𐌼𐌰𐌽.
+ \'%1$s\' 𐌹𐍃𐍄 𐌹𐌽 𐍃𐍄𐌰𐌳𐌰 𐌰𐌽𐌸𐌰𐍂𐌰𐌼𐌼𐌰.
+ 𐌱𐌹 𐍃𐌿𐌽𐌾𐌰𐌹 𐍅𐌹𐌻𐌴𐌹𐌶𐌿 𐍃𐍅𐌴𐌹𐌱𐌰𐌽 𐌰𐌻𐌻 𐌸𐌰𐍄𐌴𐌹 𐌹𐌽𐌽𐌰𐍄𐌱𐌰𐌹𐍂𐌰𐌳𐌰?
+ 𐌿𐍃𐌵𐌹𐍃𐍄𐌴𐌹𐌽 𐌲𐌰𐍄𐌿𐌻𐌲𐌾𐌰𐌽
+ 𐌿𐍃𐌵𐌹𐍃𐍄𐌾𐌰𐌽
+ 𐍃𐍅𐌴𐌹𐌱𐌰𐌽
+ 𐍃𐌰 𐍃𐍄𐌰𐌸𐍃 𐌽𐌹 𐌷𐌰𐌱𐌰𐌹𐌸 𐌽𐌰𐌿𐌷 𐍆𐍂𐌹𐍃𐌰𐌷𐍄, 𐌽𐌹𐌼 𐍃𐌿𐌼𐌰!
+ 𐍃𐌰 𐍃𐍄𐌰𐌸𐍃 𐌾𐌿 𐍆𐍂𐌹𐍃𐌰𐌷𐍄 𐌷𐌰𐌱𐌰𐌹𐌸.
+ 𐌽𐌿 𐍃𐌰𐌹𐍈𐌰𐌳𐌰 𐌾𐌰𐌱𐌰𐌹 𐍃𐌰 𐍃𐍄𐌰𐌸𐍃 𐌷𐌰𐌱𐌰𐌹𐌸 𐍆𐍂𐌹𐍃𐌰𐌷𐍄.
+ 𐌱𐍂𐌿𐌺𐌴𐌹𐌽𐍉𐍃 𐌽𐌹 𐌱𐌹𐌲𐌹𐍄𐌰𐌽𐍉𐍃
+ 𐌲𐌰𐌼𐌰𐌹𐌽𐌰
+ 𐌰𐌽𐌸𐌰𐍂𐌰 𐍅𐌹𐌺𐌾𐌰
+ 𐌱𐍂𐌿𐌺𐌴𐌹𐌽𐍉𐍃 𐍆𐌰𐌾𐌻𐌹𐍃
+ 𐍂𐌰𐌷𐌽𐌴𐌹𐌽𐍃
+ 𐌹𐌽 𐌽𐌴𐍈𐌰 𐌱𐌰𐌽𐌳𐍅𐌾𐌰𐌽
+ 𐌲𐌰𐍃𐌺𐌰𐍀𐌰𐌽 𐍆𐍂𐌰𐌼 %1$s 𐌾𐌰𐌷 𐌹𐌽𐌽𐌰𐍄𐌱𐌰𐌿𐍂𐌰𐌽 𐍆𐍂𐌰𐌼 %2$s
+
diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml
index dcb769ca1..1f98d2f70 100644
--- a/app/src/main/res/values-hi/strings.xml
+++ b/app/src/main/res/values-hi/strings.xml
@@ -152,10 +152,10 @@
डिफॉल्ट लाइसेन्स
पिछले शीर्षक/विवरण का उपयोग करें
थीम
- एट्रीब्यूशन-शेयरअलाइक 4.0
- एट्रिब्यूशन 4.0
- एट्रीब्यूशन-शेयरअलाइक 3.0
- एट्रीब्यूशन 3.0
+ एट्रीब्यूशन-शेयरअलाइक 4.0
+ एट्रिब्यूशन 4.0
+ एट्रीब्यूशन-शेयरअलाइक 3.0
+ एट्रीब्यूशन 3.0
सीसी0
सीसी बाय-एसए 3.0
CC BY 3.0
@@ -301,6 +301,7 @@
प्रश्न
परिणाम
सेल्फी की ज्ञानकोषीय महत्ता बहुत अधिक नहीं है। कृपया अपना चित्र तब तक अपलोड न करें जबतक आपके बारे में कोई विकिपीडिया लेख पहले से उपलब्ध नहीं है।
+ जारी रखें
उत्तर हेतु प्रश्न के दो विकल्प में से कोई एक चुने
लॉग इन सत्र समाप्त हो गया, कृपया पुनः लॉग इन करें।
अपनी प्रश्नोत्तरी अपने मित्रों के साथ साझा करें!
@@ -322,7 +323,7 @@
धन्यवाद प्राप्त किया
निर्वाचित चित्र
स्तर %d
- %s (स्तर %s )
+ %s (स्तर %s )
चित्र अपलोड हुआ
चित्रों को वापस नहीं किया गया
उपयोग हुए चित्र
@@ -374,6 +375,7 @@
सफल
गणना
रद्द करें
+ वैकल्पिक अनुमति: श्रेणी सुझाव हेतु वर्तमान स्थान ज्ञात करें
वार्ता
क्या आप वाकई सभी अपलोड रद्द करना चाहते हैं?
सभी अपलोड रद्द किये जा रहे हैं...
diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml
index 155881491..fb1b2154e 100644
--- a/app/src/main/res/values-hr/strings.xml
+++ b/app/src/main/res/values-hr/strings.xml
@@ -113,10 +113,10 @@
Podrazumijevana licencija
Rabite prethodni naziv i opis
Izgled
- Imenovanje-Dijeli pod istim uvjetima 4.0
- Imenovanje 4.0
- Imenovanje-Dijeli pod istim uvjetima 3.0
- Imenovanje 3.0
+ Imenovanje-Dijeli pod istim uvjetima 4.0
+ Imenovanje 4.0
+ Imenovanje-Dijeli pod istim uvjetima 3.0
+ Imenovanje 3.0
CC0
CC BY-SA 3.0
CC BY 3.0
@@ -305,4 +305,5 @@
Mjesto
Suradnica/suradnik
Pogrješka pri postavljanju novoga avatara, molimo Vas, pokušajte ponovno.
+ Potrebno dopuštenje za određivanje trenutačne lokacije za prijedloge kategorija (nije obvezno)
diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml
index a52805873..ff4881119 100644
--- a/app/src/main/res/values-hu/strings.xml
+++ b/app/src/main/res/values-hu/strings.xml
@@ -137,10 +137,10 @@
Alapértelmezett licenc
Előző cím és leírás használata
Téma
- Nevezd meg! – Így add tovább! 4.0
- Nevezd meg! 4.0
- Nevezd meg! – Így add tovább! 3.0
- Attribution 3.0
+ Nevezd meg! – Így add tovább! 4.0
+ Nevezd meg! 4.0
+ Nevezd meg! – Így add tovább! 3.0
+ Attribution 3.0
CC0
CC BY-SA 3.0
CC BY 3.0
@@ -482,6 +482,7 @@
Korlátozott kapcsolat-mód kikapcsolva. Függőben lévő feltöltések folytatása.
Korlátozott kapcsolat mód
Itt ellenőrizheted a már Commonsba feltöltött képeket.
+ Lehetséges engedély: Jelenlegi hely megszerzése, a kategóriajavaslatok lehetőségéért.
Megjelölés, mint nem feltöltendő
Nem feltöltendő megjelölés visszavonása
Jelentés
diff --git a/app/src/main/res/values-ia/strings.xml b/app/src/main/res/values-ia/strings.xml
index bdea0dbd8..add190e0f 100644
--- a/app/src/main/res/values-ia/strings.xml
+++ b/app/src/main/res/values-ia/strings.xml
@@ -33,7 +33,7 @@
- (%1$d)
- (%1$d)
- Initia incargamentos
+ Initia incargamentos
- Processa %d incargamento
- Processa %d incargamentos
@@ -150,15 +150,18 @@
Tu non ha ancora incargate alcun photo.
Reprobar
Cancellar
+ Insere nomine de lingua
+ Recercas recente
+ Tote le linguas
Submittente iste imagine, io declara que iste es mi obra proprie, que illo non contine material protegite per derectos de autor ni ‘selfies’, e adhere al <a href=\"https://commons.wikimedia.org/wiki/Commons:Policies_and_guidelines\">politicas de Wikimedia Commons</a>.
Discargar
Licentia predefinite
Usar le titulo e description precedente
Thema
- Attribution-CompartiSimile 4.0
- Attribution 4.0
- Attribution-CompartiSimile 3.0
- Attribution 3.0
+ Attribution-CompartiSimile 4.0
+ Attribution 4.0
+ Attribution-CompartiSimile 3.0
+ Attribution 3.0
Wikimedia Commons alberga le major parte del imagines usate in Wikipedia.
Tu imagines adjuta a educar gente de tote le mundo!
Per favor incarga solmente imagines que tu mesme ha prendite o create:
@@ -206,6 +209,7 @@
Description
Discussion
Autor
+ Incargator
Data de incargamento
Licentia
Coordinatas
@@ -234,7 +238,7 @@
A proposito
Parametros
Commentario
- Retroaction per GitHub
+ Commentarios per GitHub
Clauder session
Tutorial
Notificationes
@@ -256,6 +260,7 @@
Le processo de incargamento require un accesso a internet active. Per favor verifica tu connexion al rete.
Problemas trovate in le imagine
Per favor, solmente incarga imagines que tu ha prendite tu mesme. Non incarga imagines que tu ha discargate del Internet.
+ Incargamentos
Salveguardar photos prendite in app
Salveguardar le photos prendite con le camera del application sur le immagazinage de tu apparato
Aperir session in tu conto
@@ -364,7 +369,7 @@
Imagines eminente
Imagines via “Locos a proximitate”
Nivello %d
- %s (Nivello %s)
+ %s (Nivello %s)
Imagines incargate
Imagines non revertite
Imagines usate
@@ -429,6 +434,8 @@
Termina le:
Monstrar campanias
Vider le campanias in curso
+ Monstrar button de deletion
+ Activar le button “Deler dossier” in le selector personalisate
Permitter al application obtener le localisation in caso que le camera non lo registra. Alcun cameras de apparatos non registra le localisation. In tal casos, permitter al application obtener e attachar le localisation rende tu contribution plus utile. Tu pote cambiar isto a tote momento in le Parametros.
Permitter
Clauder
@@ -666,7 +673,7 @@
Monstrar in application cartographic
Modificar position
Le vista in imagine del selector de loco
- \n Le umbra del vista in imagine del selector de loco
+ Le umbra del vista de imagine del selector de loco
Localisation del imagine
Verificar si le localisation es correcte
Etiquetta
@@ -692,6 +699,8 @@
Wiki Loves Monuments es un concurso international de photographias de monumentos organisate per Wikimedia
Permission necessari
Le cartas a proximitate debe leger le STATO DEL TELEPHONO pro functionar correctemente
+ Activa le servicios de localisation pro monstrar le locos a proximitate.
+ Le accesso al position es necessari pro monstrar locos a proximitate sur le carta.
Contributiones del usator: %s
Realisationes del usator: %s
Vider le profilo del usator
@@ -717,8 +726,8 @@
Modello del apparato
Nomine del apparato
Typo de rete
- Gratias pro dar retroaction
- Error durante le invio del retroaction
+ Gratias pro dar commentario
+ Error durante le invio del commentario
Que es tu commentario?
Tu commentario
Marcar como non a incargar
@@ -749,7 +758,7 @@
Permissiones es necessari pro functionalitate
Apprende como scriber un description utile
Appende como scriber un legenda utile
- Vider tu realisationes
+ Examinar tu realisationes
Modificar imagine
Modificar position
Position actualisate!
@@ -782,7 +791,7 @@
‘%1$s’ se trova in un altere loco.
‘%1$s’ es in un altere loco. Per favor specifica le loco correcte hic infra, e si possibile, indica le latitude e longitude correcte.
Altere problema o information (per favor explica hic infra).
- Tu retroaction apparera sur le sequente pagina wiki: <a href=\"https://commons.wikimedia.org/wiki/Commons:Mobile_app/Feedback\">Commons:Mobile app/Feedback</a>
+ Tu commentario apparera sur le sequente pagina wiki: <a href=\"https://commons.wikimedia.org/wiki/Commons:Mobile_app/Feedback\">Commons:Mobile app/Feedback</a>
Es tu secur de voler cancellar tote le incargamentos?
Cancella tote le incargamentos…
Incargamentos
diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml
index 8a44ef84d..686d32e84 100644
--- a/app/src/main/res/values-in/strings.xml
+++ b/app/src/main/res/values-in/strings.xml
@@ -110,17 +110,20 @@
Ambil foto
Sekitar
Unggahan saya
+ Salin pranala
+ Pranala telah disalin ke papan klip
Bagikan
Lihat halaman berkas
Takarir (Wajib)
Berikan takarir untuk berkas ini
Deskripsi
Takarir
- Tidak dapat login - kesalahan pada jaringan
+ Tidak dapat login - kesalahan pada jaringan
Terlalu banyak percobaan masuk yang gagal. Harap coba lagi dalam beberapa menit
Maaf, pengguna ini telah diblokir di Commons
Anda harus memberikan kode otentikasi dua faktor milik Anda
- Gagal masuk log
+ Kode verifikasi masuk log telah dikirim ke alamat surel Anda. Mohon berikan kode untuk masuk log.
+ Gagal masuk log
Unggah
Beri nama set ini
Modifikasi
@@ -161,10 +164,10 @@
Lisensi standar
Gunakan judul dan deskripsi sebelumnya
Tema
- Atribusi-BerbagiSerupa 4.0
- Atribusi 4.0
- Atribusi-BerbagiSerupa 3.0
- Atribusi 3.0
+ Atribusi-BerbagiSerupa 4.0
+ Atribusi 4.0
+ Atribusi-BerbagiSerupa 3.0
+ Atribusi 3.0
CC0
CC BY-SA 3.0
CC BY 3.0
@@ -179,7 +182,7 @@
Mohon JANGAN diunggah:
Foto narsis atau foto teman Anda
Gambar yang Anda unduh dari Internet
- Cuplikan layar suatu aplikasi
+ Cuplikan layar suatu aplikasi tidak bebas
Contoh unggahan:
Judul: Gedung Opera Sydney
Deskripsi: Gedung Opera Sydney yang dilihat dari seberang teluk
@@ -200,7 +203,7 @@
Lisensi tidak diketahui
Segarkan
Meminta Izin Akses Penyimpanan
- Perlu Izin: Membaca penyimpanan eksternal. Tanpa hal ini aplikasi tidak dapat mengakses galeri Anda.
+ Perlu Izin: Membaca penyimpanan eksternal. Tanpa izin ini aplikasi tidak dapat mengakses galeri Anda.
Perlu Izin: Menulis penyimpanan eksternal. Tanpa hal ini aplikasi tidak dapat mengakses kamera/galeri.
Meminta Izin Lokasi
OKE
@@ -351,7 +354,7 @@
Apakah tangkapan layar ini OKE untuk diunggah?
Bagikan Aplikasi
Putar
- Galat saat mengambil tempat terdekat.
+ Tidak dapat memuat tempat-tempat terdekat.
Tidak ada gambar di area ini
Tidak ditemukan tempat yang dekat
Galat saat mengambil monumen terdekat.
@@ -368,7 +371,7 @@
Ucapan terima kasih diterima
Gambar Pilihan
Gambar via \"Tempat di Sekitar\"
- Tingkat
+ Tingkat %d
Gambar Diunggah
Gambar Tidak Dikembalikan
Gambar Digunakan
@@ -410,7 +413,7 @@
Saya menyadari itu buruk untuk privasi saya
Saya berubah pikiran, saya tidak ingin itu terlihat publik lagi
Maaf gambar ini tidak menarik untuk ensiklopedia
- Diunggah saya sendiri pada %1$s, digunakan dalam %2$d artikel.
+ Diunggah saya sendiri pada %1$s, digunakan paling tidak dalam %2$d artikel.
Unggah media pertama Anda dengan mengetuk tombol.
Tidak ada Kategori yang Dipilih
Gambar tanpa kategori jarang dapat digunakan. Apakah Anda yakin ingin mengirim tanpa memilih kategori?
@@ -427,7 +430,7 @@
Jangan pernah menanyakan ini lagi
Tanya untuk izin lokasi
Minta izin lokasi ketika diperlukan untuk fitur tampilan kartu pemberitahuan sekitar.
- Terjadi kesalahan. Kami tidak dapat mengambil pencapaian Anda
+ Terjadi kesalahan. Kami tidak dapat mengambil pencapaian Anda
Anda telah membuat begitu banyak kontribusi sehingga sistem perhitungan pencapaian kami tidak dapat menanggulanginya. Ini adalah pencapaian yang tertinggi.
Berakhir pada:
Sinahang penyobyahan
@@ -435,7 +438,7 @@
Izinkan
Tutup
Anda tidak akan melihat kampanye ini lagi. Namun, Anda bisa mengaktifkan kembali pemberitahuan ini di Pengaturan jika diinginkan.
- Fungsi ini membutuhkan hubungan jaringan, tolong periksa pengaturan jaringan Anda.
+ Fungsi ini memerlukan hubungan jaringan. Tolong periksa pengaturan jaringan Anda.
Terjadi masalah saat memproses gambar. Harap mengulang lagi.
Dapatkan token untuk menyunting
Menambahkan templat untuk pemeriksaan kategori
@@ -543,7 +546,7 @@
Tidak bisa menambahkan koordinat.
Tidak bisa menambahkan deskripsi.
Tidak bisa menambahkan takarir.
- Tidak bisa mendapatkan koordinat.
+ Koordinat gambar tidak diperbarui
Tidak bisa memperoleh deskripsi.
Sunting deskripsi dan takarir
Bagikan gambar melalui
@@ -558,7 +561,7 @@
Perlu foto
Jenis tempat:
Jembatas, museum, hotel, dll.
- Terjadi kesalahan ketika masuk log, Anda perlu mengubah kata sandi Anda !!
+ Terjadi kesalahan ketika masuk log. Anda perlu mengatur ulang kata sandi Anda !!
MEDIA
KELAS ANAK
KELAS INDUK
@@ -578,7 +581,7 @@
Untuk hasil terbaik, pilih mode Akurasi Tinggi.
Nyalakan lokasi?
Tempat sekitar perlu lokasi yang diaktifkan agar bekerja dengan benar
- Anda perlu memberikan akses ke lokasi Anda saat ini untuk mengatur lokasi secara otomatis.
+ Anda perlu memberikan izin lokasi Anda saat ini untuk mengatur lokasi secara otomatis.
Apakah Anda menangkap kedua gambar ini di tempat yang sama? Apakah Anda ingin menggunakan lintang/bujur dari gambar yang di kanan?
Muat Lebih Banyak
Tempat tidak ditemukan, coba ubah kriteria pencarian Anda.
@@ -679,9 +682,10 @@
Wiki Loves Monuments adalah lomba foto internasional untuk monumen yang diselenggarakan oleh Wikimedia
Membutuhkan Izin
Peta Sekitar harus membaca STATUS TELEPON untuk berfungsi dengan benar
+ Pilihan Izin: Lokasi saat ini untuk saran kategori
Kontribusi Pengguna: %s
Pencapaian Pengguna: %s
- Lihat halaman pengguna
+ Lihat profil pengguna
Sunting penggambaran
Sunting kategori
Opsi Lanjutan
diff --git a/app/src/main/res/values-io/strings.xml b/app/src/main/res/values-io/strings.xml
index 1a74c2cc4..b2b3893b0 100644
--- a/app/src/main/res/values-io/strings.xml
+++ b/app/src/main/res/values-io/strings.xml
@@ -155,10 +155,10 @@
Licenco \'\'default\'\'
Uzez antea titulo e deskripto
Temo
- Atributo-ShareAlike 4.0
- Atributo 4.0
- Atributo-ShareAlike 3.0
- Atributo 3.0
+ Atributo-ShareAlike 4.0
+ Atributo 4.0
+ Atributo-ShareAlike 3.0
+ Atributo 3.0
CC BY 3.0
CC BY-SA 4.0
CC BY 4.0
@@ -209,6 +209,7 @@
Deskripto
Diskuto
Autoro
+ Adkarganto
Dato sendita
Licenco
Koordinati
@@ -367,7 +368,7 @@
Remarkinda imaji
Imaji tra \"Loki Vicina\"
Nivelo %d
- %s (Nivelo %s)
+ %s (Nivelo %s)
Imaji sendita
Imaji ne reversionita
Imaji uzita
@@ -410,7 +411,7 @@
Me konstatis ke ol esas mala por mea privateso
Me chanjis mea ideo: me ne pluse deziras ke ol esos publike videbla
Pardonez! Ca imajo ne esas interesanta por ula enciklopedio
- Adjuntita da me, che %1$s, uzita en %2$d artiklo/artikli.
+ Adjuntita da me che %1$s; uzita en adminime %2$d artiklo/artikli.
Bonveno a Commons!\n\nSendez vua unesma arkivo kliktanta sur butono \"adjuntez\" (\'\'add\'\').
Nula kategorio selektita
Imaji sen kategorii rare esas uzebla. Ka vu fakte deziras sendar ol sen selektar irga kategorio?
@@ -436,11 +437,13 @@
Permisar
Eskartar
Voluntez kapabligar registrago di lokizo en \'\'Settings\'\', e probez itere.\n\nNoto: l\'arkivo sendanta povas ne havar informo pri lokizo, se l\'\'\'app\'\' ne povas rekuperar l\'informo pri lokizo en kurta intervalo.
+ La kamero en l\'utensilo bezonas permiso por adjuntar ca informo en imaji, se l\'informo ne esas disponebla che EXIF. Voluntez permisar ke l\'\'\'app\'\' acesez vua lokizo, e probez itere.\n\nAtencez: Esas posibla ke l\'imajo sendonta ne havos informo rekuperebla pri lokizo, se l\'\'\'app\'\' ne povos rekuperor ol pos kurta intervalo.
La programo \'\'app\'\' ne enrejistros informo pri lokizo en la fotografuri pro manko di permisi
Sen kapabligar GPS, l\'enrejistro di la lokizo en la fotografuri ne facesas.
Uzez selektilo di fotografuri segun dokumenti
La nova funciono \'\'Android photo picker\'\' povas perdar informo pri lokizo. Kapabligez ol, se vu semblas uzar ol.
Deskapabliganta ol povos deskuplar la nova funciono \'\'Android photo picker\'\'. Posible perdos informo pri lokizo.
+ Vu ne pluse vidos ta kampanii. Tamen, vu povos itere kapabligar ca avizo en Ajusti (\'\'Settings\'\'), se vu deziros.
Ca funciono bezonas ligilo ad interreto. Verifikez vua ajusti pri konekti.
Eventis eroro dum procesado dil imajo. Voluntez probar ol itere!
Kaptanta \'\'token\'\' por redaktar.
@@ -573,6 +576,8 @@
\'\'MEDIA\'\'
SUBKLASI
KLASI PLU ABSTRAKTA
+ SUB-KATEGORII
+ PRECIPUA KATEGORII
Loko proxima trovesis
Ka ca imaji apartenas a %1$s?
Ka to esas imajo di %1$s?
@@ -636,19 +641,26 @@
Uzita
Mea rango
Kapabligesis por uzar kun limitizita konekti!
+ Posibleso pri uzo kun limitizita konekto deskapabligita. La sendo di arkivi rikomencos nun.
Modo por limitizita retoligilo
Imaji di qualeso
+ Imaji kun qualeso esas diagrami o fotografuri qui havas qualesi (maxim-multa-kaze teknikala) ed esas valoroza por projeti de Wikimedia
Duriganta sendajo...
Pauzanta sendajo...
Nuliganta sendajo...
Cesar kargajo
+ Vu kapabligesis l\'uzo di limitizita konekto. Omna senduri pauzesis e durigos nur kande vu deskapabligos ta uzo.
Kapabligesis por uzar limitizita konekti.
Voluntez skribar kurta titulo deskriptanta quon vua imajo montras. En la deskripto, explikez pro quo la fotografuro esas interesanta, tipala o rara, ed explikez la kuntexto, videbla o ne. Skriptez tan exakta kam posibla.
+ Voluntez trovar e selektar omna konceptaji quan ca imajo reprezentas. Esez plu preciza kam vu povas. Se ta imajo montras diversa kozi, selektez precize omna ek li. Ne uzez nepreciza deskripturi, se specifika deskripturi existas.
+ Voluntez selektar la kategorii konvenanta. Diferante de deskripturi, kategorii nur existas en Angla linguo.
+ En Commons, vua imaji povos riuzesar ed adaptesar da omni. Ka vu deziras renuncar omna autoroyuri? Ka vu deziras ke l\'imajo atribuesos a vu? Ka vu deziras adapti por uzar la sama licenco?
Montras
Licencizo di \'\'media\'\'
Detali pri \'\'media\'\'
Vidar kategorio-pagino
Vidar pagino dil arkivo
+ Idiomo di vua interfacio
Removar titulo e deskripto
Lektez pluse
En omna idiomi
@@ -658,7 +670,7 @@
Montrar en l\'utensilo \'\'app\'\' di mapo
Aktualigar lokizo
Vidado dil imajo de la selektilo di lokizo
- L\'ombro di la vidado dil imajo de la selektilo di lokizo
+ L\'ombro di la vidado dil imajo de la selektilo di lokizo
Lokizo dil imajo
Verifikez se la lokizo esas korekta
Etiketo
@@ -669,7 +681,11 @@
Facita
Retroirar
Bonveno a personalizita selektilo di imaji
+ Ica selektilo montras quala imaji vu ja sendis a Commons.
+ Diferanta dil imajo adsinistre, l\'imajo addextre havas la logotipö Commons, qua indikas ke ol ja sendesis. \n Kliktez e mantenez por previdado dil imajo.
Ecelanta
+ Ca imajo ja sendesis a Commons.
+ Por teknikala motivi, l\'utensilo \'\'app\'\' ne povas fidinde sendar plua kam %1$d pikturi samatempe. La limito %1$d superesis per %2$d.
Eskartar
Maximo: %1$d
Eroro: Limito pri sendajo transpasita
@@ -686,12 +702,19 @@
Redaktar deskripturi
Redaktar kategorii
Progresiva selektaji (advanced options)
+ Vu povas ajustar la demando \"Vicini\" (\'\'Nearby\'\'). Se erori aparos, riadjustez ed aplikez.
Aplikar
Restaurar
+ Datumi pri lokizo helpas editeri di Wiki trovar vua imajo, do ol divenas plu uzebla.\nL\'imaji quin vu sendis recente ne mencionas lokizo.\nNi sugestas ke vu kapabligez lokizo en l\'ajusti di vua kamero.\nDanko por sendar imaji!
Nula lokizo trovita
Ka vu deziras informar la loko de ube vu obtenis ca imajo?\nInformo pri la lokizo helpos editeri trovar vua imajo, do ol divenos plu utila.\nDanko!
Adjuntez lokizo
+ Voluntez removar de ca e-postala mesajo irga informo quan vu ne deziras divenar publika. Anke konciez ke vua e-postal-adreso quan vu esas uzanta, vua nomo e l\'imajo asociita a vu divenos publike videbla.
Detali
+ Sucesi nur esas disponebla en la definitiva versiono. Voluntez verifikar la dokumentigo pri developo.
+ La klasifiko-tabelo nur esas disponebla en la definitiva versiono. Voluntez verifikar la dokumentigo pri developo.
+ Voluntez sendar nur pikturi facita da vu. Senderi di imaji kun autoroyuro ne libera blokusesos. To aplikesas anke por probi \'\'beta\'\'. Danko por probar l\'utensilo \'\'app\'\'!
+ Voluntez deskapabligar irga informo quan vu ne deziras partigar publike.
nivelo di API
versiono di Android
Fabrikanto dil aparato
@@ -705,6 +728,9 @@
Indikez por ne sendar ol
Itere indikez por sendar ol
Indikanta ke ol ne sendesos
+ Indikita kom por ne sendar
+ Montrar imaji ja traktita
+ Celanta imaji ja traktita
Ne trovesis plusa imaji
Ca imajo ja sendesis
Ne povis selektar ca imajo por sendar (\'\'upload\'\')
@@ -719,11 +745,15 @@
Demandar blokuso di ca uzero
Bonveno a selekto di Modo \"tota-skreno\"
Uzez du fingri por augmentar o diminutar \'\'zoom\'\'.
+ Glitez rapide e longe por facar lo sequanta: \n- Sinistre/Dextre: Irar al antea/nexta\n- Adsupre: Selektar\n- Adinfre: Indikez kom ne por sendar.
+ Por establisar l\'avataro di vua klasifiko-tabelo, kliktez \"Uzar kom avataro\" en la 3-punti menuo de irga imajo.
Koordinati ne esas l\'exakta, tamen l\'individuo qua sendis ca imajo kredas ke la koordinati quin lu informis esas suficante proxima.
+ Permiso pri enmagazinigado neaceptata
Ne povis partigar ca arkivo
+ Uzar la funcionado bezonas permisi
Savez quale skribar utila deskripto
Savez quale skribar utila etiketo
- Videz vua sucesi
+ Vidar vua sucesi
Modifikar imajo
Aktualigar lokizo
Lokizo aktualigita!
@@ -734,6 +764,7 @@
Dankar l\'autoro
Eroro sendanta danki al autoro.
La tempo-quanto por vua \'\'log in\'\' finis. Voluntez itere enirar.
+ Nula utensilo \'\'app\'\' disponebla por apertar arkivi GPX
Konservo sucesoza di arkivo
Ka vu deziras apertar arkivo GPX?
Ka vu deziras apartar l\'arkivo KML?
@@ -745,8 +776,18 @@
- %d imajo selektita
- %d imaji selektita
+ Savez ke omna imaji, kande sendesas kune, recevas la sama kategorii, e deskripturi. Se ta imaji ne havas la sama deskripti e kategorii, voluntez facar diversa sendaji separata.
+ Noto pri multopla sendado di imaji
+ Informez Wikipedio pri problemo en ca temo
+ Voluntez facar kelka komenti
Diskuto
Dicez irgu pri l\'arkivo \'%1$s\'. Ol esos videbla publike.
+ \'%1$s\' ne pluse existas, nula imajo povos rekuperesar de ol.
+ \'%1$s\' esas en diferanta loko.
+ \'%1$s\' esas en diferanta loko. Voluntez mencionar la korekta loko adinfre e, se posibla, skribez la korekta latitudo e longitudo.
+ Altra problemo od informo (voluntez explikar adinfre).
+ Vua retrokomento publikigesis che sequanta pagino wiki: \'\'<a href=\"https://commons.wikimedia.org/wiki/Commons:Mobile_app/Feedback\">Commons:Mobile app/Feedback</a>\'\'
+ Ka vu fakte deziras interuptar omna sendi?
Extinganta la tota sendaji...
Arkivi sendita
Vartanta
@@ -763,6 +804,7 @@
Faliis trovar quale rekuperar dokumentaro kun \'\'bucket ID\'\' %1$d
Ankore ne existas fotografuro pr ta loko, facez fotografuro!
Ja existas imajo pri ta loko.
+ Verifikanta se ta loko ja havas imajo.
Eroro dum kargado
Nula uzo trovesis
Commons
@@ -776,6 +818,9 @@
Deskripto-texto
Deskripto-texto kopiita a \'\'clipboard\'\'
Gratuli! Omna imaji en ca albumo sive sendesis, sive indikesis por ne sendar.
+ Montrez en \'\'Explore\'\'
+ Montrez en Proxima (\'\'Nearby\'\')
Kreesis e sendesis da: %1$s
Kreita da %1$s e sendita da %2$s
+ Indikita por Efaco
diff --git a/app/src/main/res/values-is/strings.xml b/app/src/main/res/values-is/strings.xml
index 427e3cec3..3a5f47668 100644
--- a/app/src/main/res/values-is/strings.xml
+++ b/app/src/main/res/values-is/strings.xml
@@ -147,10 +147,10 @@
Sjálfgefið notkunarleyfi
Nota fyrri titil og lýsingu
Þema
- Attribution-ShareAlike 4.0
- Attribution 4.0
- Attribution-ShareAlike 3.0
- Attribution 3.0
+ Attribution-ShareAlike 4.0
+ Attribution 4.0
+ Attribution-ShareAlike 3.0
+ Attribution 3.0
CC0
CC BY-SA 3.0
CC BY 3.0
@@ -656,6 +656,7 @@
Wiki Loves Monuments er alþjóðleg ljósmyndasamkeppni um myndir af minnismerkjum sem skipulögð er af Wikimedia
Þarfnast heimildar
Kort í nágrenninu þurfa að geta lesið STÖÐU SÍMANS til að virka almennilega
+ Nauðsynlegar heimildir: Lesa núverandi staðsetningu til að geta stungið upp á flokkum
Framlög notanda: %s
Frammistaða notanda: %s
Skoða notandasíðu
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index 662fb3d64..9402a65bf 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -12,6 +12,7 @@
* Dream Indigo
* Emabarto01
* Gianfranco
+* Giorgx12
* Lorelai87
* Lorem Ipsum
* Luca.favorido
@@ -55,7 +56,7 @@
- (%1$d)
- (%1$d)
- Avvio del caricamento
+ Avvio caricamenti
- Elaborando %d caricamento
- Elaborando %d caricamenti
@@ -143,7 +144,7 @@
Cerca categorie
Cerca elementi che il tuo file rappresenta (montagna, Taj Mahal, ecc.)
Salva
- Menu overflow
+ Menu di overflow
Aggiorna
Elenco
(Non è stato ancora caricato niente)
@@ -172,15 +173,16 @@
Non hai ancora caricato alcuna foto.
Riprova
Annulla
+ Ricerche recenti
Caricando questa immagine, dichiaro che questa è una mia opera, che non contiene materiale protetto da copyright o ritratti, e che comunque è conforme alle <a href=\"https://commons.wikimedia.org/wiki/Commons:Policies_and_guidelines\">Policy di Wikimedia Commons</a>.
Scarica
Licenza predefinita
Utilizza titolo e descrizione precedenti
Tema
- Attribuzione-Condividi allo stesso modo 4.0
- Attribuzione 4.0
- Attribuzione-Condividi allo stesso modo 3.0
- Attribuzione 3.0
+ Attribuzione-Condividi allo stesso modo 4.0
+ Attribuzione 4.0
+ Attribuzione-Condividi allo stesso modo 3.0
+ Attribuzione 3.0
CC0
CC BY-SA 3.0
CC BY 3.0
@@ -233,6 +235,7 @@
Descrizione
Discussione
Autore
+ Caricato da
Data di caricamento
Licenza
Coordinate
@@ -283,6 +286,7 @@
Il processo di caricamento richiede un accesso a internet attivo. Per favore verifica la tua connessione alla rete.
Problemi trovati nell\'immagine
Carica solo le foto che hai scattato da solo. Non caricare immagini scaricate da Internet.
+ Caricamenti
Salva scatti in-app
Salva le foto scattate con la fotocamera in-app nella memoria del tuo dispositivo
Accedi alla tua utenza
@@ -354,6 +358,7 @@
Quiz
Questa immagine è OK per essere caricata?
Domanda
+ Non sono sicuro
Risultato
Se continui a caricare immagini che richiedono la cancellazione, la tua utenza sarà probabilmente bannata. Sei sicuro di voler terminare il quiz?
Più di %1$s delle immagini che hai caricato sono state cancellate. Se continui a caricare immagini che richiedono la cancellazione, la tua utenza sarà probabilmente bannata.\n\nTi piacerebbe visualizzare di nuovo il tutorial e poi fare un quiz per aiutarti a capire che tipo di immagini dovresti o non dovresti caricare?
@@ -363,6 +368,7 @@
Uno degli obiettivi di Commons è raccogliere immagini di qualità. Pertanto, le immagini sfocate non dovrebbero essere caricate. Cerca sempre di scattare belle foto con una buona illuminazione.
Le foto che mostrano tecnologia o cultura sono molto apprezzate su Commons.
Hai ottenuto %1$s delle risposte corrette. Congratulazioni!
+ Continua
Seleziona una delle due opzioni per rispondere alla domanda
Sessione scaduta. Accedi nuovamente.
Condividi il tuo quiz con i tuoi amici!
@@ -391,7 +397,7 @@
Immagini in evidenza
Immagini tramite \"Luoghi nelle vicinanze\"
Livello %d
- %s (Livello %s)
+ %s (Livello %s)
Immagini caricate
Immagini non ripristinate
Immagini utilizzate
@@ -456,6 +462,7 @@
Termina il:
Visualizza campagne
Vedi le campagne in corso
+ Mostra pulsante di cancellazione
Consenti all\'app di recuperare la posizione nel caso in cui la fotocamera non la registri. Alcune fotocamere del dispositivo non la registrano. In questi casi, consentire all\'app di recuperare e allegare la posizione rende il tuo contributo più utile. Potrai modificarlo in qualsiasi momento dalle Impostazioni
Consenti
Nascondi
@@ -533,6 +540,7 @@
Carica foto su Wikimedia Commons direttamente dal tuo telefono. Scarica subito l\'app Commons: %1$s
Condividi applicazione tramite...
Informazioni sull\'immagine
+ Non mostrare più questo messaggio
Nessuna categoria trovata
Nessuna definizione trovata
Caricamento annullato
@@ -564,6 +572,10 @@
Aggiorna le categorie
Tentativo di aggiornare le rappresentazioni.
Modifica le raffigurazioni
+
+ - Viene aggiunta la raffigurazione %1$s.
+ - Vengono aggiunte le raffigurazioni %1$s.
+
Non è stato possibile aggiungere descrizioni.
Tentativo di aggiornare le coordinate.
Coordinate aggiornate
@@ -596,6 +608,7 @@
CLASSI FIGLIE
CLASSI SUPERIORI
Sottocategorie
+ CATEGORIE SUPERIORI
Rinvenuto luogo nei pressi
Queste sono immagini di %1$s?
Questa è un\'immagine di %1$s?
@@ -658,6 +671,7 @@
Nelle vicinanze
Usa
Il mio posizionamento
+ © <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a>
Attivata modalità di connessione limitata!
Disattivata modalità di connessione limitata. I caricamenti in attesa riprenderanno adesso.
Modalità di connessione limitata
@@ -707,13 +721,16 @@
Nascondi
Massimo: %1$d
Errore: limite di caricamento superato
+ WLM
Questa immagine sarà ammessa al concorso Wiki Loves Monuments
Mostra monumenti
È il mese di Wiki Loves Monuments!
ULTERIORI INFORMAZIONI
+ Wiki Loves Monuments
Wiki Loves Monuments è un concorso internazionale di fotografia di monumenti organizzato da Wikimedia
Autorizzazione/i richiesta/e
Le mappe vicine hanno bisogno di leggere lo STATO DEL TELEFONO per funzionare correttamente
+ Autorizzazione facoltativa: ottieni la posizione corrente per i suggerimenti di categoria
Contributi dell\'utente: %s
Traguardi dell\'utente: %s
Vedi il profilo utente
@@ -819,12 +836,15 @@
Cartella %1$s cancellata correttamente
Impossibile eliminare la cartella %1$s
Impossibile eliminare i contenuti nella cartella: %1$s
+ Impossibile recuperare il percorso della cartella per l\'ID bucket: %1$d
Questo posto non ha ancora una foto, scattane una!
Questo posto ha già una foto.
Ora controlliamo se questo posto ha una foto.
Errore durante il caricamento
+ Nessun uso trovato
Commons
Altri wiki
+ •
Utilizzi del file
SingleWebViewActivity
Utenza
diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml
index 51abc1041..17dd2fb9e 100644
--- a/app/src/main/res/values-iw/strings.xml
+++ b/app/src/main/res/values-iw/strings.xml
@@ -182,6 +182,9 @@
עדיין לא העלית תמונות.
לנסות שוב
ביטול
+ נא להקליד שם שפה
+ חיפושים אחרונים
+ כל השפות
שליחת התמונה הזאת מהווה את הצהרתי על כך שזאת יצירה שלי, שהיא לא מכילה חומר מוגבל בזכויות יוצרים או תמונות עצמיות (סלפי) ומתאימה בכל אופן ל<a href=\"https://commons.wikimedia.org/wiki/Commons:Policies_and_guidelines\">מדיניות של אתר ויקישיתוף של ויקימדיה</a>.
הורדה
רישיון ברירת מחדל
@@ -243,6 +246,7 @@
תיאור
דיון
יוצר
+ מעלה
תאריך העלאה
רישיון
נקודות ציון
@@ -293,6 +297,7 @@
תהליך ההעלאה דורש גישה פעילה לאינטרנט. נא לבדוק את חיבור הרשת שלך.
נמצאו בעיות בתמונה
נא להעלות רק תמונות שצילמת בעצמך. לא להעלות תמונות שהורדת מהאינטרנט.
+ העלאות
שמירת צילומים מתוך היישומון
שמירת תמונות שצולמו באמצעות מצלמה בתוך היישום לאחסון של המכשיר שלך
כניסה לחשבון שלך
@@ -364,6 +369,7 @@
מבחן
האם בסדר להעלות את התמונה הזאת?
שאלה
+ לא ברור לי
תוצאה
העלאה של תמונות שדורשות מחיקה תגרום להחרמתך. האם באמת לסיים את הבוחן?
יותר מ־%1$s מהתמונות שהעלית נמחקו. אם תמשיך להעלות תמונות שצריך למחוק, חשבונך כנראה ייחסם.\n\nאולי תרצה לראות שוב את המדריך ואז לעשות בוחן כדי לעזור לך ללמוד אילו סוגים של תמונות צריך או לא צריך להעלות?
@@ -373,6 +379,7 @@
אחת מהמטרות של ויקישיתוף היא לאסוף תמונות איכותיות. לפיכך, אין להעלות תמונות מטושטשות. השתדלו תמיד לצלם תמונות יפות עם תאורה טובה.
תמונות שמציגות טכנולוגיה או תרבות מתקבלות בברכה בוויקישיתוף.
ענית נכון על %1$s מהשאלות. ברכות!
+ להמשיך
יש לבחור אפשרות אחת מתוך שתיים כדי לענות על השאלה
זמן הכניסה לחשבון פקע, נא להיכנס שוב.
שתף את הבוחן שלך עם חברים!
@@ -401,7 +408,7 @@
תמונות מומלצות
תמונות דרך \"מקומות בסביבה\"
רמה %d
- %s (רמה %s)
+ %s (רמה %s)
תמונות שהועלו
תמונות שלא שוחזרו
תמונות בשימוש
@@ -466,6 +473,8 @@
מסתיים ב־:
הצגת מסעי פרסום
ר\' את מסעי פרסום שמתרחשים כרגע
+ הצגת כפתור מחיקה
+ הפעלת כפתור „מחיקת תיקייה” בבורר המותאם אישית
נא לאפשר ליישום לאחזר את המיקום אם המצלמה לא מתעדת אותו. חלק ממצלמות המכשירים לא רושמות את המיקום. במקרים כאלה, מתן אפשרות ליישום למשוך את המיקום ולשייך אותו לתמונה משפר את השימושיות של התרומה שלך. אפשר לשנות זאת בכל עת דרך ההגדרות
לאפשר
לסרב
@@ -496,7 +505,7 @@
נשלחת תודה עבור %1$s
האם זה עומד בתנאי זכויות היוצרים?
האם הקטגוריה נכונה?
- האם זה בתחום?
+ האם זה שייך כאן?
ברצונך להודות לתורם?
נא ללחוץ על \"לא\" כדי להציע את התמונה הזאת למחיקה אם היא בכלל לא שימושית.
סמלים, צילומי מסך, כרזות של סרטים, הם לעיתים קרובות הפרות זכויות יוצרים.\nנא ללחוץ \"לא\" כדי להציע את התמונה הזאת למחיקה.
@@ -543,6 +552,7 @@
כדי להעלות תמונות לוויקינתונים של ויקימדיה ישר מהטלפון שלך. אתם מוזמנים להוריד את היישום של ויקינתונים עכשיו: %1$s
שיתוף היישום דרך...
פרטי תמונה
+ לא להציג את ההודעה הזאת שוב
לא נמצאו קטגוריות
לא נמצאו מוצגים
ההעלאה בוטלה
@@ -735,6 +745,8 @@
ויקי אוהבת אתרי מורשת היא תחרות בין־לאומית לצילום אתרי מורשת שמאורגנת על־ידי ויקימדיה
נדרשת הרשאה
מפות בסביבה זקוקות להרשאה מצב הטלפון כדי לפעול כראוי
+ נא להפעיל שירותי מיקום כדי לצפות במקומות קרובים.
+ הרשאה לא מחייבת: קבלת מיקום נוכחי בשביל הצעות קטגוריות
תרומות של המשתמש: %s
הישגי המשתמש: %s
הצגת פרופיל המשתמש
@@ -861,7 +873,7 @@
הכותרת הועתקה ללוח
ברכותינו, כל התמונות באלבום הזה הועלו או שסומנו לא להעלאה.
בתצוגת סיור
- בתצוגת בסביבה
+ לעבור לתצוגת בסביבה
נוצר והועלה על־ידי: %1$s
נוצר על־ידי %1$s והועלה על־ידי %2$s
הועמדה למחיקה
diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml
index f051ba27a..17dc66117 100644
--- a/app/src/main/res/values-ja/strings.xml
+++ b/app/src/main/res/values-ja/strings.xml
@@ -34,7 +34,9 @@
コモンズのロゴ
コモンズのウェブサイト
送信
+ 別の説明を追加
キャプション
+ 言語の説明
キャプション
説明
画像
@@ -87,6 +89,8 @@
ログインに成功しました!
ログインに失敗しました!
ファイルが見つかりません。別のファイルでお試しください。
+ 試行回数の上限に達しました!アップロードをキャンセルしてもう一度お試しください
+ バッテリーの最適化をオフにしますか?
認証に失敗しました。もう一度ログインしてください。
アップロードを開始しました!
アップロードは待機中です(限定接続モードが有効)
@@ -120,6 +124,7 @@
失敗した回数が多すぎます。数分後にもう一度お試しください。
申し訳ありませんが、この利用者はコモンズでブロックされています。
二要素認証コードを入力してください。
+ ログイン認証コードをあなたのメールアドレスに送信しました。ログインするにはコードを提示してください。
ログイン失敗
アップロード
このセットに名前をつけてください
@@ -156,6 +161,8 @@
まだ写真をアップロードしていません。
再試行
キャンセル
+ 最近の検索
+ すべての言語
この画像の投稿に当たり、私はこれが自分自身の作品であり、著作権のあるコンテンツや自撮りは含まれず<a href=\"https://commons.wikimedia.org/wiki/Commons:Policies_and_guidelines\">Wikimedia Commons の方針</a>に従っていると宣言します。
ダウンロード
既定のライセンス
@@ -215,6 +222,7 @@
説明
議論
作者
+ アップロード者
アップロード日時
ライセンス
緯度経度
@@ -222,6 +230,7 @@
ベータ版テスターになる
Google Playのベータ版チャンネルにオプトインして、新機能やバグ修正プログラムに早期にアクセス
2FAコード
+ メール認証コード
ログアウトしてもよろしいですか?
メディアイメージが失敗しました
下位カテゴリは見つかりませんでした
@@ -264,6 +273,7 @@
アップロードの過程にはインタネットアクセスへの接続が必要です。ネットワーク接続をご確認ください。
画像に問題があります
あなた自身の撮影した画像に限定してアップロードしてください。ウェブ上からダウンロードした画像はアップロードできません。
+ アップロード
アプリの画面キャプチャを保存
アプリ内のカメラで撮影した写真を端末のメモリに保存する
自分のアカウントにログイン
@@ -351,6 +361,9 @@
アプリをシェアする
回転
付近の場所を読み込めません
+ この地域に写真がありません
+ 周辺の場所がありません
+ 近くのモニュメントの取得エラー。
最近の検索はまだありません
本当に検索履歴を消去しますか?
このアップロードを本当にキャンセルしますか?
@@ -366,6 +379,7 @@
秀逸な画像
「近くの場所」機能でアップロードした画像
レベル %d
+ %s (レベル %s)
アップロードした画像
却下されなかった画像
使用された画像
@@ -408,7 +422,7 @@
自分のプライバシーを傷つけると気がつきました
以前とは考えが変わりました。今後は公衆の場で自分の画像を公開したくありません
残念ですがこの画像は百科事典の目的に合いません
- 私本人が%1$sにアップロードし、%2$d件の記事で使用されました。
+ 私本人が%1$sにアップロードし、少なくとも%2$d件の記事で使用されました。
コモンズへようこそ!\n\n追加ボタンを押して、ぜひご自分のメディアの初投稿をしましょう。
カテゴリが選択されていません
カテゴリを指定しない画像は使用されることがほとんどありません。ほんとうにカテゴリを選択しないまま作業を続けますか?
@@ -425,11 +439,14 @@
以後、表示しない
位置情報許可を請求
通知カードで近くの場所を表示するたび、位置情報の使用を許可するかどうか尋ねる
- 何か問題が発生し、あなたの貢献を表示できませんでした
+ 何か問題が発生し、貢献を表示できませんでした
たくさんの貢献をしていただいたのですが、件数が多すぎるため貢献度算出システムが処理できません。まさに究極の貢献をされました。
終了日:
キャンペーンを表示
現在、実施中のキャンペーンを表示
+ 削除ボタンを表示
+ 許可
+ 閉じる
キャンペーンの通知は表示されなくなります。ただし「設定」ページでいつでも通知の表示を有効にできます。
この機能はネットワーク接続が必要です。接続設定の確認をお願いします。
画像の処理中にエラーが発生しました。もう一度、お願いします。
@@ -574,6 +591,7 @@
位置設定を開ませんでした。手動で位置を有効にしてください
高品質を確保するには、高精モードを選択してください。
位置を有効にしますか?
+ アプリが現在位置を表示するために位置サービスをオンにしてください
付近が正確に機能するには、位置を有効にしてください
これら2点の撮影場所は同じですか? 右側の画像に緯度経度情報を添付しますか?
さらに読み込む
@@ -600,7 +618,7 @@
ブックマーク
貢献
リーダーボード
- 順位
+ 順位:
件数:
順位
利用者
@@ -636,26 +654,46 @@
メディアのライセンス
メディアの詳細
カテゴリのページを表示
+ 項目ページを表示
アプリのユーザーインターフェース言語
+ キャプションと説明を除去
さらに詳しく
すべての言語
+ 位置を選択
+ パンとズームで調整
+ 位置を選択
地図アプリで表示
位置を編集
- 場所が正しいかどうかを確認する
+ 画像の位置
+ 位置が正しいかどうかを確認する
ラベル
説明
項目
+ カスタムセレクター
画像なし
完了
戻る
+ この画像は既にコモンズにアップロードされています。
+ 技術的な理由により、アプリは一度に%1$d枚以上の画像を確実にアップロードすることができません。%1$d枚のアプロード制限を%2$d枚超過しています。
+ 閉じる
+ 最大: %1$d
+ エラー: アップロード制限を超えました
+ モニュメントを表示
権限が必要です
+ 近くの場所を表示するには、位置サービスをオンにしてください。
+ マップ上に近くの場所を表示するには位置アクセス権限が必要です。
+ 利用者の投稿: %s
+ 利用者の実績: %s
利用者プロフィールを表示
題材を編集する
カテゴリを編集
+ 高度なオプション
適用
リセット
+ 位置が見つかりません
位置を追加
詳細
+ 公開したくない情報はすべて選択解除してください。
APIレベル
アンドロイドのバージョン
デバイスの製造元
@@ -666,11 +704,17 @@
フィードバックを送る途中でエラーが起きました
あなたのフィードバックは何ですか?
あなたのフィードバック
- アップロード不可としてマーク
+ アップロードしないとしてマーク
+ アップロードしないとしてマーク解除
+ アップロードしないとしてマーク中
+ アップロードしないとしてマーク解除中
+ 操作済みの画像を表示
+ 操作済みの画像を非表示
これ以上画像が見つかりません
この画像は既にアップロードされています
この画像をアップロード用に選択できません
画像が選択されました
+ アップロードしないとマークされた画像
報告
白色背景を設定
黒色背景を設定
@@ -679,28 +723,70 @@
このコンテンツを報告する
この利用者のブロックを依頼
全画面選択モードへようこそ
+ 日本の指を使ってズームイン・アウトします。
リーダーボードのアバターを設定するには、任意の画像の 3 つのドット メニューで [アバターとして設定] をタップします。
+ この項目を共有できません
この機能には権限が必要です
+ 有用な説明の書き方を学ぶ
+ 有用なキャプションの書き方を学ぶ
+ 自分の実績を表示
画像の編集
位置を編集
+ 位置を更新しました!
+ 位置を除去
+ 位置の除去警告
+ 位置情報により写真がより有用で見つけやすくなります。本当に写真から位置情報を除去しますか?
+ 位置を除去しました!
作者に感謝する
作者への感謝の送信エラー。
ログインが期限切れになりました。もう一度ログインしてください。
GPXファイルを開くことができるアプリケーションがありません
+ ファイルが正常に保存されました
+ GPXファイルを開きますか?
+ KMLファイルを開きますか?
+ KMLファイルの保存に失敗しました。
+ GPXファイルの保存に失敗しました。
+ KMLファイルを保存中
+ GPXファイルを保存中
- %d件の画像が選択されました
+ 複数アップロードではすべての画像が同一のカテゴリおよび題材となります。画像が題材およびカテゴリを共有していない場合、個別のアップロードを複数実施してください。
+ 複数アップロードに関する注記
+ この項目に関する問題をウィキデータに報告
+ コメントを入力してください
+ トーク
+ その他の問題または情報 (以下に説明してください)。
+ あなたのフィードバックは以下のウィキページに投稿されます: <a href=\"https://commons.wikimedia.org/wiki/Commons:Mobile_app/Feedback\">Commons:Mobile app/Feedback</a>
+ 本当にすべてのアップロードをキャンセルしますか?
すべてのアップロードをキャンセルしています...
アップロード
保留中
失敗しました
+ 場所データを読み込めませんでした
+ フォルダを削除
+ 削除を確認
+ %2$d個の項目を含むフォルダー %1$s を本当に削除しますか?
削除
キャンセル
+ フォルダー %1$s が正常に削除されました
+ フォルダー %1$s の削除に失敗しました
+ フォルダーの内容の削除エラー: %1$s
+ 以下のバケットIDに対するフォルダーパスの取得に失敗: %1$d
+ この場所にはまだ写真がありません、撮りに行きましょう!
+ この場所には既に写真があります。
+ この場所に写真があるか確認中です。
+ 読み込み中のエラー
+ 使用はありません
コモンズ
その他のウィキ
+ ファイルの使用
アカウント
アカウント抹消
アカウント抹消の警告
キャプション
キャプションをクリップボードにコピーしました
+ 作成およびアップロード者: %1$s
+ %1$s が作成し %2$s がアップロード
+ 削除提案
diff --git a/app/src/main/res/values-ji/strings.xml b/app/src/main/res/values-ji/strings.xml
index 0d6cf62a8..6cb05a4ee 100644
--- a/app/src/main/res/values-ji/strings.xml
+++ b/app/src/main/res/values-ji/strings.xml
@@ -80,8 +80,8 @@
אַנולירן
אראָפלאָדן
סטאנדארט־ליצענץ
- Attribution-ShareAlike 3.0
- Attribution 3.0
+ Attribution-ShareAlike 3.0
+ Attribution 3.0
CC0
CC BY-SA 3.0
CC BY 3.0
diff --git a/app/src/main/res/values-jv/strings.xml b/app/src/main/res/values-jv/strings.xml
index e775ba274..7b2bc6ed1 100644
--- a/app/src/main/res/values-jv/strings.xml
+++ b/app/src/main/res/values-jv/strings.xml
@@ -106,10 +106,10 @@
Lisènsi
Anggo sesirah/wedharan sadurungé
Modhe wengi
- Atribusi-DumSaèmper 4.0
- Atribusi 4.0
- Atribusi-DumSaèmper 3.0
- Atribusi 3.0
+ Atribusi-DumSaèmper 4.0
+ Atribusi 4.0
+ Atribusi-DumSaèmper 3.0
+ Atribusi 3.0
CC0
CC BY-SA 3.0
CC BY-SA 3.0
@@ -185,4 +185,5 @@
Golèkan mentas waé:
Pitakon
Banjuraké
+ Palilah manasuka: Njupuk pernah saiki kanggo saran ing kategori
diff --git a/app/src/main/res/values-ka/strings.xml b/app/src/main/res/values-ka/strings.xml
index 542b8895a..e90fce746 100644
--- a/app/src/main/res/values-ka/strings.xml
+++ b/app/src/main/res/values-ka/strings.xml
@@ -155,10 +155,10 @@
ნაგულისხმევი ლიცენზია
წინა სათაურისა და აღწერის გამოყენება
თემა
- Attribution-ShareAlike 4.0
- Attribution 4.0
- Attribution-ShareAlike 3.0
- Attribution 3.0
+ Attribution-ShareAlike 4.0
+ Attribution 4.0
+ Attribution-ShareAlike 3.0
+ Attribution 3.0
CC0
CC BY-SA 3.0
CC BY 3.0
@@ -369,7 +369,7 @@
რჩეული სურათები
სურათები „ახლომდებარე ადგილები“ -დან
დონე %d
- %s (დონე %s)
+ %s (დონე %s)
სურათები ატვირთულია
სურათები არ დაბრუნებულა
სურათები გამოიყენება
@@ -482,7 +482,7 @@
აპლიკაციის მომხმარებლის ინტერფეისის ენა
ადგილმდებარეობის რედაქტირება
ადგილმდებარეობის მაჩვენებლის სურათის ნახვა
- ადგილმდებარეობის მაჩვენებლის სურათის სურათის ჩრდილი
+ ადგილმდებარეობის მაჩვენებლის სურათის სურათის ჩრდილი
სურათის მდებარეობა
შეამოწმეთ, არის თუ არა ადგილმდებარეობა სწორი
სახელი
@@ -495,6 +495,7 @@
შესანიშნავია
უარყოფა
მეტის გაგება
+ დამატებითი ნებართვა: მიმდინარე ადგილმდებარეობის განსაზღვრა კატეგორიის შემოთავაზებისთვის
დეტალები
API დონე
Android-ის ვერსია
diff --git a/app/src/main/res/values-kab/strings.xml b/app/src/main/res/values-kab/strings.xml
index 1370e84c0..d5aed8797 100644
--- a/app/src/main/res/values-kab/strings.xml
+++ b/app/src/main/res/values-kab/strings.xml
@@ -115,10 +115,10 @@
Turagt
Seqdec azwel neɣ aglam yezrin
Askar n yiḍ
- Attribution-ShareAlike 4.0
- Attribution 4.0
- Attribution-ShareAlike 3.0
- Attribution 3.0
+ Attribution-ShareAlike 4.0
+ Attribution 4.0
+ Attribution-ShareAlike 3.0
+ Attribution 3.0
CC0
CC BY-SA 3.0
CC BY-SA 3.0
@@ -201,4 +201,5 @@
Nadi
Nadi
Taggayin
+ Tasiregt tafrayant: Awi adig amiran i yisumar n taggayt
diff --git a/app/src/main/res/values-kge/error.xml b/app/src/main/res/values-kge/error.xml
new file mode 100644
index 000000000..f2f30519a
--- /dev/null
+++ b/app/src/main/res/values-kge/error.xml
@@ -0,0 +1,10 @@
+
+
+
+ Commons cadang ngojut
+ Ay uy. Uwat say mak bonos!
+ Unjuk panday sikam apiya say Niku gawiko, raduna kirimko jak surel. Informasimu nulung sikam nandanina!
+ Tarima kasih!
+
diff --git a/app/src/main/res/values-km/strings.xml b/app/src/main/res/values-km/strings.xml
index 3139b9293..e0629a87d 100644
--- a/app/src/main/res/values-km/strings.xml
+++ b/app/src/main/res/values-km/strings.xml
@@ -108,8 +108,8 @@
បោះបង់
ទាញយក
អាជ្ញាបណ្ណលំនាំដើម
- Attribution-ShareAlike 3.0
- Attribution 3.0
+ Attribution-ShareAlike 3.0
+ Attribution 3.0
CC0
CC BY-SA 3.0
CC BY 3.0
diff --git a/app/src/main/res/values-kn/strings.xml b/app/src/main/res/values-kn/strings.xml
index 5e0e0c602..0ffe96db3 100644
--- a/app/src/main/res/values-kn/strings.xml
+++ b/app/src/main/res/values-kn/strings.xml
@@ -2,6 +2,7 @@
ಕಾಮನ್ಸ್ ಫೇಸ್ಬುಕ್ ಪುಟ
+ ಕಾಮನ್ಸ್ನ ಗಿಟ್ಹಬ್ ಮೂಲ ಕೋಡ್
+ ಕಾಮನ್ಸ್ ಲಾಂಛನ
ಕಾಮನ್ಸ್ ಜಾಲತಾಣ
+ ಸ್ಥಳ ಆಯ್ಕೆಯಿಂದ ನಿರ್ಗಮಿಸಿ
ಸಲ್ಲಿಸಿ
+ ಇನ್ನೊಂದು ವಿವರಣೆಯನ್ನು ಸೇರಿಸಿ
+ ಹೊಸ ಕೊಡುಗೆಗಳನ್ನು ಸೇರಿಸಿ
+ ಕ್ಯಾಮೆರಾದಿಂದ ಕೊಡುಗೆಯನ್ನು ಸೇರಿಸಿ
+ ಫೋಟೋಗಳಿಂದ ಕೊಡುಗೆಯನ್ನು ಸೇರಿಸಿ
+ ಹಿಂದಿನ ಕೊಡುಗೆಗಳ ಗ್ಯಾಲರಿಯಿಂದ ಕೊಡುಗೆಯನ್ನು ಸೇರಿಸಿ
+ ತಲೆಬರಹ
+ ಭಾಷಾ ವಿವರಣೆ
+ ತಲೆಬರಹ
+ ವಿವರಣೆ
+ ಚಿತ್ರ
+ ಎಲ್ಲಾ
+ ಮೇಲಕ್ಕೆ ಟಾಗಲ್ ಮಾಡಿ
ದಿನದ ಚಿತ್ರ
- %1$d ಕಡತ ಅಪ್ಲೋಡ್ ಅಗುತ್ತಿದೆ
@@ -83,10 +99,10 @@
ಪೂರ್ವನಿಯೋಜಿತ ಪರವಾನಗಿ
ಹಿಂದಿನ ಶೀರ್ಷಿಕೆ ಹಾಗೂ ವರ್ಣನೆಯನ್ನು ಬಳಸಿ
ಥೀಮ್
- Attribution-ShareAlike 4.0
- Attribution 4.0
- Attribution-ShareAlike 3.0
- Attribution 3.0
+ Attribution-ShareAlike 4.0
+ Attribution 4.0
+ Attribution-ShareAlike 3.0
+ Attribution 3.0
ದಯವಿಟ್ಟು ಅಪ್ಲೋಡ್ ಮಾಡಬೇಡಿ:
ಸೆಲ್ಫಿ ಅಥವಾ ನಿಮ್ಮ ಗೆಳೆಯರ ಚಿತ್ರಗಳು
ನೀವು ಅಂತರ್ಜಾಲದಿಂದ ಡೌನ್ಲೋಡ್ ಮಾಡಿದ ಚಿತ್ರಗಳು
diff --git a/app/src/main/res/values-ko-rKP/strings.xml b/app/src/main/res/values-ko-rKP/strings.xml
index b58b509c7..8e315c9dc 100644
--- a/app/src/main/res/values-ko-rKP/strings.xml
+++ b/app/src/main/res/values-ko-rKP/strings.xml
@@ -105,10 +105,10 @@
기본 허가권
이전의 제목/설명을 사용하기
야간방식
- 저작자표시-동일조건변경허락 4.0
- 저작자표시 4.0
- 저작자표시-동일조건변경허락 3.0
- 저작자표시 3.0
+ 저작자표시-동일조건변경허락 4.0
+ 저작자표시 4.0
+ 저작자표시-동일조건변경허락 3.0
+ 저작자표시 3.0
CC0
크리에이티브코먼즈 저작자표시-동일조건변경허락 3.0
크리에이티브코먼즈 저작자표시 3.0
@@ -290,4 +290,5 @@
여기를 검색
권한 요청
다시는 묻지 않음
+ 선택적 허가: 분류 추천을 위해 현재 위치를 가져옵니다.
diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml
index 469b66f10..9a767a640 100644
--- a/app/src/main/res/values-ko/strings.xml
+++ b/app/src/main/res/values-ko/strings.xml
@@ -24,7 +24,7 @@
* 아라
-->
- 공용 페이스북 페이지
+ 공용 페이스북 문서
공용 GitHub 소스 코드
공용 로고
공용 웹사이트
@@ -35,11 +35,11 @@
카메라에서 기여 참가
사진에서 기여 참가
이전 기여 갤러리에서 기여 참가
- 캡션
+ 설명
언어 설명
캡션
설명
- 이미지
+ 그림
모두
위로 전환
검색 뷰
@@ -141,7 +141,7 @@
%1$s와(과) 일치하는 분류를 찾을 수 없습니다
%1$s에 대한 위키데이터 검색 결과가 없습니다
%1$s에 자식 클래스가 없습니다
- %1$s에 부모 클래스가 없습니다
+ %1$s에 상위 클래스가 없습니다
위키미디어 공용에서 그림을 더 찾기 쉽게 만들기 위해 분류를 추가합니다.\n분류를 추가하려면 입력을 시작하세요.
분류
설정
@@ -168,10 +168,10 @@
기본 라이선스
이전의 제목과 설명을 사용하기
테마
- 저작자표시-동일조건변경허락 4.0
- 저작자표시 4.0
- 저작자표시-동일조건변경허락 3.0
- 저작자표시 3.0
+ 저작자표시-동일조건변경허락 4.0
+ 저작자표시 4.0
+ 저작자표시-동일조건변경허락 3.0
+ 저작자표시 3.0
CC0
크리에이티브 커먼즈 저작자표시-동일조건변경허락 3.0
크리에이티브 커먼즈 저작자표시 3.0
@@ -222,6 +222,7 @@
설명
토론
저자
+ 올린 사람
올린 날짜
라이선스
좌표
@@ -379,7 +380,7 @@
알찬 그림
\"주변 장소\" 경유 이미지
레벨 %d
- %s (레벨 %s)
+ %s (레벨 %s)
사진 업로드됨
사용된 이미지
친구와 성과를 공유하세요!
@@ -538,6 +539,8 @@
미디어
자식 클래스
상위 클래스
+ 하위 분류
+ 상위 분류
주변 장소 발견
%1$s의 사진이 맞습니까?
%1$s의 사진이 맞습니까?
@@ -582,8 +585,10 @@
아바타로 설정
매년
매주
+ 항상
업로드
근처
+ 사용됨
내 순위
제한된 연결 모드
고품질 사진
@@ -625,6 +630,7 @@
오류: 업로드 제한에 도달했습니다
더 알아보기
권한 필요
+ 선택적 권한: 분류 추천을 위해 현재 위치 정보를 가져옵니다.
사용자의 기여: %s
사용자 달성도: %s
사용자 프로필 보기
@@ -671,7 +677,7 @@
기능에 대한 권한이 필요합니다
유용한 설명을 추가하는 법 알아보기
유용한 캡션을 추가하는 법 알아보기
- 업적 보기
+ 업적 보기
그림 편집
위치 편집
위치가 갱신되었습니다!
diff --git a/app/src/main/res/values-krc/strings.xml b/app/src/main/res/values-krc/strings.xml
index 205865bbb..da4f1d3b2 100644
--- a/app/src/main/res/values-krc/strings.xml
+++ b/app/src/main/res/values-krc/strings.xml
@@ -156,10 +156,10 @@
Тынгылау бла Лицензия
Аллындагъы башлыкъ эмда ачыкълауну хайырлан
Тема
- Attribution-ShareAlike 4.0
- Attribution 4.0
- Attribution-ShareAlike 3.0
- Attribution 3.0
+ Attribution-ShareAlike 4.0
+ Attribution 4.0
+ Attribution-ShareAlike 3.0
+ Attribution 3.0
CC0
CC BY-SA 3.0
CC BY 3.0
@@ -210,6 +210,7 @@
Ачыкълау
Сюзюу
Автор
+ Джюклеген
Джюклениу дата
Лицензия
Координатла
@@ -368,7 +369,7 @@
Сайланнган Суратла
\"Джууукъдагъы Джерле\" юсю бла суратла
Дараджа %d
- %s (Дараджа %s)
+ %s (Дараджа %s)
Суратла Джюклендиле
Суратла Кери Алынмадыла
Суратла Хайырландыла
@@ -576,6 +577,8 @@
МЕДИА
БАЛА КЛАССЛА
АНА КЛАССЛА
+ ТЮБ КАТЕГОРИЯЛА
+ ТАМЫР КАТЕГОРИЯЛА
Джууукъдагъы Джер Табылды
Была %1$s суратламыдыла?
Бу %1$s суратымыды?
@@ -668,7 +671,7 @@
Карта къошакъда кёргюз
Локацияны тюзет
Локация сайлаучуну сурат кёрюнюмю
- \n\n Локация сайлаучуну сурат кёрюнюмюню кёлеккеси
+ \n\n Локация сайлаучуну сурат кёрюнюмюню кёлеккеси
Суратны Локациясы
Локацияны тюз болгъанын тинт
Белги
@@ -751,7 +754,7 @@
Ишлерча болууу ючюн эркинликле керекдиле
Хайырланырча ачыкълау къалай джазаргъа керек болгъанын юренигиз
Хайырланырча тюб джазыуну къалай джазаргъа керек болгъанын юренигиз
- Джетишимлеригизге къарагъыз
+ Джетишимлеригизни кёргюзюгюз
Суратны Тюзет
Локацияны Тюзет
Локация джангыртылды!
diff --git a/app/src/main/res/values-kus/strings.xml b/app/src/main/res/values-kus/strings.xml
index 75447a96b..cc772f1e1 100644
--- a/app/src/main/res/values-kus/strings.xml
+++ b/app/src/main/res/values-kus/strings.xml
@@ -143,10 +143,10 @@
Tʋʋma gbaʋŋ kanɛ an na\'ana
Nɔkim pian\'azug kʋdʋg nɛ pa\'alʋg
Pian\'azug
- Attribution-ShareAlike 4.0
- Attribution 4.0
- Attribution-ShareAlike 3.0
- Attribution 3.0
+ Attribution-ShareAlike 4.0
+ Attribution 4.0
+ Attribution-ShareAlike 3.0
+ Attribution 3.0
Wikimedia Commonsi mɔr footonam bɛdegʋ banɛ bɛ Wikipedia tʋʋma ni la.
Fʋ footo la sʋŋi pa\'an nidib dunia wʋsa ni!
M bɛlimnɛ kpɛn\'ɛsim footo banɛ ka fʋ mɛŋ ki\'a bɛɛ ka fʋ mɛŋ maal:
@@ -556,7 +556,7 @@
Pa\'alim map app ni
Dɛmisim zin\'ig
Footo gɔsʋg kanɛ bɛ zin\'ig nɔkirin
- Footo kanɛ pa\'an zin\'ig nɔkir la siig
+ Footo kanɛ pa\'an zin\'ig nɔkir la siig
Footo zin\'ig
Gɔsim ye zin\'ig la an sʋm bɛ
Zanbin
diff --git a/app/src/main/res/values-ky/strings.xml b/app/src/main/res/values-ky/strings.xml
index 17cb550ae..6cdb2e364 100644
--- a/app/src/main/res/values-ky/strings.xml
+++ b/app/src/main/res/values-ky/strings.xml
@@ -3,14 +3,17 @@
* Askar Nazyrov
* Baydastann
* Bosogo
+* Incall
* Kotormochu
* Maksat
* NR Deblocked
-->
Викиказынанын Facebook баракчасы
+ Викиказына GitHub булак коду
Викиказынанын логотиби
Викиказынанын сайты
+ Жайгашкан жер тандоодон чыгуу
Жөнөтүү
Башка сүрөттөмө кошуу
Жаңы салым кошуу
@@ -23,28 +26,39 @@
Сыпаттама
Сүрөт
Баары
+ Жогоруну которуу
Кызыктуу жерди издөө
Жердин абалы
Күндүн сүрөтү
-
- - 1 файл жүктөлүүдө
- - %1$d файл жүктөлүүдө
+
+ - %1$d файл жүктөлүүдө
+ - %1$d файлдар жүктөлүүдө
-
- - Азырынча жүктөөлөр жок
- - 1 жүктөө
- - %1$d жүктөө
+
+ - (%1$d)
+ - (%1$d)
Жүктөө башталууда
-
- - 1 жүктөө башталды
- - %1$d жүктөө башталды
+
+ - %d файл жүктөлүүдө
+ - %d файлдар жүктөлүүдө
- %d жүктөө
- %d жүктөө
- Бул сүрөт %1$s лицензияланат
+
+ - Бул сүрөт %1$s лицензиясы астында берилет
+ - Бул сүрөттөр %1$s лицензиясы астында берилет
+
+
+ - %1$d файл жүктөлдү
+ - %1$d файлдар жүктөлдү
+
+
+ - Бөлүшүлгөн контент алынып жатат. Сүрөттү иштетүү бир аз убакыт талап кылынышы мүмкүн, сүрөттүн көлөмүнө жана түзмөгүңүзгө жараша
+ - Бөлүшүлгөн контент алынып жатат. Сүрөттөрдү иштетүү бир аз убакыт талап кылынышы мүмкүн, сүрөттөрдүн көлөмүнө жана түзмөгүңүзгө жараша
+
Изилдөө
Көрүнүш
Жалпы
@@ -67,8 +81,12 @@
Ийгиликтүү кирдиңиз!
Системага кирүүдө катачылык бар!
Файл табылган жок. Башка файлды издеп көрүңүз.
+ Максималдуу аракеттердин саны жетти! Жүктөөнү токтотуп, кайра аракет кылыңыз.
+ Батарейка оптимизациясын өчүрүү керекпи?
+ 3дөн ашык сүрөттү жүктөө батарейка оптимизациясы өчүрүлгөндө туруктуу иштейт. Жакшы жүктөө үчүн Викиказына тиркемесинде батарейка оптимизациясын өчүрүңүз. \n\nБатарейка оптимизациясын өчүрүү үчүн мүмкүн болгон кадамдар:\n\nКадам 1: Төмөндөгү «Настройкалар» баскычын таптаңыз.\n\nКадам 2: «Оптимизацияланбаган»дан «Бардык тиркемелер»ге которуңуз.\n\nКадам 3: \"Викиказына\" же \"fr.free.nrw.commons\" издеңиз.\n\nКадам 4: Тиркемени таптап, «Оптимизациялобо» деп тандаңыз.\n\nКадам 5: «Аяктоо» баскычын басыңыз.
Аутентификация ишке ашкан жок. Кайра кириңиз.
Жүктөө башталды!
+ Жүктөө кезекке коюлду (чектелген байланыш режими иштетилди)
%1$s жүктөлдү !
Жүктөлгөн файлды көрүү үчүн басыңыз
Файл жүктөлүүдө: %s
@@ -88,7 +106,9 @@
Жакынкы
Жүктөөлөрүм
Шилтемени көчүрүү
+ Ссылка клипбордго көчүрүлдү
Бөлүшүү
+ Файл барагын көрүү
Коштомо жазуу (талап кылынат)
Бул файлга коштомо жазуу бериңиз
Сыпаттама
@@ -106,58 +126,114 @@
Түрмөктөрдү издөө
Файлдагы элементтерди издөө (тоо, Таж-Махал ж.б.)
Сактоо
+ Кошумча меню
Жаңылоо
Тизме
(Азырынча жүктөөлөр жок)
%1$s түрмөктөрү табылган жок
- Уикиказынада Сиздин сүрөттөрдү жеңил табуу үчүн түрмөктөрдү кошуңуз.\n\nТүрмөктөрдү жазууну баштаңыз.\nБул кадамды аттап өтүү үчүн, бул билдирүүнү (же кийинкини) басыңыз.
+ %1$s менен дал келген Викимаалымат элементтери табылган жок
+ %1$s балалык класстарга ээ эмес
+ %1$s ата-энелик класстарга ээ эмес
+ Сүрөттөрүңүздү Викиказына сайтында оңой табылат кылуу үчүн категорияларды кошуңуз.\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=\"%1$s\">GitHub маселесин</a> түзүңүз, ката тууралуу билдирүүлөр жана сунуштар үчүн.
Купуялык саясаты
+ Ыраазычылык
Колдонмо тууралуу
Шарттуу жооп жөнөтүү (Email)
+ Электрондук почта кардары орнотулбаган
Жакында колдонулган түрмөктөр
Алгачкы мезгилдештирүүнү күтүү…
Сиз бир дагы сүрөт жүктөй элексиз.
Кайра аракет кылуу
Жокко чыгаруу
+ Бул сүрөттү жөнөтүп жатканым менен, мен анын менин жеке эмгегим экенин, анда автордук укук менен корголгон материалдар же селфилер жок экенин жана башкача айтканда <a href=\"https://commons.wikimedia.org/wiki/Commons:Policies_and_guidelines\">Викиказына саясатына</a> ылайык экенин билдирем.
Жүктөп алуу
- Лицензия
+ Стандарттык лицензия
+ Мурунку аталыш жана сүрөттөмөнү колдонуу
Тема
- CC Attribution-ShareAlike 3.0
- CC Attribution 3.0
+ Attribution-ShareAlike 4.0
+ Attribution 4.0
+ Attribution-ShareAlike 3.0
+ Attribution 3.0
CC0
CC BY-SA 3.0
CC BY 3.0
+ Викиказына Википедияда колдонулган сүрөттөрдүн көбүн жайгаштырат.
+ Сиздин сүрөттөрүңүз дүйнө жүзү боюнча адамдарды окутууга жардам берет!
+ Сураныч, толугу менен өзүңүз тарткан же түзгөн сүрөттөрдү жүктөңүз:
+ Табигый объекттер (гүлдөр, жаныбарлар, тоолор)
+ Пайдалы объекттер (велосипеддер, темир жол станциялары)
+ Белгилүү адамдар (шаарыңыздын мэри, жолугушкан Олимпиадалык спортчулар)
+ Сураныч, ЖҮКТӨБӨҢҮЗ:
+ Селфилер же досторуңуздун сүрөттөрү
+ Интернеттен жүктөп алган сүрөттөр
+ Закрытылган тиркемелердин скриншоттору
+ Мисал катары жүктөө:
+ Аталышы: Сидней опера театры
+ Сүрөттөмө: Сидней опера театры булуңдун боюнан караганда
+ Категориялар: Сидней опера театрынын батыштан көрүнүшү, Сидней опера театрынын алыстан көрүнүшү
Жеке сүрөттөрүңүздү жүктөңүз. Уикипедияда макалаларды жандандырууга салымыңызды кошуңуз!
Уикипедиядагы сүрөттөр Уикиказынада жеткиликтүү
Сиздин сүрөттөр дүйнө жүзүндөгү адамдардын билим алышына өбөлгө түзүүдө.
Интернетте жарыяланган автордук укукка ээ сүрөттөрдөн, ошондой эле плакаттардан жана китептердин мукабасынан ж.б. четтеңиз.
Сизге бул түшүнүктүүбү?
Ооба!
+ Кошумча маалымат
Категориялар
Жүктөлүүдө…
Тандалган жок
+ Сүрөттөмө жок
Сыпаттама жок
+ Талкуу жок
+ Белгисиз лицензия
Жаңылоо
+ Сактоо уруксатын сурап жатабыз
+ Талап кылынган уруксат: Сырттан сактоону окуу. Бул уруксаты жок тиркеме галереяңызды көрө албайт.
+ Талап кылынган уруксат: Сырттан сактоого жазуу. Бул уруксаты жок тиркеме камераңызды/галереяңызды колдоно албайт.
+ Жайгашкан жерди аныктоо уруксатын сурап жатабыз
+ Тиркеме ичинде тартылган сүрөттөр үчүн жайгашкан жерди сактоо
+ Эгерде түзмөк камерасы жайгашкан жерди сактамаса, тиркеме ичиндеги сүрөттөр менен жайгашкан жерди жазуу үчүн бул параметрди күйгүзүңүз.
Макул
+ Эскертүү
+ Кайталанган файл аталышы табылды
Жүктөө
Ооба
Жок
Коштомо жазуу
Аталыш
+ Сүрөттөмөлөр
Сыпаттама
Талкуу
Автор
+ Жүктөөчү
Жүктөлгөн датасы
Лицензия
Координаттар
+ Берилген жок
+ Бета-тестирлөөчү болуңуз
+ Google Play\'деги бета-каналыбызга кошулуп, жаңы функцияларга жана каталарды оңдоолорго эрте жеткиликтүүлүк алыңыз
+ 2FA коду
+ Электрондук почта текшерүү коду
+ Чындыгында чыгуу каалайсызбы?
+ Медиа сүрөт жүктөлгөн жок
+ Төмөнкү категориялар табылган жок
+ Ата-энелик категориялар табылган жок
+ Зао тоосу
+ Ламалар
+ Көкүрөктүн көпүрөсү
+ Тюльпан
Википедияга кош келиңиз
+ Кош келиңиз! Автордук укук
+ Сидней опера театры
Жокко чыгаруу
Ачуу
Жабуу
@@ -176,6 +252,25 @@
Файлдын Викиказынадагы барагы
Викимаалыматтын элементи
Википедия макаласы
+ Сүрөттөмөнү мүмкүн болушунча кенен бериңиз: Бул сүрөт кайда тартылган? Эмне көрсөтөт? Контекст кандай? Объекттерди же адамдарды сүрөттөп бериңиз. Жөнөкөй жол менен болжолдоого болбогон маалыматтарды кошуңуз, мисалы, пейзаж болсо күндүн кайсы учурунда тартылганын. Эгер сүрөттөгү нерсе өзгөчө болсо, эмне үчүн өзгөчө экенин түшүндүрүңүз.
+ Сүрөттүн кыскача сүрөттөмөсүн жазыңыз. Биринчи сүрөттөмө сүрөттүн Аталышы катары колдонулат. 255 белгиден ашпасын.
+ Бул сүрөт менен мүмкүн болгон көйгөйлөр:
+ Сүрөт өтө караңгы.
+ Сүрөт булуттуу.
+ Сүрөт мурунтан эле Викиказынада бар
+ Бул сүрөт башка жерде тартылган.
+ Сураныч, жөн гана өзүңүз тарткан сүрөттөрдү жүктөңүз. Башкалардын Facebook баракчаларынан тапкан сүрөттөрдү жүктөбөңүз.
+ Бул сүрөттү дагы деле жүктөп койгуңуз келеби?
+ Байланыш катасы
+ Файлды жүктөө үчүн интернетке туташуу керек. Тармагыңызды текшериңиз.
+ Сүрөттө табылган көйгөйлөр
+ Сураныч, жөн гана өзүңүз тарткан сүрөттөрдү жүктөңүз. Интернеттен жүктөлгөн сүрөттөрдү жүктөбөңүз.
+ Тиркеме ичиндеги сүрөттөрдү сактоо
+ Тиркеме ичиндеги камера менен тартылган сүрөттөрдү түзмөгүңүздүн сактоосуна сактоо
+ Эсеп жазууга кирүү
+ Лог файлын жөнөтүү
+ Тиркеме менен байланышкан көйгөйлөрдү талдоо үчүн лог файлын электрондук почта аркылуу иштеп чыгуучуларга жөнөтүңүз. Эскертүү: логдордо жеке маалымат болушу мүмкүн.
+ URL ачуу үчүн веб-браузер табылган жок
Ката! URL табылган жок
Өчүрүүгө сунуштоо
Бул файлды өчүрүү сунушталган.
@@ -185,16 +280,112 @@
Аккаунтка кирүүнү чындап эле өткөрүп жибергиңиз келеби?
Кийин файлдарды жүктөө үчүн аккаунтка кирген болгонуңуз керек болот.
Бул функцияны колдонуу үчүн аккаунтка кириңиз
+ Викитекстти клипбордго көчүрүү
+ Викитекст клипбордго көчүрүлдү
+ Жакынкы объекттер туура иштебеши мүмкүн, жайгашкан жер жеткиликсиз.
+ Интернет жеткиликсиз. Тек гана кэштелген жерлер көрсөтүлөт.
+ Жайгашкан жерге уруксат берилген жок. Бул функцияны колдонуу үчүн жайгашкан жерди кол менен орнотуңуз.
+ Жакынкы жерлердин тизмесин көрсөтүү үчүн уруксат керек
+ Жакынкы сүрөттөрдүн тизмесин көрсөтүү үчүн уруксат керек
+ Жол көрсөтмөлөрү
Викимаалымат
Википедия
+ Викиказына
+ Баа бериңиз
КБС
+ Колдонуучунун нускамасы
+ Окуу куралын өткөрүп жиберүү
+ Интернет жеткиликсиз
+ Билдирүүлөрдү алуу катасы
+ Сын үчүн сүрөттү алуу катасы. Кайра аракет кылуу үчүн жаңылоону басыңыз.
+ Билдирүүлөр табылган жок
Которуу
Тилдер
+ Кайсы тил үчүн котормолорду жөнөтүүнү каалай турганыңызды тандаңыз
+ Улантуу
Жокко чыгаруу
+ Кайра аракет кылуу
+ Бул жерлер сизге жакын, алар үчүн Википедия макалаларын сүрөттөр менен иллюстрациялоо керек.\n\n«БУУ ЖЕРДИ ИЗДӨӨ» баскычын чыкылдатуу картага бекитип, ошол жердин айланасында жакынкы издөө жүргүзөт.
+ Бул жер үчүн сүрөт керек.
+ Бул жердин сүрөтү мурунтан эле бар.
+ Бул жер эми жок.
+ Сүрөттөр табылган жок!
+ Сүрөттөрдү жүктөөдө ката кетти.
+ Жүктөгөн: %1$s
+ Бөгөттөлдү
+ Сизге Викиказынага түзөтүүгө бөгөт коюлган.
+ Күндүн сүрөтү
+ Издөө
+ Викиказынада издөө
+ Издөө
+ Жакында издегендер:
+ Жакында издеген сурамдар
+ Жакында издеген тил сурамдары
+ Категорияларды жүктөөдө ката кетти.
+ Сүрөттөмөлөрдү жүктөөдө ката кетти.
Медиа
Категориялар
+ Элементтер
+ Тандалма
Уюлдук телефон аркылуу жүктөлгөн
Карта
+ %1$s\'ке Викимаалыматтарда сүрөт кошулду!
+ Тиешелүү Викимаалымат элементинин жаңыртылышы ишке ашкан жок!
+ Обои катары коюу
+ Обои ийгиликтүү коюлду!
+ Тест
+ Бул сүрөттү жүктөөгө болобу?
+ Суроо
+ Жыйынтык
+ Өчүрүү керек болгон сүрөттөрдү жүктөөнү уланта берсеңиз, аккаунтуңуз бөгөттөлүшү мүмкүн. Тестти токтоткуңуз келеби?
+ Сиз жүктөгөн сүрөттөрдүн %1$s%дан көбү өчүрүлгөн. Эгер өчүрүлүшү керек болгон сүрөттөрдү жүктөөнү уланта берсеңиз, аккаунтуңуз бөгөттөлүшү мүмкүн.\nОкутуу сабактарын кайра көрүп, кайсы сүрөттөрдү жүктөш керек, кайсынысы керек эмес экенин үйрөнүү үчүн тесттен өтөсүзбү?
+ Селфилердин энциклопедикалык мааниси аз. Эгер сиз жөнүндө Википедияда макала жок болсо, өзүңүздүн сүрөтүңүздү жүктөөдөн алыс болуңуз.
+ Айрым өлкөлөрдө эстеликтердин жана сырттагы көрүнүштөрдүн сүрөттөрүн жүктөө көбүнчө уруксат берилет. Бирок, сыртта коюлган убактылуу көркөм инсталляциялар көбүнчө автордук укукка ээ болуп, жүктөөгө болбойт.
+ Веб-сайттардын скриншоттору деривативдик (жөндөлгөн) чыгармалар болуп саналат жана сайттагы автордук укуктарга ылайык келет. Муну колдонуу үчүн автордон уруксат алуу керек. Андай уруксат болбосо, алардын ишине негизделген бардык чыгармаңыз мыйзам боюнча уруксатсыз көчүрмө катары эсептелип, оригинал авторго таандык.
+ Виктказынанын максаттарынын бири — сапаттуу сүрөттөрдү чогултуу. Ошондуктан, тайгалак сүрөттөрдү жүктөөгө болбойт. Ар дайым жакшы жарыкта ачык жана сапаттуу сүрөттөрдү тартууга аракет кылыңыз.
+ Технологияны же маданиятты чагылдырган сүрөттөр Викиказынада абдан куттукталат.
+ Сиз %1$s суроого туура жооп бердиңиз. Куттуктайбыз!
+ Суроого жооп берүү үчүн эки варианттын бирин тандаңыз.
+ Кирүү мөөнөтү бүттү. Кайра кириңиз, сураныч.
+ Тестиңизди досторуңуз менен бөлүшүңүз!
+ Улантуу
+ Туура жооп
+ Туура эмес жооп
+ Бул скриншотту жүктөөгө болобу?
+ Колдонмону бөлүшүү
+ Айлантуу
+ Жакын жерлерди жүктөө мүмкүн болгон жок.
+ Бул аймакта сүрөттөр жок.
+ Жакын жерде эч кандай жайлар жок.
+ Жакынкы эстеликтерди алуудө ката кетти.
+ Жакында издөө болгон жок.
+ Издөө тарыхыңызды тазалоону каалайсызбы?
+ Бул жүктөөнү токтоткуңуз келетпи?
+ Бул издөөнү өчүргүңүз келеби?
+ Издөө тарыхы өчүрүлдү
+ Өчүрүү үчүн сунуштоо
+ Өчүрүү
+ Жетишкендиктер
+ Профиль
+ Бээжелер
+ Статистика
+ Рахматтар кабыл алынды
+ Тандалган сүрөттөр
+ Сүрөттөр «Жакынкы жайлар» аркылуу
+ Деңгээл %d
+ %s (Деңгээл %s)
+ Жүктөлгөн сүрөттөр
+ Сүрөттөр кайра алынган жок
+ Колдонулган сүрөттөр
+ Жетишкендиктериңизди досторуңуз менен бөлүшүңүз!
+ Сиз ушул талаптарды аткарган сайын деңгээлиңиз көтөрүлөт. «Статистика» бөлүмүндөгү элементтер деңгээлиңизге эсептелбейт.
+ минималдуу талап:
+ Викиказынага ар кандай жүктөө программалары аркылуу жүктөгөн сүрөттөрдүн саны
+ Викиказынага жүктөгөн сүрөттөрүңүздүн өчүрүлбөгөн пайызы
+ Викиказынага жүктөгөн жана Викимедиа макалаларында колдонулган сүрөттөрдүн саны
+ Ката кетти!
+ Викиказына билдирүүсү
+ Ыңгайлаштырылган автордун атын колдонуу
Жүктөөнү жокко чыгаруу
Артка баскычын колдонуу менен бул жүктөө жокко чыгарылат жана сиз ийгиликти жоготосуз
Жүктөөнү улантуу
diff --git a/app/src/main/res/values-lb/strings.xml b/app/src/main/res/values-lb/strings.xml
index 1cd99e223..1dbb074c3 100644
--- a/app/src/main/res/values-lb/strings.xml
+++ b/app/src/main/res/values-lb/strings.xml
@@ -131,14 +131,16 @@
Dir hutt nach keng Fotoen eropgelueden.
Nach eng Kéier probéieren
Ofbriechen
+ Sproochnumm antippen
+ All Sproochen
Eroflueden
Standardlizenz
Viregen Titel a Beschreiwung benotzen
Faarfscheema
Attribution-ShareAlike 4.0
Attribution 4.0
- CC Attribution-ShareAlike 3.0
- CC Attribution 3.0
+ Attribution-ShareAlike 3.0
+ Attribution 3.0
CC0
CC BY-SA 3.0
CC BY 3.0
@@ -179,7 +181,7 @@
Warnung
Eroplueden
Jo
- Neen
+ Nee
Beschrëftung
Titel
Motiven
@@ -307,7 +309,7 @@
Mercie kritt
Bemierkenswäert Biller
Niveau %d
- %s (Niveau %s)
+ %s (Niveau %s)
Eropgeluede Biller
Biller net zréckgesat
Benotzte Biller
@@ -340,6 +342,7 @@
Eropluede weiderféieren
Dëst ni méi froen
Dir hutt esou vill Kontributioune gemaach datt eise Berechnungssystem iwwerfuerdert ass. Dëst ass déi bescht Leeschtung.
+ Läschknäppche weisen
Erlaben
Verwerfen
Fäerdeg
@@ -348,8 +351,8 @@
Merci fir %1$s schécken
Entsprécht dat de Copyright-Reegelen?
Ass dëst richteg kategoriséiert?
- Klickt op NEEN fir dëst Bild fir d\'Läsche virzeschloen, falls et guer net nëtzlech ass.
- Logoen, Screenshotten oder Filmplakate sinn oft Urheberrechtsverletzungen. Klickt op NEEN fir virzeschloen, datt dëst Bild geläscht gëtt
+ Klickt op NEE fir virzeschloen, datt dëst Bild geläscht gi soll, falls et guer net nëtzlech ass.
+ Logoen, Screenshotten oder Filmplakate sinn oft Urheberrechtsverletzungen.\nKlickt op NEE fir virzeschloen, datt dëst Bild geläscht gi soll
Oh, dat ass mol net kategoriséiert!
Et ass eng Urheberrechtsverletzung, well et
Nächst Bild
@@ -483,7 +486,7 @@
Elementer
Keng Biller
Fäerdeg
- Zréck
+ Zeréck
Genial
Dëst Bild gouf schonn op Commons eropgelueden.
Dëst Bild wäert beim Concours Wiki Loves Monuments agereecht ginn.
@@ -492,6 +495,7 @@
Wiki Loves Monuments
Wiki Loves Monuments ass een internationale Fotosconcours fir Monumenter, dee vu Wikimedia organiséiert gëtt
Autorisatioun gëtt gebraucht
+ Fakultativ Autorisatioun: Déi aktuell Plaz kréie fir Propose fir Kategorien
Kontributioune vum Benotzer: %s
Leeschtunge vum Benotzer: %s
Benotzerprofil weisen
diff --git a/app/src/main/res/values-li/strings.xml b/app/src/main/res/values-li/strings.xml
index f477ed8f0..70b392aee 100644
--- a/app/src/main/res/values-li/strings.xml
+++ b/app/src/main/res/values-li/strings.xml
@@ -97,10 +97,10 @@
Standerdlicentie
Gebroek veurige naam/besjrieving
Nachmodus
- Naamsvermeljing-GeliekDeile 4.0
- Naamsvermeljing 4.0
- Naamsvermeljing-GeliekDeile 3.0
- Naamsvermeljing 3.0
+ Naamsvermeljing-GeliekDeile 4.0
+ Naamsvermeljing 4.0
+ Naamsvermeljing-GeliekDeile 3.0
+ Naamsvermeljing 3.0
CC0
CC BY-SA 3.0
CC BY 3.0
diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml
index e148a0a20..0d52af2f9 100644
--- a/app/src/main/res/values-lt/strings.xml
+++ b/app/src/main/res/values-lt/strings.xml
@@ -40,7 +40,7 @@
- \@string/contributions_subtitle_zero
- 1 įkėlimas
- Įkėlimai pradedami
+ Įkėlimai pradedami
- Pradedamas %1$d įkėlimas
- Pradedami %1$d įkėlimai
@@ -146,10 +146,10 @@
Numatytoji Licencija
Naudoti ankstesnį pavadinimą ir aprašymą
Tema
- Attribution-ShareAlike 4.0
- Attribution 4.0
- Attribution-ShareAlike 3.0
- Attribution 3.0
+ Attribution-ShareAlike 4.0
+ Attribution 4.0
+ Attribution-ShareAlike 3.0
+ Attribution 3.0
CC BY-SA 4.0
CC BY 4.0
Vikimedija Commons talpina daugumą paveikslėlių, kurie yra naudojami Vikipedijoje.
@@ -353,7 +353,7 @@
Rinktiniai paveikslėliai
Vaizdai per „Netoliese esančios vietos“
Lygis %d
- %s (%s lygis)
+ %s (%s lygis)
Vaizdai įkelti
Paveikslėliai negrąžinti
Naudoti vaizdai
@@ -641,7 +641,7 @@
Rodyti žemėlapio programėlėje
Redaguoti vietą
Vietos rinkiklio vaizdo rodinys
- \n Vietos parinkiklio vaizdo rodinio šešėlis
+ \n Vietos parinkiklio vaizdo rodinio šešėlis
Vaizdo vieta
Patikrinkite, ar vieta yra teisinga
Etiketė
@@ -667,6 +667,7 @@
Viki myli paminklus yra tarptautinis nuotraukų konkursas, skirtas paminklams, kurį organizuoja Vikiteka
Reikia leidimo
Netoliese žemėlapiai turi perskaityti TELENFONO BŪSENĄ, kad tinkamai funkcionuotų
+ Neprivaloma teisė: Gauti dabartinę vietovę, kad būtų pasiūlomos kategorijos
Naudotojo indėlis: %s
Naudotojo pasiekimai: %s
Žiūrėti naudotojo puslapį
@@ -721,7 +722,7 @@
Funkcionalumui reikalingi leidimai
Sužinokite, kaip parašyti naudingą aprašymą
Sužinokite, kaip parašyti naudingą antraštę
- Pamatykite savo pasiekimus
+ Pamatykite savo pasiekimus
Redaguoti paveikslėlį
Redaguoti vietą
Vieta atnaujinta!
diff --git a/app/src/main/res/values-min/error.xml b/app/src/main/res/values-min/error.xml
new file mode 100644
index 000000000..11f57f87d
--- /dev/null
+++ b/app/src/main/res/values-min/error.xml
@@ -0,0 +1,10 @@
+
+
+
+ Commons bamasalah
+ Ups. Ado nan salah!
+ Caritokan apo nan sanak karajoan, sudah tu bagikan lewat email ka kami. Akan kami bantu mamelokannyo!
+ Tarimo kasih!
+
diff --git a/app/src/main/res/values-mk/strings.xml b/app/src/main/res/values-mk/strings.xml
index 3d1300106..f8f503d47 100644
--- a/app/src/main/res/values-mk/strings.xml
+++ b/app/src/main/res/values-mk/strings.xml
@@ -34,7 +34,7 @@
- (%1$d)
- (%1$d)
- Почеток на подигањата
+ Подигањето почнува
- Обработувам %1$d подигање
- Обработувам %1$d подигања
@@ -151,14 +151,17 @@
Сè уште немате подигнато ниедна слика.
Пробај пак
Откажи
+ Внесете име на јазик
+ Скорешни пребарувања
+ Сите јазици
Поднесувајќи ја сликава, изјавувам дека истата е мое сопствено дело, дека не содржи никаков материјал заштитен со авторски права, не содржи самослици, и дека на секој друг начин е во склад со <a href=\"https://commons.wikimedia.org/wiki/Commons:Policies_and_guidelines/mk?uselang=mk\">правилата на Ризницата</a>.
Преземи
Стандардна лиценца
Користи претходен наслов и опис
Изглед
- Наведи извор-Сподели под исти услови 4.0
+ Наведи извор-Сподели под исти услови 4.0
Наведи извор 4.0
- Наведи извор-Сподели под исти услови 3.0
+ Наведи извор-Сподели под исти услови 3.0
Наведи извор 3.0
CC0
CC BY-SA 3.0
@@ -212,6 +215,7 @@
Опис
Разговор
Автор
+ Подигач
Датум на подигање
Лиценца
Координати
@@ -262,6 +266,7 @@
Постапката за подигање бара да сте поврзани\n со семрежјето. Проверете си ја врската.
Пронајдени проблеми во сликата
Подигајте само слики кои самите сте ги направиле. Не подигајте слики што сте ги нашле некаде на семрежјето.
+ Подигања
Зачувај слики направени во прилогот
Зачувување на сликите направени со камерата во прилогот на вашиот уред
Најавете се со вашата сметка
@@ -333,6 +338,7 @@
Квиз
Дали е во ред да се подигне сликава?
Прашање
+ Не знам
Исход
Ако продолжите со подигање на слики што треба да се бришат, веројатно е дека ќе добиете забрана на сметката. Сигурно сакате да го завршите квизот?
Преку %1$s од сликите што ги подигнавте беа избришани. Ако продолжите со подигање на слики што треба да се бришат, веројатно е дека ќе добиете забрана на сметката.\n\nДали би сакале повторно да го погледате упатството, па да си го проверите знаењето во квиз за тоа какви слики се дозволени за подигање?
@@ -342,6 +348,7 @@
Една од целите на Ризницата е да биде собиралиште за квалитетни слики. Затоа, не треба да се подигаат матни фотографии. Секогаш настојувајте да правите уредни слики со добро осветлување.
На Ризницата се добредојдени слики на кои се прикажува технологија или култура.
Дадовте %1$s точни одговори. Честитаме!
+ Продолжи
Изберете еден одговор
Најавната седница истече. Најавете се повторно.
Споделете го квизов со пријателите!
@@ -370,7 +377,7 @@
Избрани слики
Слики преку „Околни места“
Степен %d
- %s (Степен %s)
+ %s (Степен %s)
Подигнати слики
Неоткажани слики
Употребени слики
@@ -435,6 +442,8 @@
Завршува:
Прикажи походи
Погледајте ги тековните походи
+ Прикажувај го копчето за бришење
+ Овозможете го копчето „Избриши папка“ во прилагодливиот избирач
Дозволете му на прилогот да презема местоположба во случај камерата да не го заведува овој податок. Камерите на некои уреди не заведуваат местоположба. Во такви случаи, овозможете му на прилогот да најде и запише местоположба за вашиот придонес да биде покорисен. Ова можете да го смените во секое време во Нагодувањата
Дозволи
Тргни
@@ -512,6 +521,7 @@
Подигајте слики непосредно на Ризницата од телефон. Преземете го прилогот на Ризницата сега: %1$s
Сподели преку...
Инфо за сликата
+ Повеќе не ја покажувај поракава
Не пронајдов ниедна категорија
Не пронајдов ниедно прикажување
Откажено подигање
@@ -700,6 +710,8 @@
„Вики ги сака спомениците“ е меѓународен фотографски натпревар за споменици приреден од Викимедија
Се бара дозвола
Картите со блиски места треба можат да ја прочитаат СОСТОЈБАТА НА ТЕЛЕФОНОТ за да работат правилно
+ Вклучете ја вашата местоположба за да ги гледате околните места.
+ Дозвола по желба: Утврдување на тековната местоположба за предлагање категории
Придонеси на корисникот: %s
Достигнувања на корисникот: %s
Погл. кориснички профил
@@ -757,7 +769,7 @@
Се бараат дозволи за функцијата
Дознајте како да напишете корисен опис
Дознајте како да напишете корисно толкување
- Видете ги вашите достигнувања
+ Погледајте ги вашите достигнувања
Уреди слика
Уреди местоположба
Местоположбата е подновена!
diff --git a/app/src/main/res/values-ml/strings.xml b/app/src/main/res/values-ml/strings.xml
index 775a64bcc..2dd0eb04d 100644
--- a/app/src/main/res/values-ml/strings.xml
+++ b/app/src/main/res/values-ml/strings.xml
@@ -134,10 +134,10 @@
ഡൗൺലോഡ്
സ്വതേയുള്ള ഉപയോഗാനുമതി
വിഷയം
- ആട്രിബ്യൂഷൻ-ഷെയർഎലൈക് 4.0
- ആട്രിബ്യൂഷൻ 4.0
- ആട്രിബ്യൂഷൻ-ഷെയർഎലൈക് 3.0
- ആട്രിബ്യൂഷൻ 3.0
+ ആട്രിബ്യൂഷൻ-ഷെയർഎലൈക് 4.0
+ ആട്രിബ്യൂഷൻ 4.0
+ ആട്രിബ്യൂഷൻ-ഷെയർഎലൈക് 3.0
+ ആട്രിബ്യൂഷൻ 3.0
സി.സി.0
സി.സി. ബൈ-എസ്.എ. 3.0
സി.സി. ബൈ 3.0
diff --git a/app/src/main/res/values-mni/strings.xml b/app/src/main/res/values-mni/strings.xml
index 7d48bab32..1f2a72372 100644
--- a/app/src/main/res/values-mni/strings.xml
+++ b/app/src/main/res/values-mni/strings.xml
@@ -48,10 +48,10 @@
ꯑꯌꯥꯕꯥ ꯱ ꯍꯦꯛꯇ
ꯁꯤꯖꯤꯟꯅꯧ ꯃꯃꯥꯡꯒꯤ ꯃꯃꯤꯡ ꯱ꯁꯨꯡ ꯑꯀꯨꯞꯄ ꯋꯥꯔꯣꯜ
ꯃꯑꯣꯡ
- ꯑꯇ꯭ꯔꯤꯕꯨꯁꯟ-ꯁꯌꯥꯔꯑꯂꯥꯏꯛ ꯴.꯰
- ꯑꯇ꯭ꯔꯤꯕꯨꯁꯟ ꯴.꯰
- ꯑꯇ꯭ꯔꯤꯕꯨꯁꯟ-ꯁꯌꯥꯔꯑꯂꯥꯏꯛ ꯳.꯰
- ꯑꯇ꯭ꯔꯤꯕꯨꯁꯟ ꯳.꯰
+ ꯑꯇ꯭ꯔꯤꯕꯨꯁꯟ-ꯁꯌꯥꯔꯑꯂꯥꯏꯛ ꯴.꯰
+ ꯑꯇ꯭ꯔꯤꯕꯨꯁꯟ ꯴.꯰
+ ꯑꯇ꯭ꯔꯤꯕꯨꯁꯟ-ꯁꯌꯥꯔꯑꯂꯥꯏꯛ ꯳.꯰
+ ꯑꯇ꯭ꯔꯤꯕꯨꯁꯟ ꯳.꯰
ꯆꯥꯟꯕꯤꯗꯨꯅꯥ ꯊꯥꯒꯠꯀꯅꯨ:
ꯏꯁꯥ ꯏꯇꯣꯝ ꯃꯥꯏꯑꯣꯟꯁꯤꯟꯗꯨꯅꯥ ꯀꯥꯞꯆꯕ ꯅꯠꯇ꯭ꯔꯒ ꯅꯃꯥꯝꯅꯕꯒꯤ ꯃꯤꯔꯦꯜꯁꯤꯡ
ꯊꯥꯒꯠꯄꯒꯤ ꯈꯨꯗꯝ:
diff --git a/app/src/main/res/values-mnw/strings.xml b/app/src/main/res/values-mnw/strings.xml
index a6c18bca3..be0d342b5 100644
--- a/app/src/main/res/values-mnw/strings.xml
+++ b/app/src/main/res/values-mnw/strings.xml
@@ -117,10 +117,10 @@
လာင်ဇြေန် နူတမ်
စုတ် က္ဍိုပ်လိက် ကေုာံ ဗမံက်ထ္ၜး တၞဟ်ခြာညိ
ပရူပရာ
- Attribution-ShareAlike 4.0
- Attribution 4.0
- Attribution-ShareAlike 3.0
- Attribution 3.0
+ Attribution-ShareAlike 4.0
+ Attribution 4.0
+ Attribution-ShareAlike 3.0
+ Attribution 3.0
CC0
CC BY-SA 3.0
CC BY 3.0
@@ -226,7 +226,7 @@
ဗဒင်ဏအ် ဟွံကၠောန်ကမၠောန် ဗွဲဓမ္မတာ၊ ဒၞာဲဒတန် ဟွံသၟဟ်အစောမ်။
အာတ်မိက်ဒၟံင် အခေါင် သွက်ဂွံထ္ၜး စရင်ဒၞာဲဒတန် ဗဒင်ဗဒင်
စမၞောန်ဂမၠိုင်
- ဝဳကဳဒါတာ
+ ဝဳကဳတင်ဂၞင်
ဝဳကဳပဳဒဳယာ
ခမ်မောန်
ကဵုင္ၚုဟ် ကုပိုယ်
@@ -275,4 +275,5 @@
တုဲဒှ်
ကလေင်
သေသာတ်
+ အခေါင်မပိုင်ပြဳ: ကေတ် ဒတန်ဒၞာဲ မနွံဒၟံင်လၟုဟ် သွက်ဂွံစုတ် ပ္ဍဲကဏ္ဍ
diff --git a/app/src/main/res/values-mr/strings.xml b/app/src/main/res/values-mr/strings.xml
index 546b43f4f..0d67ab9b5 100644
--- a/app/src/main/res/values-mr/strings.xml
+++ b/app/src/main/res/values-mr/strings.xml
@@ -103,10 +103,10 @@
डिफॉल्ट परवाना
मागील शीर्षक/वर्णन वापरा
रात्रीच्या वेळेच्या व्यवस्था
- Attribution-ShareAlike 3.0
- Attribution 3.0
- Attribution-ShareAlike 3.0
- Attribution 3.0
+ Attribution-ShareAlike 3.0
+ Attribution 3.0
+ Attribution-ShareAlike 3.0
+ Attribution 3.0
CC0
CC BY-SA 3.0
CC BY 3.0
@@ -190,4 +190,5 @@
सतत विचारले जाणारे प्रश्न
ट्यूटोरियल वगळा
+ ऐच्छिक परवानगी:वर्ग सुचवण्यांसाठी सध्याचे स्थान मिळवा
diff --git a/app/src/main/res/values-my/strings.xml b/app/src/main/res/values-my/strings.xml
index ea7ab06f9..8421c66ba 100644
--- a/app/src/main/res/values-my/strings.xml
+++ b/app/src/main/res/values-my/strings.xml
@@ -109,10 +109,10 @@
နဂို လိုင်စင်
ယခင် ခေါင်းစဉ်နှင့်ဖော်ပြချက် သုံးမည်
ညကြည့်ပုံစံ
- Attribution-ShareAlike 4.0
- Attribution 4.0
- Attribution-ShareAlike 3.0
- Attribution 3.0
+ Attribution-ShareAlike 4.0
+ Attribution 4.0
+ Attribution-ShareAlike 3.0
+ Attribution 3.0
CC0
CC BY-SA 3.0
CC BY 3.0
diff --git a/app/src/main/res/values-nb/strings.xml b/app/src/main/res/values-nb/strings.xml
index 81fb7030b..7bf311b42 100644
--- a/app/src/main/res/values-nb/strings.xml
+++ b/app/src/main/res/values-nb/strings.xml
@@ -159,10 +159,10 @@
Standardlisens
Bruk forrige tittel og beskrivelse
Tema
- Navngivelse-Del på samme vilkår 4.0
- Navngivelse 4.0
- Navngivelse-Del på samme vilkår 3.0
- Navngivelse 3.0
+ Navngivelse-Del på samme vilkår 4.0
+ Navngivelse 4.0
+ Navngivelse-Del på samme vilkår 3.0
+ Navngivelse 3.0
CC0
CC BY-SA 3.0
CC BY 3.0
@@ -618,6 +618,7 @@
Det er Wiki Loves Monuments-måneden!
LÆR MER
Trenger tillatelse
+ Valgfri tillatelse: Hent nåværende posisjon for kategoriforslag
Bidrag til bruker: %s
Prestasjoner til bruker: %s
Vis brukerside
diff --git a/app/src/main/res/values-ne/strings.xml b/app/src/main/res/values-ne/strings.xml
index ef94ba081..6883f7ea1 100644
--- a/app/src/main/res/values-ne/strings.xml
+++ b/app/src/main/res/values-ne/strings.xml
@@ -15,12 +15,47 @@
कमन्सकाे Github स्रोत कोड
कमन्सकाे लाेगाे
कमन्सकाे वेबसाइट
+ स्थान चयनकर्ताबाट बाहिर निस्कनुहोस्
बुझाउनुहोस्
+ अर्को विवरण थप्नुहोस्
+ नयाँ योगदान थप्नुहोस्
+ क्यामराबाट योगदान थप्नुहोस्
+ फोटोहरूबाट योगदान थप्नुहोस्
+ अघिल्लो योगदान ग्यालरीबाट योगदान थप्नुहोस्
+ शीर्षकहरू
+ भाषा विवरण
+ शीर्षक
विवरण
चित्र
सबै
+ दृश्य खोज्नुहोस्
+ स्थानको अवस्था
दिनकाे उत्कृष्ट तस्वीर
- यी तस्वीरहरू %1$s अनुमतिपत्र अनुसार प्राप्त हुनेछ
+
+ - %1$d फाइल अपलोड हुँदैछ
+ - %1$d फाइलहरू अपलोड हुँदैछ
+
+
+ - (%1$d)
+ - (%1$d)
+
+ अपलोडहरू सुरु गर्दै
+
+ - %d अपलोड प्रशोधन गर्दै
+ - %d अपलोडहरू प्रशोधन गर्दै
+
+
+ - %d अपलोड
+ - %d अपलोडहरू
+
+
+ - यो तस्वीर %1$s अन्तर्गत इजाजतपत्र प्राप्त हुनेछ
+ - यी तस्वीरहरू %1$s अन्तर्गत इजाजतपत्र प्राप्त हुनेछन्।
+
+
+ - %1$d अपलोड
+ - %1$d अपलोडहरू
+
अन्वेषण
स्वरूप
सामान्य
@@ -29,6 +64,7 @@
कमन्स
मेरो अभिरुचिहरू
कमन्समा उर्ध्वभरण गर्नुहाेस्
+ अर्ध्वभरण कार्य हुँदैछ
प्रयोगकर्ता नाम
पासवर्ड
तपाईको कमन्स बिटा खातामा प्रवेश गर्नुहोस्
@@ -37,18 +73,21 @@
खाता खाेल्नुहाेस्
प्रवेश गर्दै
कृपया प्रतीक्षा गर्नुहोस् …
+ शीर्षक र विवरणहरू अद्यावधिक गर्दै
कृपया प्रतीक्षा गर्नुहोस् …
- प्रवेश सफल!
- प्रवेश सफल हुन सकेन!
+ प्रवेश सफल!
+ प्रवेश सफल हुन सकेन!
चित्र भेटिएन। कृपया अर्को चित्र प्रयास गर्नुहोस्।
- प्रमाणिकरण असफल भयो, कृपया पुनः प्रवेश गर्नुहोस्!
+ पुन: प्रयास गर्ने अधिकतम सीमा पुग्यो! कृपया अपलोड रद्द गर्नुहोस् र फेरि प्रयास गर्नुहोस्।
+ ब्याट्री अप्टिमाइजेसन बन्द गर्ने?
+ प्रमाणिकरण असफल भयो। कृपया पुनः प्रवेश गर्नुहोस्!
उर्ध्वभरण सुरू भयो!
%1$s उर्ध्वभरण गरियो !
तपाईको उर्ध्वभरण हेर्नको लागि ट्याप गर्नुहोस्
- %1$s उर्ध्वभरण सुरू गर्दैै
+ %s: उर्ध्वभरण सुरू गर्दैै
%1$s उर्ध्वभरण गरिँदै
%1$s उर्ध्वभरण सकाउँदै
- %1$s उर्ध्वभरण असफल भयो
+ %1$s उर्ध्वभरण असफल भयो
हेर्नको लागि ट्याप गर्नुहोस्
हेर्नको लागि ट्याप गर्नुहोस्
मेरा वर्तमानका उर्ध्वभरणहरू
@@ -62,13 +101,13 @@
मेरा उर्ध्वभरणहरू
बाड्ने
चित्र पृष्ठ हेर्नुहोस्
- शीर्षक (आवश्यक)
+ शीर्षक (आवश्यक)
वर्णन
- प्रवेश गर्न असमर्थ - जडान खराबी
+ प्रवेश गर्न असमर्थ - जडान खराबी
धेरै असफल प्रयासहरू भए। कृपया केही मिनेट पछि पुन: प्रयास गर्नुहोस
माफ गर्नुहोस, यो प्रयोगकर्तालाई कमन्समा प्रतिबन्ध गरिएको छ
तपाईंले आफ्नो दुई कारक प्रमाणीकरण अङ्क प्रदान गर्नुपर्नेछ।
- प्रवेश सफल हुन सकेन
+ प्रवेश सफल हुन सकेन
उर्ध्वभरण गर्ने
यो सेटलाई नाम दिनुहोस्
परिवर्तनहरू
@@ -87,7 +126,7 @@
श्रेणी
बारेमा
विकिमिडिया कमन्स अनुप्रयाेग एक स्वतन्त्र स्रोत अनुप्रयाेग हो। यो अनुप्रयाेग विकिमिडिया समुदायका अनुदानप्राप्तकर्ताहरू र स्वयंसेवकहरूद्वारा निर्मित एवम् प्रबन्धित छ। विकिमिडिया फाउण्डेसन यस अनुप्रयाेगकाे निर्माण, विकास र प्रबन्धनमा कुनै पनि प्रकारले संलग्न छैन।
- <a href=\"https://github.com/commons-app/apps-android-commons\">गिटहब</a> मा स्रोत। <a href=\" https://github.com/commons-app/apps-android-commons/issues\">बगजिल्ला</a> मा बग छ।
+ बग प्रतिवेदन र सुझावहरूको लागि नयाँ <a href=\"<span class=\"notranslate\" translate=\"no\">%1$s \">गिटहब समस्या</a> सिर्जना गर्नुहोस्।
गोपनीयता नीति
श्रेयहरू
बारेमा
@@ -102,10 +141,10 @@
पूर्वनिर्धारित अनुमति पत्र
अघिल्लो शीर्षक र वर्णनकाे प्रयोग गर्नुहोस्
थिम
- एट्रिब्युसन- सेयरअलाइक ४.०
- एट्रिब्युसन ४.०
- एट्रिब्युसन- सेयरअलाइक ३.०
- एट्रिब्युसन ३.०
+ एट्रिब्युसन- सेयरअलाइक ४.०
+ एट्रिब्युसन ४.०
+ एट्रिब्युसन- सेयरअलाइक ३.०
+ एट्रिब्युसन ३.०
CC०
CC BY-SA ३.०
CC BY-SA ३.०
@@ -181,7 +220,10 @@
कमन्स
मूल्याङ्कन गर्नुहाेस्
प्राय सोधिएका प्रश्नहरू
+ प्रयोगकर्ता निर्देशिका
इन्टरनेट उपलब्ध छैन
+ सूचनाहरू प्राप्त गर्दा त्रुटि भयो
+ समीक्षाको लागि छवि प्राप्त गर्दा त्रुटि भयो। फेरि प्रयास गर्न रिफ्रेस थिच्नुहोस्।
कुनै सूचनाहरू फेला परेन
अनुवाद
भाषाहरू
@@ -189,6 +231,9 @@
अगाडि बढ्नुहोस्
रद्द गर्नुहोस्
पुनः प्रयास गर्नुहोस्
+ यो ठाउँको फोटो चाहिन्छ।
+ यो ठाउँको फोटो पहिले नै छ।
+ यो ठाउँ अब अवस्थित छैन।
कुनैपनि तस्वीर फेला परेन
निषेधित
तपाईलाई कमन्स सम्पादन गर्नबाट प्रतिबन्ध गरिएको छ
@@ -217,13 +262,13 @@
तथ्याङ्कहरू
धन्यवाद प्राप्त भयाे
विशेष तस्वीरहरू
- स्तर
+ स्तर %d
प्रयाेग भएका तस्वीरहरू
कमन्स सूचना
योगदानहरू
नजिकैको
सूचनाहरू
- सूचनाहरू (अभिलेख)
+ सूचनाहरू (अभिलेखित)
सूची
भण्डारण अनुमति
अर्को
@@ -254,7 +299,7 @@
लोगो
सफल
सफल
- तपाईले हालसम्म कुनैपनि सम्पादन गर्नु भएकाे छैन
+ तपाईले हालसम्म कुनैपनि सम्पादन गर्नु भएकाे छैन %s
खाता सृजना गरियो!
नजिकैको स्थान भेटियो
पुस्तक चिनोहरू
@@ -262,7 +307,7 @@
पुस्तक चिनाेमा थपियाे
भित्तेपत्रकाे रूपमा चयन गर्नुहोस्
वालपेपर सेट गर्दै। कृपया प्रतीक्षा गर्नुहोस् …
- पुनर्निर्धारित
+ प्रणाली पछ्याउनुहोस्
गाढा
हल्का
पुस्तक चिनोहरू
@@ -279,7 +324,8 @@
थप जान्नुहोस्
विकी लभ्स मोनुमेन्टस्
अनुमति आवश्यक
- प्रयोगकर्ता पृष्ठ हेर्नुहोस्
+ वैकल्पिक अनुमति: श्रेणी सुझावहरूको लागि हालको स्थान प्राप्त गर्नुहोस्
+ प्रयोगकर्ता पृष्ठ हेर्नुहोस्
सम्पादन श्रेणीहरू
उन्नत विकल्प
तपाईँले निकटता क्वेरी अनुकूलन गर्न सक्नुहुन्छ। त्रुटी भएमा पूर्ववत् गर्नुभएर लागू गर्नुहोस्।
@@ -306,7 +352,7 @@
यस प्रयोगकर्तालाई रोक लगाउन अनुरोध गर्नुहोस्
तस्विर सम्पादन गर्नुहोस्
स्थान सम्पादन गर्नुहोस्
- स्थान अपडेट गरियो!
+ स्थान अद्यावधिक गरियो!
यो स्थान हटाउनुहोस्
स्थान चेतावनी हटाउनुहोस्
स्थान हटाइयो!
diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml
index b0cbef3e3..adb05e02f 100644
--- a/app/src/main/res/values-nl/strings.xml
+++ b/app/src/main/res/values-nl/strings.xml
@@ -173,14 +173,17 @@
U hebt nog geen afbeeldingen geüpload.
Opnieuw proberen
Annuleren
+ Typ taalnaam
+ Recente zoekopdrachten
+ Alle talen
Door deze foto te delen, verklaar ik dat dit mijn eigen werk is, dat het geen auteursrechtelijk beschermd materiaal of selfies bevat en dat het verder voldoet aan het <a href=\"https://commons.wikimedia.org/wiki/Commons:Policies_and_guidelines\">Wikimedia Commons-beleid</a>.
Downloaden
Standaardlicentie
Vorige titel/omschrijving gebruiken
Thema
- Naamsvermelding-GelijkDelen 4.0
+ Naamsvermelding-GelijkDelen 4.0
Naamsvermelding 4.0
- Naamsvermelding-GelijkDelen 3.0
+ Naamsvermelding-GelijkDelen 3.0
Naamsvermelding 3.0
CC0
CC BY-SA 3.0
@@ -234,13 +237,14 @@
Beschrijving
Overleg
Auteur
+ Uploader
Uploaddatum
Licentie
Coördinaten
Niet opgegeven
Word bètatester
Meld u aan voor ons bètakanaal op Google Play en krijg vroegtijdig toegang tot nieuwe functies en bugfixes
- 2FA-code
+ Tweetrapsauthenticatie-code
E-mailverificatiecode
Wilt u zich echt afmelden?
Media-afbeelding is mislukt
@@ -284,6 +288,7 @@
Het uploadproces vereist een actieve internettoegang. Controleer uw netwerkverbinding.
Problemen gevonden in afbeelding
Upload alleen foto\'s die je zelf hebt genomen. Upload geen foto\'s die je van internet hebt gedownload.
+ Uploads
In-app opnamen opslaan
Foto\'s die met de in-app-camera zijn gemaakt, opslaan in de opslag van uw apparaat
Aanmelden bij uw account
@@ -392,7 +397,7 @@
Uitgelichte afbeeldingen
Afbeeldingen via \"Plaatsen in de buurt\"
Niveau %d
- %s (Niveau %s)
+ %s (Niveau %s)
Geüploade afbeeldingen
Afbeeldingen niet teruggedraaid
Gebruikte afbeeldingen
@@ -457,6 +462,8 @@
Eindigt op:
Campagnes weergeven
Bekijk de lopende campagnes
+ Verwijderknop weergeven
+ De knop “Map verwijderen” in de aangepaste kiezer activeren
Toestaan dat de app de locatie ophaalt voor het geval de camera deze niet registreert. Sommige apparaatcamera\'s doen dat namelijk niet. In dergelijke gevallen wordt uw bijdrage nuttiger als u de app de locatie laat ophalen en aan uw foto\'s laat koppelen. U kunt dit op elk gewenst moment wijzigen via de Instellingen.
Toestaan
Afwijzen
@@ -600,6 +607,8 @@
MEDIA
SUBKLASSEN
HOOFDKLASSEN
+ SUBCATEGORIEËN
+ BOVENLIGGENDE CATEGORIEËN
Plaats in de buurt gevonden
Zijn dit afbeeldingen van %1$s?
Is dit een afbeelding van %1$s?
@@ -720,6 +729,8 @@
Wiki Loves Monuments is een internationale fotowedstrijd voor monumenten georganiseerd door Wikimedia
Toestemming Nodig
Kaarten in de buurt moeten TELEFOONSTATUS lezen om goed te kunnen werken
+ Schakel locatievoorzieningen in om plaatsen in de buurt te bekijken.
+ Optionele toestemming: Huidige locatie ophalen voor categoriesuggesties
Bijdragen door Gebruiker: %s
Prestaties van Gebruiker: %s
Gebruikersprofiel bekijken
diff --git a/app/src/main/res/values-nqo/strings.xml b/app/src/main/res/values-nqo/strings.xml
index 99923ca5f..c8b0ad708 100644
--- a/app/src/main/res/values-nqo/strings.xml
+++ b/app/src/main/res/values-nqo/strings.xml
@@ -124,8 +124,8 @@
ߕߦߊ ߓߐߛߎ߲
ߢߍߦߋߟߌ ߞߎ߲߬ߕߐ߮ ߟߊߓߊ߯ߙߊ߫ ߊ߬ ߣߌ߫ ߞߊ߲߬ߛߓߍ߬ߟߌ
ߞߍߗߏ߮
- ߘߍ߬ߡߍ߲߬ߘߌ߬ߟߌ ߄.߀
- ߘߍ߬ߡߍ߲߬ߘߌ߬ߟߌ ߃.߀
+ ߘߍ߬ߡߍ߲߬ߘߌ߬ߟߌ ߄.߀
+ ߘߍ߬ߡߍ߲߬ߘߌ߬ߟߌ ߃.߀
ߥߞߌߡߋߘߌߦߊ ߞߐߡߐ߲ߛ ߟߋ߬ ߦߋ߫ ߖߌ߬ߦߊ߬ߓߍ ߝߊ߲߬ߓߊ ߡߊߛߐ߫ ߟߊ߫ ߡߍ߲ ߠߎ߬ ߦߋ߫ ߟߊߓߞߊߙߊ߫ ߟߊ߫ ߥߞߌߔߋߘߌߦߊ ߟߊ߫.
ߌ ߟߊ߫ ߖߌ߬ߦߊ߬ߓߍ ߦߋ߫ ߡߐ߱ ߟߎ߬ ߘߍ߬ߡߍ߲߬ ߠߊ߫ ߞߊ߬ߙߊ߲ ߡߊ߬ ߘߎߢߊ߫ ߝߊ߲߭ ߓߍ߯߹
ߖߊ߰ߣߌ߲߫ ߌ ߦߋ߫ ߖߌ߬ߦߊ߬ߓߍ߫ ߟߎ߫ ߟߋ߬ ߟߊߦߟߍ߬ ߡߍ߲ ߠߎ߬ ߕߊ߬ߣߍ߲߫ ߥߟߊ߫ ߛߌ߲ߘߌߣߍ߲߫ ߦߴߌ ߖߍ߬ߘߍ ߓߟߏ߫:
@@ -322,6 +322,7 @@
ߖߌ߬ߦߊ߬ߓߍ߫ ߟߊߓߊ߯ߙߕߊ ߟߎ߬
ߖߌ߬ߦߊ߬ߓߍ ߞߊߕߙߍ߬ \"ߛߌ߰ߢߐ߲߰ ߦߙߐ\" ߡߊ߬
ߞߊߓߋ
+ %s (ߞߊߓߋ %s)
ߖߌ߬ߦߊ߬ߓߍ ߓߘߊ߫ ߟߊߦߟߍ߬
ߖߌ߬ߦߊ߬ߓߍ ߡߊ߫ ߖߏ߰ߛߌ߬
ߖߌ߬ߦߊ߬ߓߍ߬ ߟߊߓߊ߯ߙߊߣߍ߲ ߠߎ߬
@@ -577,6 +578,7 @@
ߥߞߌ ߝߙߎߕߎ ߞߊ߬ߣߌ߲߬ߓߊ߮ ߟߎ߬ ߦߋ߫ ߡߊ߲߬ߕߏ߲߬ߕߍ߬ߦߊ ߖߌ߬ߦߊ߬ߓߍ ߛߊ߲ߞߊߢߐ߲߯ߦߊ ߟߋ߬ ߘߌ߫ ߡߍ߲ ߛߌ߲߬ߝߏ߲߬ߧߊ߬ߣߍ߲߫ ߦߋ߫ ߥߞߌߡߋߘߌߦߊ ߓߟߏ߫
ߡߊ߬ߞߏ ߦߋ߫ ߟߊ߬ߘߌ߬ߢߍ߬ߟߌ ߟߊ߫
ߞߙߍ߬ߝߍ߬ ߔߊߔߘߊ (ߡߊߔߛ) ߡߊ߬ߞߏ ߦߋ߫ ߜߋߟߋ߲ߜߋߟߋ߲ ߞߊ߬ߝߏ ߘߐߞߊ߬ߙߊ߲ ߠߊ߫ ߞߊ߬ ߛߋ߫ ߓߊ߯ߙߊ߫ ߟߴߊ߬ ߢߊߓߘߍ ߡߊ߬
+ ߢߣߊߕߊߟߌ ߟߊ߬ߘߌ߬ߢߍ߬ߟߌ: ߕߋ߲߭ߕߋ߲߭ ߦߌߟߡߊ߫ ߦߌ߬ߘߊ߬ߣߍ߲ ߠߎ߬ ߘߌ߲߬ߞߌߙߊ ߟߊߛߐ߬ߘߐ߲߬.
ߟߊ߬ߓߊ߰ߙߊ߬ߟߊ %s: ߟߊ߫ ߓߟߏߡߊߜߍ߲
ߟߊ߬ߓߊ߰ߙߊ߬ߟߊ %s: ߟߊ߫ ߞߎ߲߬ߝߊ߰ߟߌ ߟߎ߬
ߟߊ߬ߓߊ߰ߙߊ߬ߟߌ߫ ߞߐߜߍ ߦߋ߫
diff --git a/app/src/main/res/values-oc/strings.xml b/app/src/main/res/values-oc/strings.xml
index f837c4732..5b045c3fc 100644
--- a/app/src/main/res/values-oc/strings.xml
+++ b/app/src/main/res/values-oc/strings.xml
@@ -88,10 +88,10 @@
Telecargar
Licéncia
Tèma
- Atribucion-PartimentIdentic 4.0
- Atribucion 4.0
- Atribucion-PartimentIdentic 3.0
- Atribucion 3.0
+ Atribucion-PartimentIdentic 4.0
+ Atribucion 4.0
+ Atribucion-PartimentIdentic 3.0
+ Atribucion 3.0
CC0
CC BY-SA 3.0
CC BY 3.0
diff --git a/app/src/main/res/values-pa/strings.xml b/app/src/main/res/values-pa/strings.xml
index fa2051047..d1f12c4e9 100644
--- a/app/src/main/res/values-pa/strings.xml
+++ b/app/src/main/res/values-pa/strings.xml
@@ -128,10 +128,10 @@
ਮੂਲ ਲਸੰਸ
ਪਿਛਲੇ ਸਿਰਲੇਖ ਅਤੇ ਵੇਰਵੇ ਦੀ ਵਰਤੋਂ ਕਰੋ
ਵਿਸ਼ਾ-ਵਸਤੂ
- Attribution-ShareAlike 4.0
- Attribution 4.0
- CC Attribution-ShareAlike 3.0
- Attribution 3.0
+ Attribution-ShareAlike 4.0
+ Attribution 4.0
+ CC Attribution-ShareAlike 3.0
+ Attribution 3.0
CC0
CC BY-SA 3.0
CC BY 3.0
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index 9476382a4..c1d25eb0c 100644
--- a/app/src/main/res/values-pl/strings.xml
+++ b/app/src/main/res/values-pl/strings.xml
@@ -1,6 +1,7 @@
+ ويکيخونديځ خرابشوی
+ اوو. يو څه ناسم پېښ شول!
+ موږ ته ووایاست چې تاسو څه کول غواړئ، بيايې له موږ سره د برېښليک له لارې شريک کړئ. دا به موږ سره د هغې سمولو کې مرسته وکړي!
مننه!
diff --git a/app/src/main/res/values-ps/strings.xml b/app/src/main/res/values-ps/strings.xml
index 6083dc833..95daaaf5b 100644
--- a/app/src/main/res/values-ps/strings.xml
+++ b/app/src/main/res/values-ps/strings.xml
@@ -10,42 +10,42 @@
-->
د خونديځ فيسبوک پاڼه
- خونديځ ګيټهوب سرچينه کوډ
+ خونديځ گيټهاب سرچينه کوډ
خونديځ نښان
خونديځ وېبپاڼه
له ځای ټاکونکي وتل
سپارل
- بل سپيناوی ورزياتول
- نوې ونډې ورزياتول
- د کامرې له لارې ونډه ورزياتول
- انځورونو له لارې ونډه ورزياتول
- د پخوانيو ونډو له انځورتونه د ونډې ورزياتول
+ بل څرگنداوی ورگډول
+ نوې ونډې ورگډول
+ د کامرې له لارې ونډه ورگډول
+ انځورونو له لارې ونډه ورگډول
+ د پخوانيو ونډو له انځورتونه د ونډې ورگډول
نيونگې
- ژبې سپيناوی
+ ژبې څرگنداوی
نيونگ
- سپيناوی
+ څرگنداوی
انځور
ټول
- پورته کول
- لټون ليد
+ پورته بدلول
+ لټون کتنه
ځای حالت
ورځې انځور
- - %1$d دوتنه پورته کول
- - %1$d دوتنې پورته کول
+ - %1$d دوتنه راپورتهکول
+ - %1$d دوتنې راپورتهکول
- (%1$d)
- (%1$d)
- پورته کولو پيل
+ راپورتهکول پيلول
- - جريان %d پورته کول
- - پورته کولو %d جريان
+ - راپورتهکولو %d بهير
+ - راپورتهکولو %d بهير
- %d upload
- - %d پورته کول
+ - %d راپورتهکول
- دا انځور به د منښتليک %1$s لاندې وي
@@ -100,7 +100,7 @@
لږ
نابريال شو
%1$d%% بشپړ
- د برسېرېدلو په حال کې…
+ راپورتهکېږي
له انځورتون څخه
انځور اخيستل
نژدې
@@ -111,11 +111,11 @@
د دوتنې مخ کتل
نيونگ (اړين دی)
مهرباني وکړئ، د دې دوتنې لپاره نيونگ ورکړئ
- څرگندونه
+ څرگنداوی
نيونگ
غونډال ته ننوتنه ناشونې ده - د جال پاتې راتلنه
- ډیری ناکامه هڅې. لطفا څو دقیقې وروسته بیا هڅه وکړئ.
- بخښنه غواړو، په دي کارن د کامنز لخوا بنديز ولګول شو
+ ډېرې ناکامه هڅې. لطفا څو دقیقې وروسته بیا هڅه وکړئ.
+ بخښنه غواړو، په دې کارن د کامنز لخوا بنديز ولگول شو
تاسو بايد خپل دوه لامليز تاييد کوډ ورکړئ.
ستاسو برېښليک پتې ته د ننوتلو تاييد کوډ لېږل شوی دی. مهرباني وکړئ د ننوتلو لپاره کوډ ورکړئ.
غونډال کې ننوتنه نابريالۍ شوه
@@ -154,6 +154,9 @@
تاسې تر اوسه کوم انځور نه دی پورته کړی.
بياآزمويل
ناگارل
+ ژبې نوم وليکئ
+ وروستنۍ پلټنې
+ ټولې ژبې
ښکته کول
تلواليز منښتليک
مخکنی سرليک او سپيناو وکاروئ
@@ -163,6 +166,7 @@
ځانگړي کونگ-ورته وېشنه ۳.۰
ځانگړي کونگ ۳.۰
CC BY 3.0
+ ويکيرسنۍ خونديځ د ډېرو هغه انځورونو کوربهتوب کوي چې په ويکيپېډيا کې کارول کېږي.
ستاسو انځورونه د نړۍ په گوټ گوټ کې خلکو ته زده کړه ورکوي!
مهرباني وکړئ هغه انځورونه راپورته کړئ چې په بشپړ ډول تاسو اخيستي يا جوړ کړې وي:
طبيعي څيزونه (گلان، څاروي، غرونه)
@@ -187,28 +191,39 @@
رابرسېرېږي...
هېڅ هم نه دی ټاکل شوی
هيڅ نيونگ نشته
- څرگندونه نشته
+ څرگنداوی نشته
هيڅ شننه نشته
- نامعلوم جواز
+ ناجوت منښتليک
تازه کول
د زېرمه کولو د پرېښولي غوښتنه کول
اړينه پرېښولی: بهرنۍ زېرمه ولولئ. کاريال ستاسو انځورتونه ته پرته له دې لاسرسی نشي موندلی.
+ اجازې ته اړتيا ده: باندنۍ زېرمهکولو ليکل. له دې پرته کاريال ستاسو کامرې/انځورتون ته لاسرسی نشي موندلای.
+ د ځای د اجازې غوښتنه کول
ښه
گواښنه
+ د دوتنې غبرگونی نوم وموندل شو
راپورتهکول
هو
نه
نيونگ
سرليک
- څرگندونه
+ وييانځوريز استازيتوب
+ څرگنداوی
شننه
ليکوال
+ راپورتهکوونکی
راپورتهکېدلو نېټه
منښتليک
کورډيناټونه
هېڅ نه دي چمتو شوي
ازمېښتي ازمايښتگر شئ
- ويکيپېډياښه راغلئ
+ برېښليک تاييد کوډ
+ ايا تاسو په رښتيا له غونډاله وتل غواړئ؟
+ رسنۍ انځور نابرياله شو
+ هېڅ څېرمهوېشنيزې ونه موندل شوې
+ هېڅ بنسټيزې وېشنيزې ونه موندل شوې
+ ويکيپېډيا ته ښه راغلئ
+ لمېسلرښتې هرکلی
ناگارل
پرانېستل
تړل
@@ -218,13 +233,39 @@
په اړه
اوڼنې
غبرگون
+ گيټهاب له لارې غبرگون
وتل
+ لارښوونيزې
+ خبرتياوې
بياکتنه
- هيڅ څرگنداوی ونهٔ موندل شو
+ هېڅ څرگنداوی ونه موندل شو
+ ټولگړې دوتنې مخ
+ ويکياومتوک توکی
+ ويکيپېډيا ليکنه
+ انځور ډېر تياره دی.
+ انځور ډېر جړ دی.
+ نښلېدا تېروتنه
+ په انځور کې موندل شوې ستونزې
+ يادښت دوتنه لېږل
+ تېروتنه! وېبتړ ونه موندل شو
+ ړنگېدو ته نوماند
+ دا انځور ړنگېدو ته نوماند شوی دی.
+ سپيناوي لپاره وېبپاڼه وگورئ
+ تېرېدل
+ ننوتل
+ آیا تاسو رښتیا غواړئ چې ننوتل پرېږدئ؟
+ په راتلونکي کې تاسو بايد د انځورونو راپورته کولو لپاره غونډال ته ننوځئ.
+ د دې ځانگړنې کارولو لپاره مهرباني وکړئ، غونډال ته ننوځئ
+ ويکيليک ټينگدړې ت ولمېسئ
+ ويکيليک ټينگدړې ته ولمېسل شو
+ څېرمه ځايونه شايد په سمه توگه کار ونکړي، ځکه ځای د لاسرسي وړ نه دي.
+ اينټرنېټ د لاسرسي وړ نه دي. يوازې زېرمه شوي ځايونه ښيي.
+ ځای لاسرسی ناگاره شو. مهرباني وکړئ د دې ځانگړنې کارولو لپاره خپل ځای په لاسي ډول واوڼئ.
تگلوري
ويکياومتوک
ويکيپېډيا
خونديځ
+ و مو ارزوئ
ډځپ
کارن لارښود
ښوونې پرېښودل
@@ -238,6 +279,7 @@
پرمخځه
ناگارل
بيا هڅهکول
+ دا تاسو ته نږدې ځايونه دي چې د ويکيپېډيا ليکنې يې د ښودلو لپاره انځورونو ته اړتيا لري.\n\n\'د دې ځای پلټل\' باندې کليک کولو سره نخچه تاله کوي او د هغه ځای شاوخوا نږدې سيمې لټون پيلول.
داځای انځور ته اړتيا لري.
دا ځای لادمخه انځور لري.
دا ځای نور شتون نه لري.
@@ -253,5 +295,233 @@
وروستۍ پلټنې:
وروستۍ پلټل شوې پوښتنې
وروستۍ ژبې پوښتنې
+ رسنۍ
+ وېشنيزې
+ توکي
+ ټاکلې
+ موبايل له لارې راپورتهشوی
+ نخشه
+ انځور په ویکياومتوک کې %1$s ته ورگډ شو!
+ پوښتنه
+ پايله
+ پرلهپورې
+ سم ځواب
+ ناسم ځواب
+ کاریال وېشل
+ تاوول
+ ايا تاسو ډاډه ياست چې دا راپورتهکول ناگارل غواړئ؟
+ ايا تاسو غواړئ چې دا لټون ړنگ کړئ؟
+ د پلټنې پېښليک ړنگ شو
+ ړنگولو ته نومول
+ ړنگول
+ لاستهراوړنې
+ پېژنيال
+ مټتړوني
+ شمارنې
+ ترلاسهشوې مننې
+ ټاکلی انځور
+ انځورنه د \"څېرمه ځايونو\" له لارې
+ کچه %d
+ %s (کچه %s)
+ انځورونه راپورتهشول
+ انځورونه په څټگرځولشوي نه دي
+ کارولشوي انځورونه
+ تېروتنه رامنځته شوه!
+ د ليکوال نوم دوديزول
+ ونډې
+ څېرمه
+ خبرتياوې
+ خبرتیاوې (لوستلشوې)
+ څېرمه خبرتياوې ښکارهکول
+ لړليک
+ زخيره کولو اجازه
+ راتلونکی
+ مخکنی
+ انځورونه
+ ځايونه
+ وېشنيزې
+ په کتابنښو کې ورگډول/لرېکول
+ کتابنښې
+ تاسو هېڅ کتابنښې نه دې ورگډېکړې
+ کتابنښې
+ په تېروتنې سره مې راپورتهکړی دی
+ زه نه پوهېدم چې دا به ټولو ته ښکاره شي
+ زه پوه شوم چې دا زما د پټنتيا لپاره بد دی
+ زما اند توپير وکړ، زه نه غواړم چې دا نور په ټوليزه توگه ښکاره شي
+ په بښنې سره دا انځور د يو پوهنغونډ لپاره خواپورې نه دی
+ هېڅ وېشنيزې نه دې ټاکل شوې
+ دا سيمه وپلټئ
+ اجازې غوښتنه
+ دا بيا هېڅکله مه پوښته
+ د ځای غوښتنې پوښتل
+ پای ته رسېږي په:
+ ټاکنيزېسيالۍ ښکارهکول
+ روانې ټاکنيزېسيالۍ وگورئ
+ پرېښول
+ تړل
+ د لاسوند پربنسټ انځور راخيستونکی کارول
+ د انځور پروسسکولو پرمهال تېروتنه رامنځته شوه. مهرباني وکړئ بيا هڅه وکړئ!
+ د سمون لپاره نښه ترلاسه کول
+ د وېشنيزې سمکتنې لپاره کينډۍ ورگډول
+ د %1$s وېشنيزې سمکتنې لپاره غوښتنهکول
+ وېشنيزې سمکتنې غوښتنهکول
+ وېشنيزې سمکتنې غوښتنه وشوه
+ د وېشنيزې سمکتنې غوښتنه کار نه کوي
+ د %1$s وېشنيزې سمکتنې لپاره غوښتنه وشوه
+ د %1$s سمکتنې لپاره غوښتنه نشي کېدای
+ د %1$s وېشنيزې سمکتنې لپاره غوښتنهکول
+ وشو
+ مننې لېږل: برياليتوب
+ %1$s ته په برياليتوب سره مننه ولېږل شوه
+ %1$s ته د مننې لېږلو کې پاتې راغی
+ مننې لېږل: ناکامي
+ %1$s لپاره د مننې لېږل
+ ايا دا ډ لمېسلرښتو سره سم دی؟
+ ايا دا په سمه توگه ډلبندي شوي دي؟
+ ايا دا د منلو وړ دي؟
+ ايا تاسو غواړئ له ونډهوال نه مننه وکړئ؟
+ که دا انځور ټولگټی نه وي؛ نو ړنگېدو ته د نوماندولو لپاره يې په نه کليک وکړئ.
+ بل انځور
+ هو، ولې نه
+ هېڅ انځورونه کارېدلې نه دي
+ هېڅ انځورونه په څټگرځولشوي نه دي
+ هېڅ انځورونه راپورتهشوې نه دي
+ تاسو هېڅ نالوستې خبرتياوې نه لرئ
+ تاسو هېڅ لوستې خبرتياوې نه لرئ
+ يادښتونه شريکول د لارې
+ خپل پيغام بکس وگورئ
+ لوستي کتل
+ نالوستي کتل
+ د انځورونو راخيستلو پرمهال تېروتنه پېښه شوه
+ مهرباني وکړئ په تمه شئ...
+ لمېسلشوی
+ ليکوال
+ لمېسلرېښتې
+ ځای
+ کامرې نمونه
+ د لړۍ شمېرې
+ پوستغالی
+ د رسنيو ځای ته لاسرسی رد شو
+ کاريال د...لارې وېشل
+ انځور مالومات
+ هېڅ وېشنيزې ونه موندل شوې
+ نښان
+ ځکه چې دا
+ د وېشنيزو هممهالولو هڅهکول.
+ وېشنيزه هممهالول
+ بریالیتوب
+ وېشنيزې نشي ورگډېدای.
+ وېشنيزې هممهالول
+ د ښودنو هممهالولو هڅهکول.
+ ښودنې سمول
+ څرگنداوی هممهالول
+ نيونگ هممهالول
+ بریالیتوب
+ همغږيتوبونه %1$s ورگډ شول.
+ څرگنداوي ورگډل شول.
+ نيونگ ورگډ شو.
+ همغږيتوبونه نشي ورگډېدای.
+ څرگنداوی نشي ورگډېدای.
+ انځور وېشل په وسيله د
+ تياره
+ روښانه
+ ځایښودنه بلول
+ نور بارول
+ تاييدول
+ لارښوونې
+ ۱. لاندې ويکيليک وکاروئ:
+ درول
+ بياپيلول
+ درولشوی
+ نور
+ کتابنښې
+ لاستهراوړنې
+ سرمشريزه
+ درجه:
+ شمېر:
+ درجه
+ کارن
+ شمېر
+ د سرمشريزې ځانبڼې په توگه اوڼل
+ ځانبڼې په توگه اوڼل کېږي، مهرباني وکړئ په تمه شئ
+ ځانبڼې ټولگه
+ ځانبڼې په توگه اوڼل
+ کلنی
+ اوونيز
+ هرمهاله
+ راپورتهکول
+ څېرمه
+ کارولشوی
+ زما رتبه
+ محدودې نښلېدا ونگډول چارنشوی!
+ ښه انځورونه
+ انځور ځی
+ نښکه
+ څرگنداوی
+ توکي
+ دوديز ټاکونکی
+ انځورونه نشته
+ وشو
+ پر شا کېدل
+ دوديزه انځور پاکوونکي ته ښه راغلاست
+ پهزړهپورې
+ تړل
+ اجازې ته اړتيا لري
+ کارن پېژنيال کتل
+ وېشنيزې سمول
+ پليکول
+ لهسرهاوڼل
+ ځای اومتوکي له ويکي کارنانو سره مرسته کوي چې ستاسو انځور موندلو او لا ډېر گټور کولو کې مرسته کوي.\nستاسو وروستۍ راپورتهکېدنې ځای نه لري.\nموږ تاسو ته سپارښته کوو چې خپل د ځای ښودنه د کامرې په کاريال په اوڼنو کې بل کړئ.\nله راپورته کولو مو مننه!
+ هېڅ ځای ونه موندل شو
+ ځای ورگډول
+ سپيناوی
+ ایپيآی کچه
+ اندرويد بلبڼه
+ سولگر جوړوونکی
+ سولگر نمونه
+ وسيلې نوم
+ شبکې ډول
+ له غبرگون ورکولو مو مننه
+ د غبرگون لېږلو پرمهال تېروتنه
+ ستاسو غبرگون څه دی؟
+ ستاسو غبرگون
+ د راپورتهکولو لپاره نه دی، په توگه نښه کول
+ د راپورتهکولو لپاره نه دی، په توگه نه نښه کول
+ د راپورتهکولو لپاره نه دی، په توگه نښه کول
+ د راپورتهکولو لپاره نه دی، په توگه نه نښه کول
+ ټاکلشوی انځور
+ راپور
+ سپين شاليد اوڼل
+ تور شاليد اوڼل
+ سرغړونې راپور
+ د دې کارن راپور ورکول
+ دا گڼون راپور کول
+ د دې کارن د بنديز غوښتنه
+ د ټول سکرين ټاکنې دريځ ته ښه راغلاست
+ د نژدېکولو او لرېکولو لپاره دوه گوتې وکاروئ.
+ انځور سمول
+ ځای سمول
+ ځای هممهالهشو
+ ځای لرېکول
+ ځای گواښنه لرېکول
+ راپورتهکېدنې
+ په تمه
+ پاتې راغلی
+ ځای اومتوکي نشي بارېدای
+ ړنگول تایید کړئ
+ ړنگول
+ ناگارل
+ د بارولو پرمهال تېروتنه
+ هېڅ کارهونه ونه موندل شوه
+ خونديځ
+ نورې ويکيگانې
+ دوتنې کارېدنې
+ يواړخيزهوېبکتنېچاره
گڼون
+ گڼون له منځه وړل
+ د گڼون له منځه وړلو گواښنه
+ ورکېدل يوه <b>وروستۍ چاره ده</b> او بايد <b>يوازې هغه وخت وکارول شي کله چې تاسو غواړئ د تل لپاره سمون پرېږدئ</b> او همدارنگه تر شوني بريده د ځان اړوند پخوانۍ اړيکې پټې کړئ.<br/><br/>په ويکيرسنۍ خونديځ کې گڼون ړنگول ستاسو د گڼون نوم بدلولو له لارې ترسره کېږي؛ ترڅو نور ستاسو ونډې ونه پېژني، دې چارې ته ورکېدل يا گڼون له منځه وړل وايي. <b>ورکېدل په پروژو کې په بشپړ ډول ستاسو د هويت او ونډو لرې کولو ډاډ نه درکوي</b>.
+ نيونگ
+ نيونگ ټينگدړې ته ولمېسل شو
+ ړنگولو ته نومولشوې
diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml
index 7f45ab484..2b087a60c 100644
--- a/app/src/main/res/values-pt-rBR/strings.xml
+++ b/app/src/main/res/values-pt-rBR/strings.xml
@@ -178,10 +178,10 @@
Licença padrão
Usar o título e descrição anterior
Tema
- Atribuição-CompartilhaIgual 4.0
- Atribuição 4.0
- Attribution-ShareAlike 3.0
- Attribution 3.0
+ Atribuição-CompartilhaIgual 4.0
+ Atribuição 4.0
+ Attribution-ShareAlike 3.0
+ Attribution 3.0
CC0
CC BY-SA 3.0
CC BY 3.0
@@ -680,7 +680,7 @@
Mostrar no aplicativo de mapa
Editar localização
A visualização da imagem do seletor de local
- A sombra da visualização da imagem do seletor de local
+ A sombra da visualização da imagem do seletor de local
Localização da imagem
Verifique se a localização está correta
Etiqueta
@@ -706,6 +706,7 @@
O Wiki Loves Monuments é um concurso internacional organizado pela Wikimedia sobre fotografias de monumentos
Precisa de permissão
Os mapas próximos precisam ler ESTADO DO TELEFONE para funcionar corretamente
+ Permissão opcional: Obter a localização atual de sugestões de categoria
Contribuições do usuário: %s
Conquistas do usuário: %s
Ver página de usuário
diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml
index dcafbbeb8..c7a5e5140 100644
--- a/app/src/main/res/values-pt/strings.xml
+++ b/app/src/main/res/values-pt/strings.xml
@@ -1,5 +1,6 @@
- Página da wiki Commons no Facebook
- Código-fonte da wiki Commons no Github
- Logótipo da wiki Commons
- Sítio da wiki Commons
+ Página do Facebook da Commons
+ Código Fonte do Github da Commons
+ Logótipo da Commons
+ Site da Web da Commons
Sair do selecionador de localização
- Enviar
+ Submeter
Adicionar outra descrição
Adicionar nova contribuição
Adicionar contribuição da câmara
Adicionar contribuição de fotos
Adicionar contribuição da galeria de contribuições anteriores
Legendas
- Descrição da língua
+ Descrição do Idioma
Legenda
Descrição
Imagem
Todas
Alternar para cima
- Vista de pesquisa
- Estado do local
+ Visualização da Pesquisa
+ Estado do Local
Imagem do Dia
- - a carregar %1$d ficheiro
- - a carregar %1$d ficheiros
+ - a enviar %1$d ficheiro
+ - a enviar %1$d ficheiros
- (%1$d)
- (%1$d)
- A iniciar carregamentos
+ A iniciar envios
- - A processar %d carregamento
- - A processar %d carregamentos
+ - A processar %d envio
+ - A processar %d envios
- - %d carregamento
- - %d carregamentos
+ - %d envio
+ - %d envios
- Esta imagem será licenciada com a %1$s
- Estas imagens serão licenciadas com a %1$s
- - %1$d carregamento
- - %1$d carregamentos
+ - %1$d envio
+ - %1$d envios
- A receber conteúdo partilhado. O processamento da imagem pode demorar algum tempo, dependendo do tamanho da mesma e do seu dispositivo
@@ -78,19 +79,19 @@
Comentários
Privacidade
Commons
- Configurações
- Carregar na wiki Commons
- Carregamento em progresso
+ Definições
+ Enviar para a Commons
+ Envio em progresso
Nome de utilizador
Palavra-passe
- Entrar na sua conta da wiki Commons Beta
- Entrar
+ Inicie a sessão na sua conta de Commons Beta
+ Iniciar sessão
Esqueceu-se da palavra-passe?
Registar-se
A iniciar sessão
- Aguarde, por favor…
- A atualizar legendas e descrições
- Aguarde, por favor…
+ Por favor, aguarde…
+ A atualizar as legendas e descrições
+ Por favor, aguarde…
Sessão iniciada!
O início de sessão falhou!
O ficheiro não foi encontrado. Tente outro, por favor.
@@ -99,27 +100,27 @@
O carregamento de mais de três imagens funciona de maneira mais fiável quando a otimização da bateria está desligada. Desligue a otimização da bateria para a aplicação Commons nas configurações, de forma a ter uma experiência de carregamento mais fluida. \n\nPossíveis passos para desativar a otimização da bateria:\n\nEtapa 1: premir o botão \'Configurações\' abaixo.\n\nEtapa 2: mudar de \'Não otimizado\' para \'Todas as aplicações\'.\n\nEtapa 3: pesquisar \"Commons\" ou \"fr.free.nrw.commons\".\n\nEtapa 4: premir esta e selecionar \'Não otimizar\'.\n\nEtapa 5: pressionar \'Concluído\'.
Falha na autenticação. Por favor faça login novamente.
Carregamento iniciado!
- Fila de carregamento (modo de ligação limitada ativado)
- %1$s carregado!
- Toque para ver o seu carregamento
- A carregar o ficheiro %s
- A carregar %1$s
- A terminar o carregamento de %1$s
- O carregamento de %1$s falhou
- Carregamento de %1$s em pausa
+ Envio em fila (modo de ligação limitada ativado)
+ %1$s enviado!
+ Toque para ver o seu envio
+ A enviar o ficheiro %s
+ A enviar %1$s
+ A terminar o envio de %1$s
+ O envio de %1$s falhou
+ Envio de %1$s pausado
Tocar para ver
Tocar para ver
- Carregamentos recentes
- Em espera
- Falhado
- %1$d%% transferido
- A carregar
+ Meus Envios Recentes
+ Em fila
+ Falhou
+ %1$d%% concluído
+ A enviar
Da galeria
- Tirar foto
+ Tirar fotografia
Nas redondezas
- Carregamentos
- Copiar ligação
- A ligação foi copiada para a área de transferência
+ Meus envios
+ Copiar hiperligação
+ A hiperligação foi copiada para a área de transferência
Partilhar
Ver página do ficheiro
Legenda (obrigatória)
@@ -132,13 +133,14 @@
Tem de fornecer o seu código de autenticação de dois fatores.
Foi enviado um código de verificação de autenticação para o seu endereço de correio eletrónico. Por favor, forneça o código para iniciar a sessão.
O início de sessão falhou
- Carregar
+ Enviar
Dê um nome a este conjunto
Modificações
Carregar
Pesquisar categorias
Procurar elementos que o seu conteúdo multimédia retrata (montanha, o Taj Mahal, etc.)
Gravar
+ Menu de fluxo
Atualizar
Lista
(Ainda não foi carregado nenhum ficheiro)
@@ -148,7 +150,7 @@
%1$s não tem nenhuma classe progenitora
Adicione categorias para tornar as suas imagens mais fáceis de encontrar na wiki Wikimedia Commons.\nComece a escrever para adicionar categorias.
Categorias
- Configurações
+ Definições
Registar-se
Imagens destacadas
Seletor personalizado
@@ -172,10 +174,10 @@
Licença padrão
Usar título e descrição anteriores
Tema
- Atribuição-CompartilhaIgual 4.0
- Atribuição 4.0
- Atribuição–CompartilhaIgual 3.0
- Atribuição 3.0
+ Atribuição-CompartilhaIgual 4.0
+ Atribuição 4.0
+ Atribuição–CompartilhaIgual 3.0
+ Atribuição 3.0
CC0
CC BY-SA 3.0
CC BY 3.0
@@ -228,6 +230,7 @@
Descrição
Discussão
Autor
+ Carregador
Data de carregamento
Licença
Coordenadas
@@ -235,6 +238,7 @@
Torne-se um Testador Beta
Opte pelo nosso canal beta no Google Play e obtenha acesso antecipado às novas funcionalidades e às correções de erros
Código de autenticação de dois fatores
+ Código de verificação do e-mail
Deseja realmente sair?
Falha na imagem multimédia
Não foi encontrada nenhuma subcategoria.
@@ -255,6 +259,7 @@
Sobre
Configurações
Comentários
+ Comentários via GitHub
Sair
Explicação
Notificações
@@ -294,8 +299,10 @@
Copiar o texto wiki para a área de transferência
O texto wiki foi copiado para a área de transferência
A identificação de locais próximos pode não funcionar devidamente; o serviço de localização não está disponível.
+ Internet indisponível. A mostrar apenas os locais em cache.
Acesso à localização negado. Para usar esta funcionalidade defina a sua localização manualmente, por favor.
É necessária permissão para mostrar uma lista dos locais próximos
+ É necessária a permissão para exibir uma lista de imagens próximas
Indicações
Wikidata
Wikipédia
@@ -364,6 +371,7 @@
Partilhar aplicação
Rodar
Não foi possível carregar locais próximos
+ Não há fotografias nesta área
Não existem locais próximos
Erro ao procurar monumentos próximos.
Não há pesquisas recentes
@@ -381,7 +389,7 @@
Imagens destacadas
Imagens via \"Locais próximos\"
Nível %d
- %s (Nível %s)
+ %s (Nível %s)
Imagens carregadas
Imagens não revertidas
Imagens usadas
@@ -707,6 +715,7 @@
Wiki Loves Monuments é um concurso internacional de fotografias de monumentos, organizado pela Wikimedia
Necessitar de Permissão
Os mapas de locais próximos precisam de ler ESTADO DO TELEFONE para funcionar devidamente
+ Permissão opcional: obter a localização atual para sugestões de categoria
Contribuições do utilizador: %s
Realizações do utilizador: %s
Ver perfil de utilizador
@@ -769,6 +778,7 @@
Editar localização
Localização actualizada!
Remover Localização
+ Remover Aviso de Localização
Localização removida!
Agradecer ao autor
Erro no envio de agradecimento ao autor.
@@ -777,16 +787,41 @@
Ficheiro guardado com sucesso
Deseja abrir o ficheiro GPX?
Deseja abrir o ficheiro KML?
+ Não foi possível guardar o ficheiro KML.
+ Não foi possível guardar o ficheiro GPX.
Guardar Ficheiro KML
Guardar Ficheiro GPX
- %d imagem selecionada
- %d imagens selecionadas
- Reporte um problema sobre este item na Wikidados
+ Nota sobre múltiplos envios
+ Reporte um problema sobre este item no Wikidata
Por favor, insira alguns comentários
Discussão
Outro problema ou informação (por favor, explique em baixo).
O seu comentário é publicado na seguinte página da wiki: <a href=\"https://commons.wikimedia.org/wiki/Commons:Mobile_app/Feedback\">Commons:Mobile app/Feedback</a>
+ A cancelar todos os envios...
+ Envios
+ Pendente
+ Falhou
+ Não foi possível carregar os dados do local
+ Eliminar Pasta
+ Confirmar Eliminação
+ Eliminar
+ Cancelar
+ Erro ao carregar
+ Não foram encontradas utilizações
+ Commons
+ Outras wikis
+ Utilização de ficheiro
+ Conta
+ Legenda
+ Legenda copiada para a área de transferência
+ Parabéns, todas as fotografias neste álbum foram envidas ou marcadas como \'não para enviar\'.
+ Mostrar no Explorador
+ Mostrar nas Proximidades
+ Criada e enviada por: %1$s
+ Criada por %1$s e enviada por %2$s
Nomeada para Eliminação
diff --git a/app/src/main/res/values-qq/error.xml b/app/src/main/res/values-qq/error.xml
index e48ac7c9d..9fffe2e0f 100644
--- a/app/src/main/res/values-qq/error.xml
+++ b/app/src/main/res/values-qq/error.xml
@@ -1,9 +1,11 @@
Title of dialog to show when the app crashes
+ Shown when the application crashed.
Prompt asking people to enter info about what they were doing when the app crashed
Toast to be displayed once someone sends an error report thanking them.\n{{Identical|Thank you}}
diff --git a/app/src/main/res/values-qq/strings.xml b/app/src/main/res/values-qq/strings.xml
index 7b521a8ac..1eb19052f 100644
--- a/app/src/main/res/values-qq/strings.xml
+++ b/app/src/main/res/values-qq/strings.xml
@@ -6,6 +6,7 @@
* Annick green
* Cabal
* Googology
+* H78c67c
* LeGuyanaisPure
* Liuxinyu970226
* Madhurgupta10
@@ -26,34 +27,72 @@
* Ата
-->
+ Alternative text for image link.
+ Alternative text for image link.
+ Alternative text for image.
+ Alternative text for image link.
+ Alternative text.
{{Identical|Submit}}
+ Seems to be unused.
+ Alternative text for button.
+ Alternative text for button.
+ Alternative text for button.
+ Alternative text for button.
+ List title.
+ Seems to be unused.
+ Alternative text.
+ {{Identical|Description}}
+ Alternative text.
{{identical|All}}
- Reba ishakiro
+ Seems to be unused.
+ Alternative text.
+ Seems to be unused.
+ Alternative text.
Status text about number of uploads left.\n* %1$d represents number of uploads left, including current one
See the current issue [https://phabricator.wikimedia.org/T267142 T267142] tracked in Phabricator about the <code><nowiki>|zero=</nowiki></code> option currently not supported on Translatewiki.net with the custom <code><nowiki>{{PLURAL}}</nowiki></code> rules used by this project for Android, using a non-MediaWiki syntax.
+ Widget title.
+ Widget title.
{{Identical|Upload}}
Text label telling user the license of the current upload in progress. %1$s refers to appropriate display text for the chosen CC license
+ Counter in widget.
+ Seems to be unused.
{{Identical|Explore}}
+ Seems to be unused.
{{Identical|General}}
+ A title of an option group in the settings.
+ A title of an option group in the settings.
The name of the application. A short form of \"Wikimedia Commons\". It is used in the app\'s launcher icon.\n{{Identical|Wikimedia Commons}}
Label for menu item to show settings.\n{{Identical|Settings}}
+ Label of option to upload to Commons when sharing from another app.
+ Title in window.
{{Identical|Username}}
{{Identical|Password}}
+ Log in call to action in testing mode.
Button to have people log in.\n{{Identical|Log in}}
+ Message shown on the login screen to request a new password.\n{{Identical|Forgot password}}
{{Identical|Sign up}}
Title of dialog box shown while logging in.\n{{Identical|Logging in}}
Contents of dialog box shown while logging in.\n{{Identical|Please wait}}
+ A string in a progress dialog.
+ A string in a progress dialog.
Message when login has succeeded.\n{{Identical|Login successful}}
Message when login has failed.\n{{Identical|Login failed}}
+ Error message.
+ Error message.
+ An alert when uploading multiple files.
+ An alert when uploading multiple files.
Message shown to user when authentication to Commons fails. Shown as a small toast that disappears after a while.
Toast message when uploading has started
+ A notice after queueing an upload.
Title for notification about upload being completed. %1$s represents file name.\n{{Identical|Uploaded}}
Text for notification about upload being completed.
Title for notificiation about upload being started. %1$s represents file name
Title for notification about upload being in progress. %1$s represents file name\n{{Identical|Uploading}}
Title for notification about upload being completed. %1$s represents file name
Title for notification about upload failing. %1$s represents file name
+ A notice that the current upload is paused.\n\n%1$s is the media\'s display title.
Text for notification about upload being completed.
+ Subtitle for {{msg-wm|Commons-android-strings-upload paused notification title}}.
Title for screen showing my contributions.
Show status of upload as currently queued.\n{{Identical|Queue}}
Show status of upload as failed.\n{{Identical|Failed}}
@@ -63,12 +102,19 @@
Action bar item to allow the user to take a picture to upload
{{Identical|Nearby}}
Display name for user\'s contributions in the phone\'s settings sync screen.\n{{Identical|My upload}}
+ Menu item.
+ Notice after copying a link.
{{Identical|Share}}
+ Menu item.
{{Identical|Title}}
+ A notice during the upload process.
{{Identical|Description}}
+ Label of the caption of the image.
Error message shown to user when login can not be completed due to network issues.
Error message shown to user when login can not be completed because the user has attempted to login too many times in a short period of time, and hence been throttled.
Error message shown to user when login can not be completed because the user is blocked on Wikimedia Commons
+ An error message in the login screen.
+ An error message in the login screen.
{{Identical|Login failed}}
{{Identical|Upload}}
Hint for the textbox that lets user type the Base title for the set of multiple images being uploaded. Example: If the user types \'Visit to Kolkata\' in this text box, the images will all be titled as \'Visit to Kolkata - 1\', \'Visit to Kolkata - 2\', etc.
@@ -77,14 +123,22 @@
This message is followed by a list of the categories.\n{{Identical|Search category}}
{{Doc-commons-app-depicts}}
Hint text on menu item to save selected categories.\n{{Identical|Save}}
+ Alternative text.
{{Identical|Refresh}}
This is an action button, usually a verb (just like Continue, Cancel, Delete, Search, and so on).\n{{Identical|List}}
+ Notice in the Contributions screen when the user didn\'t upload anything.
Message shown to the user when no category matching what they searched for was found. %1$s represents the category name
+ %1$s - search query.
+ %1$s is a Wikidata item name.
+ %1$s is a Wikidata item name.
Text explaining to users why and how to add categories to images. Users can also tap this message to skip adding categories.
Title for the activity where Categories are being selected to add to the Image.\n{{Identical|Category}}
{{Identical|Settings}}
{{Identical|Sign up}}
+ Activity title.
+ Activity title.
{{Identical|Category}}
+ Activity title.
{{Identical|About}}
License and legal notice. %1$s is {{msg-wm|Commons-android-strings-trademarked name}}
{{Ignored}}\n\nUsed in {{msg-wm|Commons-android-strings-about license}}\n\n{{Identical|Wikimedia Commons}}
@@ -93,13 +147,22 @@
{{Identical|Credit}}
{{Identical|About}}
Menu item text that prompts user to send feedback to WMF via email
+ An error message when trying to send an email fails.
+ Content provider title.
Message shown on contributions list during first sync.
Message shown on contribution list during non-first sync if no uploads present.
Menu item text prompting user to retry a failed upload.\n{{Identical|Retry}}
Menu item text prompting user to cancel and delete a failed upload.\n{{Identical|Cancel}}
+ Placeholder text in the top search box in the language search dialog.
+ A title in the language search dialog.
+ A title in the language search dialog.
+ License text.
Menu item text prompting user to download a selected photo or media file locally.\n{{Identical|Download}}
{{Identical|License}}
This is a button text. Concise wording is preferred (e.g. \"&\" instead of \"and\"), where possible. It should ideally be rendered in one line, even on small devices.
+ Settings menu item.
+ A license name.
+ A license name.
Attribution-Sharelike License display name.
CC Attribution License display name.
CC0 License display name.
@@ -107,7 +170,20 @@
{{Identical|CC BY}}
{{Identical|CC BY-SA}}
{{Identical|CC BY}}
+ Tutorial text.
+ Tutorial text.
+ Tutorial text.
+ Tutorial text.
+ Tutorial text.
+ Tutorial text.
Capitals not required
+ Tutorial text.
+ Tutorial text.
+ Tutorial text.
+ Tutorial text.
+ Tutorial text.
+ Tutorial text.
+ Tutorial text.
Message inviting users to contribute their images.\n\nFollowed by {{msg-wm|Commons-android-strings-welcome wikipedia subtext}}.
Preceded by {{msg-wm|Commons-android-strings-welcome wikipedia text}}.
Message explaining the educational benefit of contributing images.
@@ -118,21 +194,42 @@
Label for categories list in media detail panel.\n{{Identical|Category}}
Placeholder for categories list in media detail panel, while loading from network.\n{{Identical|Loading}}
Placeholder for categories list in media detail panel, if no categories found.\n{{Identical|None selected}}
+ Shown instead of a caption when a caption is not found.
{{Identical|No description}}
+ Shown on a file description screen when the file page has no talk page.
+ Shown on a file page.
Label for menu item that refreshes current list of images.\n{{Identical|Refresh}}
+ Panel title.
+ Explanation for storage permission request.
+ Explanation for permission request.
+ Panel title.
+ Panel title.
+ Explanation for permission request.
{{Identical|OK}}
{{Identical|Warning}}
+ Popup text.
{{Identical|Upload}}
{{Identical|Yes}}
{{Identical|No}}
+ Label of the caption of the image.
{{Identical|Title}}
{{Doc-commons-app-depicts}}
{{Identical|Description}}
{{Identical|Discussion}}
{{Identical|Author}}
+ Label on a file page.
+ Label on a file page.
{{Identical|License}}
{{Identical|Coordinate}}
Describes \"coordinates\".
+ Settings menu item.
+ Settings menu item explanation.
+ Login form label.
+ Login screen command.
+ Confirmation string.
+ Alternative text.
+ Notice when searching for subcategories.
+ Notice when searching for subcategories.
This is a mountain between Yamagata and Miyagi Prefectures in Japan, see [[d:Q167951]] for details.
An animal. See [[:d:Q42569|Wikidata item Q42569]] for a list of possible translations.
A bridge in Japan. See [[:d:Q1046736|Wikidata item Q1046736]] for a list of possible translations.
@@ -147,44 +244,218 @@
{{Identical|Upload}}
{{Identical|Nearby}}
{{Identical|About}}
+ A button that leads the user to change permissions when something is not allowed.
+ {{Identical|Feedback}}
+ Link title.
+ Menu item.
{{Identical|Tutorial}}
{{Identical|Notification}}
{{Identical|Review}}
+ A notice in the nearby screens.
+ Nearby info menu item.
+ Nearby info menu item.
+ Nearby info menu item.
+ Information box for writing descriptions.
+ Information box for writing descriptions.
+ Error message title.
+ Error example.
+ Error example.
+ Error example.
+ Error example.
+ Error.
+ Verification question.
{{Identical/Connection Error}}
+ Error message.
+ Error message.
+ Error.
+ A section title in preferences.
+ Setting.
+ Setting description.
+ Log in screen title.
+ Setting.
+ Setting description.
+ Error message.
+ Error message.
+ Button on image page.
+ Notice after nominating.
<u>See webpage for details</u>
{{Identical|Skip}}
{{Identical|Log in}}
+ Confirmation question.
+ Notice.
+ Notice.
+ Button label.
+ Notice.
+ Error message.
+ Error message.
+ Error message.
+ Error message.
+ Error message.
+ Button at the bottom of the map.
{{Identical|Wikidata}}
{{Identical|Wikipedia}}
+ Button at the bottom of the map.
Link text to rating the app in the apps market.\n\nThe word \"rate\" is supposed to be translated the same way as in the market app.
{{Identical|FAQ}}
+ Link from the About screen.
+ Button at the top of the Welcome tutorial.
+ Error message.
+ Error message.
+ Error message.
+ Notice.
Link text with underline.\n{{Identical|Translate}}
{{Identical|Language}}
+ Explanation.
+ Button.
{{Identical|Cancel}}
{{Identical|Retry}}
\"Search this area\" refers to {{msg-wm|Commons-android-strings-search this area}}.
+ Notice in showcase.
+ Notice in showcase.
+ Notice in showcase.
+ Error message in the bookmarks screen.
+ Error message in the bookmarks screen.
+ %1$s is a username.
+ Describes the user.
+ Notice.
+ App widget heading.
{{Identical|Search}}
+ Label.
{{Identical|Search}}
+ Label.
+ Label.
+ Label in language search screen.
+ Error message.
{{Doc-commons-app-depicts}}
- {{Identical|Map}}
+ Tab title in the search screen.
+ Tab title in the search screen.
+ Tab title in the search screen.
+ Tab title in the \"Explore\" view. Shows featured media.
+ Tab title in the \"Explore\" view. Shows media uploaded via mobile.
+ Tab title in the \"Explore\" view. Shows a map of your area with photos tagged with coördinates nearby.\n\n{{Identical|Map}}
Message shown in a dialog (\"success toast\") after a contribution by the user.\n\nParameter:\n* %1$s - title of the target page on Wikidata
+ Error message.
+ Menu item.
+ Notice.
+ Tab title in the search screen.
+ Question in quiz.
{{Identical|Question}}
+ In the quiz, a third option in addition to \"Yes\" and \"No\".
{{Identical|Result}}
+ Warning.
+ Question that leads to quiz.
+ Tutorial.
+ Tutorial.
+ Tutorial.
+ Tutorial.
+ Tutorial.
+ %1$s is a number.
+ Button in quiz.
+ Instruction.
+ Error.
+ Notice at the end of the quiz.
+ Button in location picker.
+ Shown after answering a question correctly.
+ Shown after answering a question incorrectly.
+ Quiz question.
+ About screen button.
+ Seems to be unused.
+ Error message.
+ Notice.
+ Notice.
+ Error message.
+ Label.
+ Confirmation question.
+ Confirmation question.
+ Confirmation question.
+ Notice.
+ Button on image page.
{{Identical|Delete}}
- Èstatistik
+ Menu item.
+ Menu item.
+ Title on Profile page.
+ Title on Profile page.
To see the correct translation for your language, please go to https://commons.wikimedia.org/wiki/Commons:Featured_pictures and select your language in \"This project page in other languages\".
+ Item in statistics.
+ Item in statistics, %d is the level number.
+ First parameter is username, second parameter is the level number.
+ {{optional}}
+ Item in statistics.
+ Item in statistics.
+ Item in statistics.
+ Call to action.
+ Explanation in statistics.
+ label in statistics:
+ Profile statistics explanation.
+ Profile statistics explanation.
+ Profile statistics explanation.
+ Error message.
+ Notifications title.
+ Settings item.
+ Settings item explanation.
+ Settings item.
+ Top title.
{{Identical|Nearby}}
+ Title.
+ Title.
+ Settings item.
+ Settings item explanation.
+ Screen title.
+ Title.
+ Permission request explanation.
+ Permission explanation recommendation
+ * %1$d - index +1.\n* %2$d - total number of steps.\n* %3$s - {{msg-wm|Commons-android-strings-depicts_step_title}}
Refers to the next \'\'\'step\'\'\' in the uploading process.
Refers to the previous \'\'\'step\'\'\' in the uploading process.
+ Confirmation message.
+ Error message.
+ Tab in bookmark view.
+ Tab in bookmark view.
+ Tab in bookmark view.
+ Call to action.
+ Provider name.
+ Notice when there are no bookmarks.
+ Provider title.
\"Send log file\" is {{msg-wm|Commons-android-strings-send log file}}.
+ Answer to a question.
+ Answer to a question.
+ Answer to a question.
+ Answer to a question.
+ Answer to a question.
+ * %1$s is a date.\n* %2$d is the number of articles in which the image is used.
+ Shown in the contributions view when the user has no contributions.
+ Warning.
+ Warning explanation.
{{Doc-commons-app-depicts}}
{{Doc-commons-app-depicts}}
+ Button for canceling an upload
+ Warning explanation.
+ Similar to a Cancel button.
+ Means that the same license applies to all the images.
+ Button in the map screen.
+ Title of card when opening the nearby view.
+ Explanation of permission request.
+ Failure explanation.
+ Checkbox label in several questions.
+ Setting title.
+ Setting explanation.
+ Error message.
+ Explanation of error message.
+ Label in description campaign. Shows the date of campaign end.
+ Settings title.
+ Setting explanation.
+ Setting.
+ Setting explanation.
+ Permission explanation.
+ Button label, indicating the user wants to allow access.\n\nSee also:\n* {{msg-mw|Mwoauth-form-button-cancel}}\n{{Identical|Allow}}
+ Don\'t give permission.
\"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}}
{{Identical|Author}}
{{Identical|Location}}
+ A label of a checkbox.
{{Doc-commons-app-depicts}}
{{Doc-commons-app-depicts}}
{{Doc-commons-app-depicts}}
@@ -197,6 +468,8 @@
{{Optional}}\n<code>&#169;</code> is the copyright symbol (©).
{{Doc-commons-app-depicts}}
{{Doc-commons-app-depicts}}
+ Label
+ Description
Panning means moving the map left/right/up/down, typically by touching the screen with one finger and moving it.\n\nZooming means making the map\'s scale bigger or smaller, typically by pinching with two fingers.\n\nExample in other app:\nhttps://igss.schneider-electric.com/features/pan-and-zoom-in-definition/
A description of a visual element, location picker image shadow. Used for accesibility usually.
{{Identical|Label}}
@@ -206,11 +479,11 @@
{{Doc-commons-app-depicts}}
{{Identical|Advanced options}}
‘Nearby’ should be translated as in {{msg-wm|Commons-android-strings-navigation item nearby}}
- {{Identical|Detail}}
+ Button at the bottom of the map.\n{{Identical|Detail}}
\"Set as avatar\" should be translated the same as {{msg-wm|Commons-android-strings-menu set avatar}}.
{{Doc-commons-app-depicts}}
An answer to the question in {{msg-wm|Commons-android-strings-custom selector confirm deletion message}}.
{{optional}}
- “Explore” should be translated as in {{msg-wm|Commons-android-strings-navigation item explore}}
- ‘Nearby’ should be translated as in {{msg-wm|Commons-android-strings-navigation item nearby}}
+ An option in the three-dots menu in the map. Switches to the Explore mode.\n\n“Explore” should be translated as in {{msg-wm|Commons-android-strings-navigation item explore}}
+ An option in the three-dots menu in the map. Switches to the Nearby mode.\n\n‘Nearby’ should be translated as in {{msg-wm|Commons-android-strings-navigation item nearby}}
diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml
index 602fb69bf..953c98e6c 100644
--- a/app/src/main/res/values-ro/strings.xml
+++ b/app/src/main/res/values-ro/strings.xml
@@ -154,10 +154,10 @@
Licență implicită
Utilizați titlul și descrierea anterioară
Tema
- Atribuire și distribuire în condiții identice 4.0
- Atribuire 4.0
- Atribuire și distribuire în condiții identice 3.0
- Atribuire 3.0
+ Atribuire și distribuire în condiții identice 4.0
+ Atribuire 4.0
+ Atribuire și distribuire în condiții identice 3.0
+ Atribuire 3.0
CC0
CC BY-SA 3.0
CC BY 3.0
@@ -586,4 +586,5 @@
Rank-ul meu
Citiți mai multe
Modifică locația
+ Permisiune opțională: Obțineți locația curentă pentru sugestiile de categorii
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index a1579d0dd..fd24cad79 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -2,6 +2,7 @@
Ostava na Facebooku
@@ -93,7 +94,7 @@
Otpremi
Pretraži kategorije
Snimi
- Preučitaj
+ Osvježi
Lista
(Još uvijek nema postavljenih datoteka)
Nema kategorija što odgovoraju %1$s
@@ -122,10 +123,10 @@
Standardna licenca
Koristi prethodni naziv i opis
Tema
- Autorstvo-Dijeliti pod istim uslovima 4.0
- Autorstvo 4.0
- Autorstvo-Dijeliti pod istim uslovima 3.0
- Autorstvo 3.0
+ Autorstvo-Dijeliti pod istim uslovima 4.0
+ Autorstvo 4.0
+ Autorstvo-Dijeliti pod istim uslovima 3.0
+ Autorstvo 3.0
Wikimedijina ostava skladišti većinu slika koje se upotrebljavaju na Wikipediji.
Vaše slike pomažu u obrazovanju ljudi širom svijeta!
Postavljajte slike koje su u potpunosti Vaše djelo:
@@ -153,9 +154,9 @@
Nema opisa
Nema razgovora
Nepoznata licenca
- Preučitaj
- Potrebna dozvola: Treba da se pročita iz spoljašnje memorije. Privitak bez ovoga nema pristupa Vašoj galeriji.
- Potrebna dozvola: Treba da se zapiše na spoljašnju memoriju. Privitak bez ovoga nema pristupa Vašoj kameri/galeriji.
+ Osvježi
+ Potrebno ovlaštenje: Čitanje vanjske memorije. Aplikacija ne može pristupiti galeriji bez toga.
+ Potrebno ovlaštenje: Pisanje u vanjskoj memoriji. Aplikacija ne može pristupiti kameri/galeriji bez toga.
U redu
Upozorenje
Postavi
@@ -215,7 +216,7 @@
U budućnosti ćete se morati prijaviti kako biste postavili slike.
Prijavite se da biste koristili ovu funkciju
„U blizini“ možda ne radi kako treba. Lokacija nije dostupna.
- Potrebna je dozvola za prikaz liste lokacija u blizini
+ Potrebno je ovlaštenje za prikaz liste lokacija u blizini
Upute
Wikidata
Wikipedia
@@ -253,6 +254,7 @@
Dijeli privitak
Nije uspelo
Logo
+ Dozvola po potrebi: Utvrđavanje trenutne lokacije za predlaganje kategorija
Doprinosi korisnika: %s
Pregled korisničke stranice
Uredi kategorije
diff --git a/app/src/main/res/values-si/strings.xml b/app/src/main/res/values-si/strings.xml
index 92fa25f3e..7436e96e3 100644
--- a/app/src/main/res/values-si/strings.xml
+++ b/app/src/main/res/values-si/strings.xml
@@ -90,9 +90,9 @@
බලපත්රය
පෙර මාතෘකාව/විස්තරය භාවිතා කරන්න
රාත්රී ආකාරය
- ඇට්රිබ්යුශන්-ශෙයාඅලයික් 4.0
- ඇට්රිබ්යුශන්-ශෙයාඅලයික් 3.0
- ඇට්රිබ්යුශන්
+ ඇට්රිබ්යුශන්-ශෙයාඅලයික් 4.0
+ ඇට්රිබ්යුශන්-ශෙයාඅලයික් 3.0
+ ඇට්රිබ්යුශන්
CC0
CC BY-SA 3.0
CC BY 3.0
diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml
index cbaca17c5..c0339e261 100644
--- a/app/src/main/res/values-sk/strings.xml
+++ b/app/src/main/res/values-sk/strings.xml
@@ -171,10 +171,10 @@
Predvolená licencia
Použiť predchádzajúci názov a popis
Motív
- Attribution-ShareAlike 4.0
- Attribution 4.0
- Attribution-ShareAlike 3.0
- Attribution 3.0
+ Attribution-ShareAlike 4.0
+ Attribution 4.0
+ Attribution-ShareAlike 3.0
+ Attribution 3.0
CC0
CC BY-SA 3.0
CC BY 3.0
@@ -702,6 +702,7 @@
Wkki miluje monumenty je medzinárodná súťaž fotografií monumentov organizovaná Wikemédiou
Potrebné povolenie
Blízke mapy potrebujú mať prístup k polohe telefónu, aby správne fungovali
+ Voliteľné oprávnenie: Umožniť aplikácii, aby získavala aktuálnu polohu a ponúkala na jej základe navrhované kategórie
Príspevky používateľa: %s
Úspechy používateľa: %s
Zobraziť profil používateľa
@@ -757,7 +758,7 @@
Povolenia sú potrebné pre zabezpečenie funkčnosti
Zistite, ako napísať užitočný popis
Zistite, ako napísať užitočný titulok
- Pozrite si svoje úspechy
+ Pozrite si svoje úspechy
Upraviť obrázok
Upraviť polohu
Poloha aktualizovaná!
diff --git a/app/src/main/res/values-sl/strings.xml b/app/src/main/res/values-sl/strings.xml
index ebcfba109..3cc9d374b 100644
--- a/app/src/main/res/values-sl/strings.xml
+++ b/app/src/main/res/values-sl/strings.xml
@@ -172,10 +172,10 @@
Privzeta licenca
Uporabi prejšnji naslov in opis
Tema
- Priznanje avtorstva-Deljenje pod enakimi pogoji 4.0
- Priznanje avtorstva 4.0
- Priznanje avtorstva-Deljenje pod enakimi pogoji 3.0
- Priznanje avtorstva 3.0
+ Priznanje avtorstva-Deljenje pod enakimi pogoji 4.0
+ Priznanje avtorstva 4.0
+ Priznanje avtorstva-Deljenje pod enakimi pogoji 3.0
+ Priznanje avtorstva 3.0
CC0
CC BY-SA 3.0
CC BY 3.0
@@ -294,7 +294,7 @@
V prihodnosti se boste morali za nalaganje slik prijaviti.
Za uporabo te možnosti se prijavite
Kopiraj vikibesedilo v odložišče
- Vikibesedilo je skopirano v odložišče
+ Vikibesedilo je kopirano v odložišče
Bližnje mogoče ne bo pravilno delovalo. Kraj ni na voljo.
Internet ni na voljo. Prikazani so samo predpomnjeni kraji.
Dostop do lokacije je bil zavrnjen. Za uporabo te možnosti ročno nastavite svojo lokacijo.
@@ -312,7 +312,7 @@
Napaka pri pridobivanju obvestil
Napaka pri pridobivanju slike za pregled. Pritisnite Osveži za ponovni poskus.
Ni najdenih obvestil
- Prevedi
+ Prevodi
Jeziki
Izberite jezike, za katere želite pošiljati prevode
Nadaljuj
@@ -386,13 +386,13 @@
Izbrane slike
Slike iz »Bližnji kraji«
Raven %d
- %s (raven %s)
+ %s (raven %s)
Naložene slike
Nevrnjene slike
Uporabljene slike
Delite svoje dosežke s prijatelji!
Ko boste izpolnili te zahteve, se bo vaša raven zvišala. Predmeti v razdelku »Statistika« ne štejejo.
- Potrebno najmanj:
+ Potrebno najmanj:
Število slik, ki ste jih naložili v Zbirko, ne glede na program za nalaganje
Delež slik v Zbirki, ki niso bile izbrisane
Število slik, ki ste jih naložili v Zbirko in se uporabljajo v člankih Wikipedije
@@ -424,10 +424,10 @@
Dodali niste nobenega zaznamka
Zaznamki
Dnevniško beleženje se je začelo. Prosimo, ZNOVA ZAŽENITE aplikacijo, opravite dejanje, ki ga želite zabeležiti, in nato znova tapnite »Pošlji dnevniški zapis«.
- Naložil sem jo pomotoma
+ Naložil_a sem jo pomotoma
Nisem vedel_a, da bo javno vidna
Spoznal sem, da je to slabo za mojo zasebnost
- Premislil_a sem si, ne želim več biti javno viden_na
+ Premislil_a sem si, ne želim več, da je javno vidna
Ta slika žal ni zanimiva za enciklopedijo.
Sliko sem naložil/a sam/a dne %1$s. Uporablja se v %2$d članku(ih).
Pozdravljeni v Wikimedijini zbirki!\n\nTapnite gumb dodaj in naložite svojo prvo predstavnostno datoteko.
@@ -478,7 +478,7 @@
Zahvala uporabniku_ci %1$s je bila uspešno poslana
Pošiljanje zahvale uporabniku_ci %1$s ni uspelo
Pošiljanje zahvale: neuspešno
- Pošiljam zahvalo uporabniku/ici %1$s
+ Pošiljam zahvalo za %1$s
Ali so tu upoštevane avtorske pravice?
Ali je to pravilno kategorizirano?
Ali to spada v okvir projekta?
@@ -509,7 +509,7 @@
Slike, naložene z Bližnjimi kraji, so slike, ki so naložene z odkrivanjem krajev na zemljevidu.
Ta možnost vam omogoča, da urejevalcem, ki so opravili koristno urejanje, pošljete zahvalo – z uporabo kratke povezave na strani zgodovine ali strani primerjave.
Kopiraj na naslednjo predstavnostno datoteko
- Skopirano
+ Kopirano
Zgledi dobrih slik za nalaganje v Zbirko
Zgledi slik, ki niso primerne za nalaganje
Preskoči to sliko
@@ -664,7 +664,7 @@
Način omejene povezanosti izklopljen. Nalaganje se bo zdaj nadaljevalo.
Način omejene povezanosti
Kakovostne slike
- Kakovostne slike so ponazoritve ali fotografije, ki ustrezajo nekaterim merilom kakovosti (ta so predvsem tehnična) in so dragocene za projekte Wikimedie
+ Kakovostne slike so ilustracije ali fotografije, ki ustrezajo nekaterim merilom kakovosti (ta so predvsem tehnična) in so dragocene za projekte Wikimedie
Nalaganje se nadaljuje ...
Zaustavljam nalaganje ...
Preklicujem nalaganje ...
@@ -690,7 +690,7 @@
Prikaži v apu
Uredi lokacijo
Slikovni prikaz izbirnika lokacije
- Senca slikovnega prikaza izbirnika lokacije
+ Senca slikovnega prikaza izbirnika lokacije
Lokacija slike
Preverite, ali je lokacija pravilna
Oznaka
@@ -718,6 +718,7 @@
Viki obožuje spomenike je mednarodni fotografski natečaj za spomenike, ki ga organizira Fundacija Wikimedia.
Potrebno je dovoljenje
Bližnji zemljevidi morajo za pravilno delovanje prebrati STANJE TELEFONA
+ Neobvezno dovoljenje: pridobitev trenutne lokacije za predlaganje kategorij
Prispevki uporabnika: %s
Dosežki uporabnika: %s
Ogled uporabniškega profila
@@ -761,7 +762,7 @@
Prijava
Nastavitev belega ozadja
Nastavitev črnega ozadja
- Prijavi kršitev
+ Prijava kršitve
Prijavi uporabnika
Prijavi to vsebino
Zahtevaj blokiranje uporabnika
@@ -775,7 +776,7 @@
Za delovanje so potrebna dovoljenja
Naučite se napisati koristen opis
Naučite se napisati koristen napis
- Oglejte si svoje dosežke
+ Oglejte si svoje dosežke
Uredi sliko
Uredi lokacijo
Lokacija posodobljena!
@@ -833,7 +834,7 @@
Zbirka
Drugi vikiji
•
- Uporabe datotek
+ Uporabe datoteke
SingleWebViewActivity
Račun
Izgini račun
diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml
index a41096306..3162625eb 100644
--- a/app/src/main/res/values-sr/strings.xml
+++ b/app/src/main/res/values-sr/strings.xml
@@ -152,6 +152,7 @@
Још увек нисте отпремили ниједну фотографију.
Покушај поново
Откажи
+ Сви језици
Слањем ове слике, ја тврдим да је у питању мој рад, да не садржи материјал или селфије заштићене ауторским правима, те да је на остале начине у складу са <a href=\"https://commons.wikimedia.org/wiki/Commons:Policies_and_guidelines\">смерницама Викимедијине оставе</a>.
Преузми
Подразумевана лиценца
@@ -261,6 +262,7 @@
Процес отпремања захтева активни приступ интернету. Проверите вашу везу са интернетом.
Пронађени су проблеми у слици
Молимо Вас да отпремате слике које сте ви направили. Не отпремајте слике које сте преузели са интернета.
+ Отпремања
Чувај снимке у апликацији
Чување слика снимљених камером у меморији на вашем уређају
Пријава на налог
@@ -664,6 +666,7 @@
Вики воли споменике
Вики воли споменике је међународно такмичење фотографија споменика, које организује Викимедија
Потребна је дозвола
+ Необавезна дозвола: преузми тренутну локацију за предлоге категорија
Доприноси корисника/це: %s
Достигнућа корисника/це: %s
Прикажи корисничку страницу
diff --git a/app/src/main/res/values-su/strings.xml b/app/src/main/res/values-su/strings.xml
index 70317c7a0..fa87bcbeb 100644
--- a/app/src/main/res/values-su/strings.xml
+++ b/app/src/main/res/values-su/strings.xml
@@ -147,10 +147,10 @@
Lisénsi buhun
Paké judul jeung dadaran saméméhna
Téma
- Atribusi-BabagiSarupa 4.0
- Atribusi 4.0
- Atribusi-BabagiSarupa 3.0
- Atribusi 3.0
+ Atribusi-BabagiSarupa 4.0
+ Atribusi 4.0
+ Atribusi-BabagiSarupa 3.0
+ Atribusi 3.0
CC0
CC BY-SA 3.0
CC BY 3.0
@@ -416,4 +416,5 @@
Naha %1$s bet kudu dihapus?
%1$s dimuat ku: %2$s
Basa dadaran baku
+ Idin pilihan: Paké lokasi kiwari pikeun usulan kategori
diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml
index 740304f7c..5d8a7b379 100644
--- a/app/src/main/res/values-sv/strings.xml
+++ b/app/src/main/res/values-sv/strings.xml
@@ -163,10 +163,10 @@
Standardlicens
Använd föregående titel och beskrivning
Tema
- Erkännande-DelaLika 4.0
- Erkännande 4.0
- Erkännande-DelaLika 3.0
- Erkännande 3.0
+ Erkännande-DelaLika 4.0
+ Erkännande 4.0
+ Erkännande-DelaLika 3.0
+ Erkännande 3.0
CC0
CC BY-SA 3.0
CC BY 3.0
@@ -219,6 +219,7 @@
Beskrivning
Diskussion
Skapare
+ Uppladdare
Uppladdningsdatum
Licens
Koordinater
@@ -377,7 +378,7 @@
Utvalda bilder
Bilder via \"Platser i närheten\"
Nivå %d
- %s (Nivå %s)
+ %s (Nivå %s)
Uppladdade bilder
Bilder som inte har återställts
Bilder som används
@@ -420,7 +421,7 @@
Jag insåg att den är dålig för mitt privatliv
Jag ändrade mig, jag vill inte längre att den ska vara synlig offentligt
Tyvärr, denna bild är inte intressant för en encyklopedi
- Laddades upp av mig den %1$s och används i %2$d artiklar.
+ Laddades upp av mig den %1$s och används i minst %2$d artiklar.
Välkommen till Commons!\n\nLadda upp din första mediafil genom att trycka på knappen för att lägga till.
Inga kategorier har valts
Bilder utan kategorier används sällan. Är du säker på att du vill fortsätta utan att välja kategorier?
@@ -585,6 +586,8 @@
MEDIA
UNDERORDNADE KLASSER
ÖVERORDNADE KLASSER
+ UNDERKATEGORIER
+ ÖVERORDNADE KATEGORIER
Hittade platser i närheten
Föreställer de här bilderna %1$s?
Är detta en bild på %1$s?
@@ -703,6 +706,7 @@
Wiki Loves Monuments är en internationell fototävling för monument som organiseras av Wikimedia
Behöver behörighet
Kartor i närheten behöver läsa TELEFONENS TILLSTÅND för att fungera ordentligt
+ Valfri behörighet: Hämta aktuell plats för kategoriförslag
Bidrag av användare: %s
Prestationer av användare: %s
Visa användarprofil
@@ -829,4 +833,5 @@
Visa i \"I närheten\"
Skapades och laddades upp av: %1$s
Skapad av %1$s och laddades upp av %2$s
+ Nominerad för radering
diff --git a/app/src/main/res/values-tcy/strings.xml b/app/src/main/res/values-tcy/strings.xml
index f3eb3562f..37f0ffa3b 100644
--- a/app/src/main/res/values-tcy/strings.xml
+++ b/app/src/main/res/values-tcy/strings.xml
@@ -119,10 +119,10 @@
ಪೂರ್ವನಿಯೋಜಿತ ಲೈಸನ್ಸ್.
ದುಂಬುದ ಪುದರ್ ಬಕ್ಕ ವಿವರಣೆನ್ ಕೊರ್ಲೆ.
ಕತ್ತಲೆದ ಕ್ರಮೊ
- ಅಸ್ಟ್ರೀಬುಷನ್ - ಸೇರ್ ಎ ಲೈಕ್ 4.0.
- Attribution 4.0
- Attribution-ShareAlike 3.0
- Attribution 3.0
+ ಅಸ್ಟ್ರೀಬುಷನ್ - ಸೇರ್ ಎ ಲೈಕ್ 4.0.
+ Attribution 4.0
+ Attribution-ShareAlike 3.0
+ Attribution 3.0
CC0
CC BY-SA 3.0
CC BY 3.0
@@ -227,7 +227,7 @@
ಜಾಗೆತ ಗುರುತು ಇಜ್ಜಿ.
ಮುಟ್ಟತ ಜಾಗೆತ ಪಟ್ಟಿನ್ ತೋಜಾಯರ ಅನುಮತಿ ಅಗತ್ಯವಾತ್ ಬೋಡು.
ನಿರ್ದೆಶನೊ
- ವಿಕಿಡಾಟ
+ ವಿಕಿಡೇಟಾ
ವಿಕಿಪೀಡಿಯ
ಕಾಮನ್ಸ್
<u>Rate us</u>
@@ -345,4 +345,5 @@
ನನಾತ್ ಓದುಲೇ
ಮಾಂತಾ ಬಾಸೆಲೆಡ್
ಆಂಡ್
+ ಐಚ್ಛಿಕ ಅನುಮತಿ: ವರ್ಗೊ ಸಲಹೆಗ್ ಇತ್ತೆತ ಸ್ಥಳೊನ್ ಪಡೆಲೆ.
diff --git a/app/src/main/res/values-te/strings.xml b/app/src/main/res/values-te/strings.xml
index 0d4b323f4..09b167400 100644
--- a/app/src/main/res/values-te/strings.xml
+++ b/app/src/main/res/values-te/strings.xml
@@ -137,10 +137,10 @@
అప్రమేయ లైసెన్సు
మునుపటి శీర్షిక, వివరణను వాడు
అలంకారం
- Attribution-ShareAlike 4.0
- Attribution 4.0
- Attribution-ShareAlike 3.0
- Attribution 3.0
+ Attribution-ShareAlike 4.0
+ Attribution 4.0
+ Attribution-ShareAlike 3.0
+ Attribution 3.0
CC0
CC BY-SA 3.0
CC BY 3.0
@@ -609,6 +609,7 @@
ఈ బొమ్మను ఈసరికే కామన్స్ లోకి ఎక్కించారు.
మరింత తెలుసుకోండి
అనుమతి కావాలి
+ ఐచ్ఛిక అనుమతి: వర్గాల సూచనల కోసం ప్రస్తుత స్థలాన్ని తెచ్చుకో
వాడుకరి తోడ్పాట్లు: %s
వాడుకరి సాధించినవి: %s
వాడుకరి పేజీ చూడండి
diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml
index dde54e41f..d39ddc0fa 100644
--- a/app/src/main/res/values-th/strings.xml
+++ b/app/src/main/res/values-th/strings.xml
@@ -147,10 +147,10 @@
สัญญาอนุญาตปริยาย
ใช้ชื่อเรื่องและคำอธิบายก่อนหน้านี้
ธีม
- Attribution-ShareAlike 4.0
- Attribution 4.0
- Attribution-ShareAlike 3.0
- Attribution 3.0
+ Attribution-ShareAlike 4.0
+ Attribution 4.0
+ Attribution-ShareAlike 3.0
+ Attribution 3.0
CC0
CC BY-SA 3.0
CC BY 3.0
@@ -428,6 +428,7 @@
สถานที่ใกล้เคียง
กำลังยกเลิกอับโหลด…
ยกเลิกอับโหลด
+ สิทธิทางเลือก: รับข้อมูลตำแหน่งที่ตั้งปัจจุบันสำหรับข้อเสนอแนะหมวดหมู่
API level
แอนดรอยด์เวอร์ชัน
รุ่นอุปกรณ์
diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml
index c741e89c4..f88181107 100644
--- a/app/src/main/res/values-tr/strings.xml
+++ b/app/src/main/res/values-tr/strings.xml
@@ -185,10 +185,10 @@
Varsayılan Lisans
Önceki başlığı ve açıklamayı kullan
Tema
- Atıf-BenzerPaylaşım 4.0
- Atıf 4.0
- Atıf-BenzerPaylaşım 3.0
- Atıf 3.0
+ Atıf-BenzerPaylaşım 4.0
+ Atıf 4.0
+ Atıf-BenzerPaylaşım 3.0
+ Atıf 3.0
CC0
CC BY-SA 3.0
CC BY 3.0
@@ -241,6 +241,7 @@
Açıklama
Tartışma
Yazar
+ Yükleyici
Yükleme tarihi
Lisans
Koordinatlar
@@ -399,7 +400,7 @@
Seçkin Resimler
\"Yakındaki Yerler\"den Resimler
Seviye %d
- %s (Seviye %s)
+ %s (Seviye %s)
Resimler Yüklendi
Resimler Geri Alınmadı
Resimler Kullanıldı
@@ -442,7 +443,7 @@
Gizliliğimin kötü olduğunu fark ettim
Fikrimi değiştirdim, artık herkese görünür olmasını istemiyorum
Üzgünüz, bu resim bir ansiklopedi için ilginç değil
- Kendi başıma %1$s üzerine yüklendi, %2$d makalede kullanıldı.
+ %1$s tarafımdan yüklendi, en az %2$d madde de kullanıldı.
Commons\'a Hoş Geldiniz!\n\nEkle düğmesine dokunarak ilk medyanızı yükleyin.
Kategori Seçilmedi
Kategorisiz görüntüler nadiren kullanılabilir. Kategori seçmeden devam etmek istediğinizden emin misiniz?
@@ -607,6 +608,8 @@
MEDYA
ALT SINIFLAR
ÜST SINIFLAR
+ ALT KATEGORİLER
+ ÜST KATEGORİLER
Yakındaki Yer Bulundu
Bunlar %1$s resimleri mi?
Bu bir %1$s resmi mi?
@@ -725,6 +728,7 @@
Viki, Anıtları Seviyor; Wikimedia tarafından düzenlenen anıtlar için uluslararası bir fotoğraf yarışmasıdır.
İzin Gerekiyor
Yakındaki haritalar düzgün çalışmak için TELEFON DURUMUNU okumaya ihtiyaç duyuyor
+ İsteğe bağlı izin: Kategori önerileri için geçerli konumu al
Kullanıcının katkıları: %s
Kullanıcının başarıları: %s
Kullanıcı profilini görüntüle
@@ -782,7 +786,7 @@
İşlevsellik için izinler gereklidir
Yararlı bir açıklamanın nasıl yazılacağını öğrenin
Nasıl faydalı bir alt yazı yazılacağını öğrenin
- Başarılarınızı görün
+ Başarılarınızı görüntüleyin
Görseli düzenle
Konumu Düzenle
Konum güncellendi!
@@ -851,4 +855,5 @@
Yakınlarda Göster
Oluşturan ve yükleyen: %1$s
%1$s tarafından oluşturuldu ve %2$s tarafından yüklendi
+ Silinmeye aday gösterildi
diff --git a/app/src/main/res/values-ug/strings.xml b/app/src/main/res/values-ug/strings.xml
index 2f189f48b..c03c85999 100644
--- a/app/src/main/res/values-ug/strings.xml
+++ b/app/src/main/res/values-ug/strings.xml
@@ -75,8 +75,8 @@
ۋاز كەچ
چۈشۈرۈڭ
ئىجازەتنامە
- ئوخشاش ئىمزالىق ئورتاق ھەمبەھىرلىنىش
- ئىمزا قويۇش
+ ئوخشاش ئىمزالىق ئورتاق ھەمبەھىرلىنىش
+ ئىمزا قويۇش
CC0
CC BY-SA 3.0
CC BY 3.0
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index 3ea969dab..3c51cde30 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -187,10 +187,10 @@
Усталена ліцензія
Використати попередні назву й опис
Тема
- Attribution-ShareAlike 4.0
- Attribution 4.0
- Attribution-ShareAlike 3.0
- CC Attribution 3.0
+ Attribution-ShareAlike 4.0
+ Attribution 4.0
+ Attribution-ShareAlike 3.0
+ CC Attribution 3.0
CC0
CC BY-SA 3.0
CC BY 3.0
@@ -243,6 +243,7 @@
Опис
Обговорення
Автор
+ Завантажувач
Дата завантаження
Ліцензія
Координати
@@ -401,7 +402,7 @@
Вибрані зображення
Зображення місць поблизу
Рівень %d
- %s (Рівень %s)
+ %s (Рівень %s)
Завантажені зображення
Не відхилені зображення
Використані зображення
@@ -735,6 +736,7 @@
Wiki Loves Monuments — це міжнародний фотоконкурс пам’яток, організований Вікімедією
Потрібен дозвіл
Для належної роботи мапи поблизу мають відображати стан PHONE STATE
+ Додатковий дозвіл: отримувати поточне розташування для підказок категорій
Внесок користувача: %s
Досягнення користувача: %s
Переглянути профіль користувача
diff --git a/app/src/main/res/values-ur/strings.xml b/app/src/main/res/values-ur/strings.xml
index a9c484405..c8898a839 100644
--- a/app/src/main/res/values-ur/strings.xml
+++ b/app/src/main/res/values-ur/strings.xml
@@ -101,10 +101,10 @@
اجازت نامہ
گزشتہ عنوان/وضاحت استعمال کریں
نائٹ موڈ
- انتباہ-شراکت 4.0
- انتباہ 4.0
- انتباہ-شراکت 3.0
- انتباہ 3.0
+ انتباہ-شراکت 4.0
+ انتباہ 4.0
+ انتباہ-شراکت 3.0
+ انتباہ 3.0
CC0
ہاں!
زمرہ جات
diff --git a/app/src/main/res/values-uz/strings.xml b/app/src/main/res/values-uz/strings.xml
index a6f19b60a..4e0e698f2 100644
--- a/app/src/main/res/values-uz/strings.xml
+++ b/app/src/main/res/values-uz/strings.xml
@@ -155,10 +155,10 @@
Standart litsenziya
Oldingi sarlavha va tavsifdan foydalanish
Mavzu
- Attribution-ShareAlike 4.0
- Attribution 4.0
- Attribution-ShareAlike 3.0
- Attribution 3.0
+ Attribution-ShareAlike 4.0
+ Attribution 4.0
+ Attribution-ShareAlike 3.0
+ Attribution 3.0
Wikimedia Commons Vikipediyada qoʻllanilib kelinadigan koʻpgina rasmlarni joylashtiradi.
Rasmlarining butun dunyo boʻylab odamlarni maʼlumot olishga yordam beradi!
Marhamat, oʻzingiz tomonidan olingan yoki tuzilgan rasmlarni joylashtiring:
diff --git a/app/src/main/res/values-vec/strings.xml b/app/src/main/res/values-vec/strings.xml
index 17765b3ce..f62e9e7c7 100644
--- a/app/src/main/res/values-vec/strings.xml
+++ b/app/src/main/res/values-vec/strings.xml
@@ -123,10 +123,10 @@
Liçensa predefinìa
Dopara titoło e descrision preçedente
Tema
- Atribusion-Spartisi n\'te ła stesa manjiera 4.0
- Atribusion 4.0
- Atribusion-Spartisi n\'te ła stesa manjiera 3.0
- Atribusion 3.0
+ Atribusion-Spartisi n\'te ła stesa manjiera 4.0
+ Atribusion 4.0
+ Atribusion-Spartisi n\'te ła stesa manjiera 3.0
+ Atribusion 3.0
la Wikimedia Commons ła ospita ła major parte de łe imajini che njien doparae so Wikipedia
Le to imajini łe juta l\'istrusion de łe persone in tuto el mondo!
Se ve prega de cargar łe imajini che njien tolte o creae interamente da sołi
@@ -472,4 +472,5 @@
Ste do foto łe gheto fate in tel steso posto? Vuto doparar ła stesa latitudine/lonxitudine de l\'imajine a drita?
Carga \'ncora
No go catà nisun posto, pròa a canbiar la to riserca.
+ Autorixasion opsionałe: Dopara ła poxision corente par i sujerimenti de categoria
diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml
index 635d71a3f..8f07619b9 100644
--- a/app/src/main/res/values-vi/strings.xml
+++ b/app/src/main/res/values-vi/strings.xml
@@ -1,5 +1,6 @@
同享壞咗
哎呀。出咗錯!
+ 話畀我哋知你做緊啲咩,然後透過電郵分享畀我哋,我哋會幫我哋解決問題!
多謝你!
diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml
index d7c023e6f..284e9fde0 100644
--- a/app/src/main/res/values-zh-rHK/strings.xml
+++ b/app/src/main/res/values-zh-rHK/strings.xml
@@ -131,10 +131,10 @@
預設授權條款
使用先前標題及描述
主題
- 姓名標示-相同方式分享4.0
- 姓名標示4.0
- 姓名標示-相同方式分享3.0
- 姓名標示3.0
+ 姓名標示-相同方式分享4.0
+ 姓名標示4.0
+ 姓名標示-相同方式分享3.0
+ 姓名標示3.0
維基共享資源管理大部份使用在維基百科的圖片。
您的圖片有助教育世人!
請上傳完全由您自己拍攝或創作的圖片:
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
index aa5ac47d6..c50f0b64e 100644
--- a/app/src/main/res/values-zh-rTW/strings.xml
+++ b/app/src/main/res/values-zh-rTW/strings.xml
@@ -174,6 +174,9 @@
您尚未上傳過任何照片。
重試
取消
+ 類型 語言 名稱
+ 近期搜尋
+ 所有語言
透過提交此圖片,我宣佈這是我個人創作的成品,且不包含受版權保護或自拍內容,並除此之外遵守<a href=\"https://commons.wikimedia.org/wiki/Commons:Policies_and_guidelines\">維基媒體共享資源方針</a>。
下載
預設授權條款
@@ -181,7 +184,7 @@
主題
姓名標示-相同方式分享4.0
姓名標示4.0
- 姓名標示-相同方式分享3.0
+ 姓名標示-相同方式分享3.0
姓名標示3.0
公眾領域貢獻宣告
CC BY-SA 3.0
@@ -235,6 +238,7 @@
描述
討論
作者
+ 上傳者
上傳日期
授權協議
座標
@@ -285,6 +289,7 @@
上傳過程需要有效的網際網路存取。請檢查您的網路連線。
在圖片中發現的問題
請僅上傳您自己拍攝的圖片。不要上傳您從網路下載來的圖片。
+ 上傳
儲存內建拍攝截圖
將使用內建相機拍攝的照片保存到您的設備存儲中
登入您的帳號
@@ -356,6 +361,7 @@
測驗
這張圖片可以上傳嗎?
問題
+ 我不確定
結果
如果您持續上傳需要刪除的圖片,您的帳號可能會被封禁。你確定要結束測驗嗎?
您上傳的圖片已有%1$s遭到刪除。如果您持續上傳需要刪除的圖片,您的帳號可能會遭到封鎖。\n\n您要不要重新閱覽教學,然後再做一次測驗,以幫助您分辨哪些類型的圖片可以上傳、哪些不可以?
@@ -365,6 +371,7 @@
收錄高畫質圖像是維基共享資源的目標之一,所以不應該上傳模糊的圖片。儘量試著只上傳光線良好的優良圖片。
在維基共享資源上,展現科技或文化的圖片很受到歡迎。
您答對了 %1$s 題,恭喜!
+ 繼續
請在兩個選項中擇一,來回答這個問題。
登入時限已過,請重新登入。
向朋友分享您的測驗結果!
@@ -393,7 +400,8 @@
特色圖片
圖片來自“附近的地方”
等級 %d
- %s(%s級)
+ %s(%s級)
+ %s(%s)
已上傳的圖片
沒有被還原回復的圖片
有被使用到的圖片
@@ -458,6 +466,8 @@
結束於:
顯示活動
檢視發生中的活動
+ 顯示刪除按鈕
+ 在自訂點選器中啟用「刪除資料夾」按鈕
允許讓應用程式索取位置,以防相機未記錄到位置。某些設備的相機不會記錄位置,在這種情況下,讓應用程式索取位置,並將其附加到圖片裡,能讓您的貢獻更有用。您可以隨時從「設定」中更改
允許
關閉
@@ -535,6 +545,7 @@
從您的手機上直接上傳照片到維基共享資源,立即下載共享資源應用程式:%1$s
分享應用程式透過…
圖片資訊
+ 不要再顯示此訊息
找不到分類
沒有找到描述
已取消上傳
@@ -723,6 +734,8 @@
維基愛古蹟是由維基媒體舉行的國際古蹟攝影比賽
需要權限
附近地圖需要讀取手機狀態來正常運作
+ 請開啟定位服務,以查看附近的地點。
+ 可有可無的權限:獲取目前的地理位置,以用於分類建議
使用者貢獻:%s
使用者成果:%s
檢視使用者個人檔案
diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml
index 8244bdc30..cab7ff146 100644
--- a/app/src/main/res/values-zh/strings.xml
+++ b/app/src/main/res/values-zh/strings.xml
@@ -208,6 +208,9 @@
您还没有上传任何照片。
重试
取消
+ 类型 语言 名称
+ 近期搜索
+ 所有语言
通过提交该图片,我声明这是我自己的作品,其不包含受版权保护的材料或自拍像,并遵循<a href=\"https://commons.wikimedia.org/wiki/Commons:Policies_and_guidelines\">维基共享资源</a>方针。
下载
默认许可协议
@@ -269,6 +272,7 @@
描述
讨论
作者
+ 上传者
上传日期
许可协议
坐标
@@ -319,6 +323,7 @@
上传过程需要有效的互联网访问。请检查您的网络连接。
在图片中发现的问题
请仅上传由您自己拍摄的图像。请勿上传您从互联网下载的图像。
+ 上传
保存应用程序内截图
将您设备内部照相机应用拍摄的照片保存至您的设备存储中
登录您的账户
@@ -427,7 +432,8 @@
特色图片
来自“附近地点”的图片
等级%d
- %s(%s級)
+ %s(%s級)
+ %s(%s)
已上传图片
未还原图片
使用过的图片
@@ -492,6 +498,8 @@
结束时间:
显示活动
显示正在进行的活动
+ 显示删除按钮
+ 在自定义选择器中启用“删除文件夹”按钮
允许应用获取位置,以防相机未记录位置。某些设备的相机不记录位置。在这种情况下,让应用获取位置并将其附加到照片可以让您的贡献更有用。您可以随时从“设置”中更改此设置
允许
拒绝
@@ -635,6 +643,8 @@
媒体
子类别
父类别
+ 子分类
+ 父级分类
找到附近地点
这些是%1$s的图片吗?
这是%1$s的图片吗?
@@ -754,6 +764,8 @@
维基爱古迹是由维基媒体举行的国际古迹摄影比赛
需要权限
附近地图需要读取手机状态权限以正常运行
+ 请打开定位服务,以查看附近的地点。
+ 可选权限:获取当前位置以提供分类建议
用户贡献:%s
用户成就:%s
查看用户个人资料
@@ -811,7 +823,7 @@
功能需要权限
了解如何写出有用的描述
了解如何写出有用的注释
- 查看您的成果
+ 查看您的成就
编辑图片
编辑位置
位置已更新!
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index cd51dde76..5a3634277 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -7,6 +7,9 @@
8dp
12dp
12dp
+ 8dp
+ 16dp
+ 24dp
30dp
diff --git a/app/src/main/res/values/ids.xml b/app/src/main/res/values/ids.xml
new file mode 100644
index 000000000..83eb08801
--- /dev/null
+++ b/app/src/main/res/values/ids.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index c93e4b81e..404689063 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -2,7 +2,7 @@
Commons Facebook Page
- Commons Github Source Code
+ Commons GitHub Source Code
Commons Logo
Commons Website
Exit location picker
@@ -35,14 +35,14 @@
- (%1$d)
- (%1$d)
- Starting Uploads
+ Starting Uploads
- - Processing %d upload
- - Processing %d uploads
+ - Processing %1$d upload
+ - Processing %1$d uploads
- - %d upload
- - %d uploads
+ - %1$d upload
+ - %1$d uploads
- This image will be licensed under %1$s
@@ -86,7 +86,7 @@
Upload queued (limited connection mode enabled)
%1$s uploaded!
Tap to view your upload
- Uploading file: %s
+ Uploading file: %1$s
%1$s uploading
Finishing uploading %1$s
Failed to upload %1$s
@@ -136,6 +136,7 @@
Settings
Sign Up
Featured Images
+ Featured images are contributions by highly skilled photographers and illustrators that the Wikimedia Commons community has chosen as some of the highest quality on the site.
Custom Selector
Category
Peer Review
@@ -153,15 +154,18 @@
You have not yet uploaded any photos.
Retry
Cancel
+ Type Language Name
+ Recent Searches
+ All Languages
By submitting this picture, I declare that this is my own work, that it does not contain copyrighted material or selfies, and otherwise adheres to <a href=\"https://commons.wikimedia.org/wiki/Commons:Policies_and_guidelines\">Wikimedia Commons policies</a>.
Download
Default License
Use previous title and description
Theme
- Attribution-ShareAlike 4.0
- Attribution 4.0
- Attribution-ShareAlike 3.0
- Attribution 3.0
+ Attribution-ShareAlike 4.0
+ Attribution 4.0
+ Attribution-ShareAlike 3.0
+ Attribution 3.0
CC0
CC BY-SA 3.0
CC BY 3.0
@@ -232,8 +236,8 @@
Llamas
Rainbow Bridge
Tulip
- Welcome Wikipedia
- Welcome Copyright
+ A schematic image of a generic Wikipedia article.
+ A drawing of a stack of papers with the copyright symbol crossed out.
Sydney Opera House
Cancel
Open
@@ -269,6 +273,7 @@
Problems found in image
Please only upload pictures that you have taken by yourself. Don\'t upload pictures that you have downloaded from the Internet.
+ Uploads
Save In-app shots
Save pictures taken with the in-app camera to your device storage
Log in to your account
@@ -347,6 +352,7 @@
Quiz
Is this picture OK to upload?
Question
+ I am not sure
Result
If you carry on uploading images that require deletion, your account will likely be banned. Are you sure you want to end the quiz?
More than %1$s of the images you uploaded have been deleted. If you carry on uploading images that require deletion, your account will likely be banned.\n\nWould you like to view the tutorial again and then take a quiz to help you learn what type of images you should or shouldn\'t upload?
@@ -356,6 +362,7 @@
One of the goals of Commons is to gather quality images. Therefore, blurry images shouldn\'t be uploaded. Always try to take nice pictures with good lighting.
Pictures showing technology or culture are very welcome on Commons.
You got %1$s of the answers correct. Congratulations!
+ Continue
Select one of the two options to answer the question
Log-in expired. Please log in again.
Share your quiz with your friends!
@@ -381,12 +388,12 @@
Achievements
Profile
Badges
- Statistics
Thanks Received
Featured Images
Images via \"Nearby Places\"
- Level %d
- %s (Level %s)
+ Level %1$d
+ %1$s (Level %2$s)
+ %1$s (%2$s)
Images Uploaded
Images Not Reverted
Images Used
@@ -458,11 +465,13 @@ Upload your first media by tapping on the add button.
Never ask this again
Ask for location permission
Ask for location permission when needed for nearby notification card view feature.
- Something went wrong, We could not fetch achievements
+ Something went wrong, and we could not fetch achievements
You\'ve made so many contributions our achievements calculation system can\'t cope. This is the ultimate achievement.
Ends on:
Display campaigns
See the ongoing campaigns
+ Show deletion button
+ Enable the \"Delete Folder\" button in the custom picker
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
@@ -487,12 +496,12 @@ Upload your first media by tapping on the add button.
Could not request category check for %1$s
Requesting category check for %1$s
Done
- Sending Thanks: Success
- Successfully sent thanks to %1$s
- Failed to send thanks %1$s
- Sending Thanks: Failure
+ Sending thanks: Success
+ Sent thanks to %1$s
+ Failed to send thanks to %1$s
+ Sending thanks: Failure
- Sending Thanks for %1$s
+ Sending thanks for %1$s
Does this follow the rules of copyright?
Is this correctly categorized?
Is this in-scope?
@@ -524,15 +533,14 @@ Upload your first media by tapping on the add button.
Error occurred while picking images
Please wait…
- Featured pictures are images from highly skilled photographers and illustrators that the Wikimedia Commons community has chosen as some of the highest quality on the site.
Images Uploaded via Nearby places are the images which are uploaded by discovering places on the map.
This feature allows editors to send a Thank you notification to users who make useful edits – by using a small thank link on the history page or diff page.
- Copy to subsequent media
+ Copy to the next items
Copied
Examples of good images to upload to Commons
Examples of images not to upload
Skip this image
- Download Failed!!. We cannot download the file without external storage permission.
+ Download failed. We cannot download the file without external storage permission.
Manage EXIF Tags
Select which EXIF tags to keep in uploads
@@ -545,12 +553,10 @@ Upload your first media by tapping on the add button.
Serial Numbers
Software
- Media location access denied
- We may not be able to automatically obtain location data from pictures you upload. Please add the appropriate location for each picture before submitting
-
Upload photos to Wikimedia Commons directly from your phone. Download the Commons App now: %1$s
Share app via...
Image Info
+ Don\'t show this message again
No Categories found
No Depictions found
Cancelled Upload
@@ -607,7 +613,7 @@ Upload your first media by tapping on the add button.
Share image via
You haven\'t made any contributions yet
- %s has not made any contributions yet
+ %1$s has not made any contributions yet
Account created!
Text copied to clipboard
Notification marked as read
@@ -616,7 +622,7 @@ Upload your first media by tapping on the add button.
Exists
Needs Photo
Place type:
- Bridge, museum, hotel etc.
+ Bridge, museum, hotel, etc.
Something went wrong with log-in. You must reset your password!
MEDIA
CHILD CLASSES
@@ -664,7 +670,7 @@ Upload your first media by tapping on the add button.
5. Paste the wikitext in the appropriate place.
6. Edit the wikitext for appropriate positioning, if necessary. For more information, see <a href="https://en.wikipedia.org/wiki/Wikipedia:Manual_of_Style/Images#How_to_place_an_image">here</a>.
7. Publish the article
- Copy wikicode to clipboard
+ Copy wikitext to clipboard
pause
resume
Paused
@@ -707,6 +713,8 @@ Upload your first media by tapping on the add button.
Please select the appropriate categories. Unlike depictions, categories are only in English.
Commons makes your pictures reusable and adapted by everyone. Do you want to waive all rights? Do you want to be attributed? Do you want adaptations to use the same license?
Depicts
+ Label
+ Description
Media License
Media Details
View category page
@@ -721,8 +729,7 @@ Upload your first media by tapping on the add button.
Show in map app
Edit location
The image view of the location picker
-
- The shadow of the image view of the location picker
+ The shadow of the image view of the location picker
Image Location
Check whether location is correct
Label
@@ -734,7 +741,7 @@ Upload your first media by tapping on the add button.
Back
Welcome to Custom Picture Selector
This picker shows you which pictures you have already uploaded to Commons.
- Unlike the picture on the left, the picture on the right has the Commons logo indicating it is already uploaded. \n Touch and hold for image preview.
+ Unlike the picture on the left, the picture on the right has the Commons logo indicating it is already uploaded.\n\nTouch and hold for image preview.
Awesome
This image has already been uploaded to Commons.
For technical reasons, the app can\'t reliably upload more than %1$d pictures at once. The upload limit of %1$d has been exceeded by %2$d.
@@ -750,9 +757,11 @@ Upload your first media by tapping on the add button.
Wiki Loves Monuments is an international photo contest for monuments organised by Wikimedia
Need Permission
Nearby maps need to read PHONE STATE to function properly
+ Please turn on location services to view nearby places.
+ Location access is needed to show nearby places on the map.
- Contributions of User: %s
- Achievements of User: %s
+ Contributions of User: %1$s
+ Achievements of User: %1$s
View user profile
Edit depictions
Edit categories
@@ -763,7 +772,7 @@ Upload your first media by tapping on the add button.
Location data helps Wiki editors find your picture, making it much more useful.\nYour recent uploads have no location.\nWe suggest you turn on location in your camera app\'s settings.\nThank you for uploading!
No location found
How about adding the place where this image was taken?\nLocation data helps Wiki editors find your picture, making it much more useful.\nThank you!
- Add location
+ Add Location
Please remove from this email any information that you are not comfortable sharing publicly. Also, please be aware that your email address with which you are posting, and the associated name and profile picture, will be visible publicly.
Details
Achievements are only available in the prod flavor. Please check the developer documentation.
@@ -784,8 +793,8 @@ Upload your first media by tapping on the add button.
Unmark as not for upload
Marking as not for upload
Unmarking as not for upload
- Show already actioned pictures
- Hiding already actioned pictures
+ Show already handled pictures
+ Hiding already handled pictures
No more images found
This image is already uploaded
Can not select this image for upload
@@ -808,7 +817,7 @@ Upload your first media by tapping on the add button.
Permissions are required for functionality
Learn how to write a useful description
Learn how to write a useful caption
- See your achievements
+ View your achievements
Edit Image
Edit Location
Location updated!
@@ -821,15 +830,15 @@ Upload your first media by tapping on the add button.
Your log-in has expired. Please log in again.
No application available to open GPX files
File Saved Successfully
- Do you want to open GPX file?
- Do you want to open KML file?
- Failed to save KML file.
- Failed to save GPX file.
- Saving KML File
- Saving GPX File
+ Do you want to open the GPX file?
+ Do you want to open the KML file?
+ Failed to save the KML file.
+ Failed to save the GPX file.
+ Saving as a KML file...
+ Saving as a GPX file...
- - %d image selected
- - %d images selected
+ - %1$d image selected
+ - %1$d images selected
Please remember that all images in a multi-upload get the same categories and depictions. If the images do not share depictions and categories, please perform several separate uploads.
Note about multi-uploads
@@ -869,7 +878,6 @@ Upload your first media by tapping on the add button.
Other wikis
•
File usages
- SingleWebViewActivity
Account
Vanish Account
Vanish account warning
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 02c314a4a..cdc8ce387 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -123,6 +123,9 @@
- @drawable/ic_arrow_back_black
- false
- false
+
+ - @android:color/transparent
+ - @android:color/transparent