diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 7a1e7c030..958c13fda 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -89,7 +89,7 @@ jobs: run: bash ./gradlew assembleBetaDebug --stacktrace - name: Upload betaDebug APK - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: betaDebugAPK path: app/build/outputs/apk/beta/debug/app-*.apk @@ -98,7 +98,7 @@ jobs: run: bash ./gradlew assembleProdDebug --stacktrace - name: Upload prodDebug APK - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: prodDebugAPK path: app/build/outputs/apk/prod/debug/app-*.apk diff --git a/.gitignore b/.gitignore index e54ea2551..7fa4767a7 100644 --- a/.gitignore +++ b/.gitignore @@ -46,4 +46,5 @@ captures/* # Test and other output app/jacoco.exec -app/CommonsContributions \ No newline at end of file +app/CommonsContributions +app/.* diff --git a/app/.attach_pid781771 b/app/.attach_pid781771 new file mode 100644 index 000000000..e69de29bb diff --git a/app/build.gradle b/app/build.gradle index 1cc594e7b..2bde0d4f1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -314,6 +314,7 @@ android { buildConfigField "String", "COMMONS_URL", "\"https://commons.wikimedia.org\"" buildConfigField "String", "WIKIDATA_URL", "\"https://www.wikidata.org\"" buildConfigField "String", "MOBILE_HOME_URL", "\"https://commons.m.wikimedia.org/wiki/\"" + buildConfigField "String", "MOBILE_META_URL", "\"https://meta.m.wikimedia.org/wiki/\"" buildConfigField "String", "SIGNUP_LANDING_URL", "\"https://commons.m.wikimedia.org/w/index.php?title=Special:CreateAccount&returnto=Main+Page&returntoquery=welcome%3Dyes\"" buildConfigField "String", "SIGNUP_SUCCESS_REDIRECTION_URL", "\"https://commons.m.wikimedia.org/w/index.php?title=Main_Page&welcome=yes\"" buildConfigField "String", "FORGOT_PASSWORD_URL", "\"https://commons.wikimedia.org/wiki/Special:PasswordReset\"" @@ -350,6 +351,7 @@ android { buildConfigField "String", "COMMONS_URL", "\"https://commons.wikimedia.beta.wmflabs.org\"" buildConfigField "String", "WIKIDATA_URL", "\"https://www.wikidata.org\"" buildConfigField "String", "MOBILE_HOME_URL", "\"https://commons.m.wikimedia.beta.wmflabs.org/wiki/\"" + buildConfigField "String", "MOBILE_META_URL", "\"https://meta.m.wikimedia.beta.wmflabs.org/wiki/\"" buildConfigField "String", "SIGNUP_LANDING_URL", "\"https://commons.m.wikimedia.beta.wmflabs.org/w/index.php?title=Special:CreateAccount&returnto=Main+Page&returntoquery=welcome%3Dyes\"" buildConfigField "String", "SIGNUP_SUCCESS_REDIRECTION_URL", "\"https://commons.m.wikimedia.beta.wmflabs.org/w/index.php?title=Main_Page&welcome=yes\"" buildConfigField "String", "FORGOT_PASSWORD_URL", "\"https://commons.wikimedia.beta.wmflabs.org/wiki/Special:PasswordReset\"" diff --git a/app/src/main/java/fr/free/nrw/commons/AboutActivity.java b/app/src/main/java/fr/free/nrw/commons/AboutActivity.java deleted file mode 100644 index dcc9bfd43..000000000 --- a/app/src/main/java/fr/free/nrw/commons/AboutActivity.java +++ /dev/null @@ -1,187 +0,0 @@ -package fr.free.nrw.commons; - -import android.annotation.SuppressLint; -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 android.widget.ArrayAdapter; -import android.widget.LinearLayout; -import android.widget.Spinner; -import androidx.annotation.NonNull; -import fr.free.nrw.commons.databinding.ActivityAboutBinding; -import fr.free.nrw.commons.theme.BaseActivity; -import fr.free.nrw.commons.utils.ConfigUtils; -import fr.free.nrw.commons.utils.DialogUtil; -import java.util.Collections; -import java.util.List; - -/** - * Represents about screen of this app - */ -public class AboutActivity extends BaseActivity { - - /* - This View Binding class is auto-generated for each xml file. The format is usually the name - of the file with PascalCasing (The underscore characters will be ignored). - More information is available at https://developer.android.com/topic/libraries/view-binding - */ - private ActivityAboutBinding binding; - - /** - * This method helps in the creation About screen - * - * @param savedInstanceState Data bundle - */ - @Override - @SuppressLint("StringFormatInvalid") - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - /* - Instead of just setting the view with the xml file. We need to use View Binding class. - */ - binding = ActivityAboutBinding.inflate(getLayoutInflater()); - final View view = binding.getRoot(); - setContentView(view); - - setSupportActionBar(binding.toolbarBinding.toolbar); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - final String aboutText = getString(R.string.about_license); - /* - We can then access all the views by just using the id names like this. - camelCasing is used with underscore characters being ignored. - */ - binding.aboutLicense.setHtmlText(aboutText); - - @SuppressLint("StringFormatMatches") - String improveText = String.format(getString(R.string.about_improve), Urls.NEW_ISSUE_URL); - binding.aboutImprove.setHtmlText(improveText); - binding.aboutVersion.setText(ConfigUtils.getVersionNameWithSha(getApplicationContext())); - - Utils.setUnderlinedText(binding.aboutFaq, R.string.about_faq, getApplicationContext()); - Utils.setUnderlinedText(binding.aboutRateUs, R.string.about_rate_us, getApplicationContext()); - Utils.setUnderlinedText(binding.aboutUserGuide, R.string.user_guide, getApplicationContext()); - Utils.setUnderlinedText(binding.aboutPrivacyPolicy, R.string.about_privacy_policy, getApplicationContext()); - Utils.setUnderlinedText(binding.aboutTranslate, R.string.about_translate, getApplicationContext()); - Utils.setUnderlinedText(binding.aboutCredits, R.string.about_credits, getApplicationContext()); - - /* - To set listeners, we can create a separate method and use lambda syntax. - */ - binding.facebookLaunchIcon.setOnClickListener(this::launchFacebook); - binding.githubLaunchIcon.setOnClickListener(this::launchGithub); - binding.websiteLaunchIcon.setOnClickListener(this::launchWebsite); - binding.aboutRateUs.setOnClickListener(this::launchRatings); - binding.aboutCredits.setOnClickListener(this::launchCredits); - binding.aboutPrivacyPolicy.setOnClickListener(this::launchPrivacyPolicy); - binding.aboutUserGuide.setOnClickListener(this::launchUserGuide); - binding.aboutFaq.setOnClickListener(this::launchFrequentlyAskedQuesions); - binding.aboutTranslate.setOnClickListener(this::launchTranslate); - } - - @Override - public boolean onSupportNavigateUp() { - onBackPressed(); - return true; - } - - public void launchFacebook(View view) { - Intent intent; - try { - intent = new Intent(Intent.ACTION_VIEW, Uri.parse(Urls.FACEBOOK_APP_URL)); - intent.setPackage(Urls.FACEBOOK_PACKAGE_NAME); - startActivity(intent); - } catch (Exception e) { - Utils.handleWebUrl(this, Uri.parse(Urls.FACEBOOK_WEB_URL)); - } - } - - public void launchGithub(View view) { - Intent intent; - try { - intent = new Intent(Intent.ACTION_VIEW, Uri.parse(Urls.GITHUB_REPO_URL)); - intent.setPackage(Urls.GITHUB_PACKAGE_NAME); - startActivity(intent); - } catch (Exception e) { - Utils.handleWebUrl(this, Uri.parse(Urls.GITHUB_REPO_URL)); - } - } - - public void launchWebsite(View view) { - Utils.handleWebUrl(this, Uri.parse(Urls.WEBSITE_URL)); - } - - public void launchRatings(View view){ - Utils.rateApp(this); - } - - public void launchCredits(View view) { - Utils.handleWebUrl(this, Uri.parse(Urls.CREDITS_URL)); - } - - public void launchUserGuide(View view) { - Utils.handleWebUrl(this, Uri.parse(Urls.USER_GUIDE_URL)); - } - - public void launchPrivacyPolicy(View view) { - Utils.handleWebUrl(this, Uri.parse(BuildConfig.PRIVACY_POLICY_URL)); - } - - public void launchFrequentlyAskedQuesions(View view) { - Utils.handleWebUrl(this, Uri.parse(Urls.FAQ_URL)); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.menu_about, menu); - return super.onCreateOptionsMenu(menu); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.share_app_icon: - String shareText = String.format(getString(R.string.share_text), Urls.PLAY_STORE_URL_PREFIX + this.getPackageName()); - Intent sendIntent = new Intent(); - sendIntent.setAction(Intent.ACTION_SEND); - sendIntent.putExtra(Intent.EXTRA_TEXT, shareText); - sendIntent.setType("text/plain"); - startActivity(Intent.createChooser(sendIntent, getString(R.string.share_via))); - return true; - default: - return super.onOptionsItemSelected(item); - } - } - - public void launchTranslate(View view) { - @NonNull List sortedLocalizedNamesRef = CommonsApplication.getInstance().getLanguageLookUpTable().getCanonicalNames(); - Collections.sort(sortedLocalizedNamesRef); - final ArrayAdapter languageAdapter = new ArrayAdapter<>(AboutActivity.this, - android.R.layout.simple_spinner_dropdown_item, sortedLocalizedNamesRef); - final Spinner spinner = new Spinner(AboutActivity.this); - spinner.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT)); - spinner.setAdapter(languageAdapter); - spinner.setGravity(17); - spinner.setPadding(50,0,0,0); - - Runnable positiveButtonRunnable = () -> { - String langCode = CommonsApplication.getInstance().getLanguageLookUpTable().getCodes().get(spinner.getSelectedItemPosition()); - Utils.handleWebUrl(AboutActivity.this, Uri.parse(Urls.TRANSLATE_WIKI_URL + langCode)); - }; - DialogUtil.showAlertDialog(this, - getString(R.string.about_translate_title), - getString(R.string.about_translate_message), - getString(R.string.about_translate_proceed), - getString(R.string.about_translate_cancel), - positiveButtonRunnable, - () -> {}, - spinner - ); - } - -} diff --git a/app/src/main/java/fr/free/nrw/commons/AboutActivity.kt b/app/src/main/java/fr/free/nrw/commons/AboutActivity.kt new file mode 100644 index 000000000..143f5e569 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/AboutActivity.kt @@ -0,0 +1,209 @@ +package fr.free.nrw.commons + +import android.annotation.SuppressLint +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 android.widget.ArrayAdapter +import android.widget.LinearLayout +import android.widget.Spinner +import fr.free.nrw.commons.CommonsApplication.Companion.instance +import fr.free.nrw.commons.databinding.ActivityAboutBinding +import fr.free.nrw.commons.theme.BaseActivity +import fr.free.nrw.commons.utils.ConfigUtils.getVersionNameWithSha +import fr.free.nrw.commons.utils.DialogUtil.showAlertDialog +import java.util.Collections + +/** + * Represents about screen of this app + */ +class AboutActivity : BaseActivity() { + /* + This View Binding class is auto-generated for each xml file. The format is usually the name + of the file with PascalCasing (The underscore characters will be ignored). + More information is available at https://developer.android.com/topic/libraries/view-binding + */ + private var binding: ActivityAboutBinding? = null + + /** + * This method helps in the creation About screen + * + * @param savedInstanceState Data bundle + */ + @SuppressLint("StringFormatInvalid") //TODO: + public override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + /* + Instead of just setting the view with the xml file. We need to use View Binding class. + */ + binding = ActivityAboutBinding.inflate(layoutInflater) + val view: View = binding!!.root + setContentView(view) + + setSupportActionBar(binding!!.toolbarBinding.toolbar) + supportActionBar!!.setDisplayHomeAsUpEnabled(true) + val aboutText = getString(R.string.about_license) + /* + We can then access all the views by just using the id names like this. + camelCasing is used with underscore characters being ignored. + */ + binding!!.aboutLicense.setHtmlText(aboutText) + + @SuppressLint("StringFormatMatches") // TODO: + val improveText = + String.format(getString(R.string.about_improve), Urls.NEW_ISSUE_URL) + binding!!.aboutImprove.setHtmlText(improveText) + binding!!.aboutVersion.text = applicationContext.getVersionNameWithSha() + + Utils.setUnderlinedText( + binding!!.aboutFaq, R.string.about_faq, + applicationContext + ) + Utils.setUnderlinedText( + binding!!.aboutRateUs, R.string.about_rate_us, + applicationContext + ) + Utils.setUnderlinedText( + binding!!.aboutUserGuide, R.string.user_guide, + applicationContext + ) + Utils.setUnderlinedText( + binding!!.aboutPrivacyPolicy, R.string.about_privacy_policy, + applicationContext + ) + Utils.setUnderlinedText( + binding!!.aboutTranslate, R.string.about_translate, + applicationContext + ) + Utils.setUnderlinedText( + binding!!.aboutCredits, R.string.about_credits, + applicationContext + ) + + /* + To set listeners, we can create a separate method and use lambda syntax. + */ + binding!!.facebookLaunchIcon.setOnClickListener(::launchFacebook) + binding!!.githubLaunchIcon.setOnClickListener(::launchGithub) + binding!!.websiteLaunchIcon.setOnClickListener(::launchWebsite) + binding!!.aboutRateUs.setOnClickListener(::launchRatings) + binding!!.aboutCredits.setOnClickListener(::launchCredits) + binding!!.aboutPrivacyPolicy.setOnClickListener(::launchPrivacyPolicy) + binding!!.aboutUserGuide.setOnClickListener(::launchUserGuide) + binding!!.aboutFaq.setOnClickListener(::launchFrequentlyAskedQuesions) + binding!!.aboutTranslate.setOnClickListener(::launchTranslate) + } + + override fun onSupportNavigateUp(): Boolean { + onBackPressed() + return true + } + + fun launchFacebook(view: View?) { + val intent: Intent + try { + intent = Intent(Intent.ACTION_VIEW, Uri.parse(Urls.FACEBOOK_APP_URL)) + intent.setPackage(Urls.FACEBOOK_PACKAGE_NAME) + startActivity(intent) + } catch (e: Exception) { + Utils.handleWebUrl(this, Uri.parse(Urls.FACEBOOK_WEB_URL)) + } + } + + fun launchGithub(view: View?) { + val intent: Intent + try { + intent = Intent(Intent.ACTION_VIEW, Uri.parse(Urls.GITHUB_REPO_URL)) + intent.setPackage(Urls.GITHUB_PACKAGE_NAME) + startActivity(intent) + } catch (e: Exception) { + Utils.handleWebUrl(this, Uri.parse(Urls.GITHUB_REPO_URL)) + } + } + + fun launchWebsite(view: View?) { + Utils.handleWebUrl(this, Uri.parse(Urls.WEBSITE_URL)) + } + + fun launchRatings(view: View?) { + Utils.rateApp(this) + } + + fun launchCredits(view: View?) { + Utils.handleWebUrl(this, Uri.parse(Urls.CREDITS_URL)) + } + + fun launchUserGuide(view: View?) { + Utils.handleWebUrl(this, Uri.parse(Urls.USER_GUIDE_URL)) + } + + fun launchPrivacyPolicy(view: View?) { + Utils.handleWebUrl(this, Uri.parse(BuildConfig.PRIVACY_POLICY_URL)) + } + + fun launchFrequentlyAskedQuesions(view: View?) { + Utils.handleWebUrl(this, Uri.parse(Urls.FAQ_URL)) + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + val inflater = menuInflater + inflater.inflate(R.menu.menu_about, menu) + return super.onCreateOptionsMenu(menu) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.share_app_icon -> { + val shareText = String.format( + getString(R.string.share_text), + Urls.PLAY_STORE_URL_PREFIX + this.packageName + ) + val sendIntent = Intent() + sendIntent.setAction(Intent.ACTION_SEND) + sendIntent.putExtra(Intent.EXTRA_TEXT, shareText) + sendIntent.setType("text/plain") + startActivity(Intent.createChooser(sendIntent, getString(R.string.share_via))) + return true + } + + else -> return super.onOptionsItemSelected(item) + } + } + + fun launchTranslate(view: View?) { + val sortedLocalizedNamesRef = instance.languageLookUpTable!!.getCanonicalNames() + Collections.sort(sortedLocalizedNamesRef) + val languageAdapter = ArrayAdapter( + this@AboutActivity, + android.R.layout.simple_spinner_dropdown_item, sortedLocalizedNamesRef + ) + val spinner = Spinner(this@AboutActivity) + spinner.layoutParams = + LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT + ) + spinner.adapter = languageAdapter + spinner.gravity = 17 + spinner.setPadding(50, 0, 0, 0) + + val positiveButtonRunnable = Runnable { + val langCode = instance.languageLookUpTable!!.getCodes()[spinner.selectedItemPosition] + Utils.handleWebUrl(this@AboutActivity, Uri.parse(Urls.TRANSLATE_WIKI_URL + langCode)) + } + showAlertDialog( + this, + getString(R.string.about_translate_title), + getString(R.string.about_translate_message), + getString(R.string.about_translate_proceed), + getString(R.string.about_translate_cancel), + positiveButtonRunnable, + {}, + spinner + ) + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/Utils.java b/app/src/main/java/fr/free/nrw/commons/Utils.java index cd9c6eed5..8d0f8b530 100644 --- a/app/src/main/java/fr/free/nrw/commons/Utils.java +++ b/app/src/main/java/fr/free/nrw/commons/Utils.java @@ -148,13 +148,27 @@ public class Utils { } /** - * Util function to handle geo coordinates - * It no longer depends on google maps and any app capable of handling the map intent can handle it - * @param context - * @param latLng + * Util function to handle geo coordinates. It no longer depends on google maps and any app + * capable of handling the map intent can handle it + * + * @param context The context for launching intent + * @param latLng The latitude and longitude of the location */ - public static void handleGeoCoordinates(Context context, LatLng latLng) { - Intent mapIntent = new Intent(Intent.ACTION_VIEW, latLng.getGmmIntentUri()); + public static void handleGeoCoordinates(final Context context, final LatLng latLng) { + handleGeoCoordinates(context, latLng, 16); + } + + /** + * Util function to handle geo coordinates with specified zoom level. It no longer depends on + * google maps and any app capable of handling the map intent can handle it + * + * @param context The context for launching intent + * @param latLng The latitude and longitude of the location + * @param zoomLevel The zoom level + */ + public static void handleGeoCoordinates(final Context context, final LatLng latLng, + final double zoomLevel) { + final Intent mapIntent = new Intent(Intent.ACTION_VIEW, latLng.getGmmIntentUri(zoomLevel)); if (mapIntent.resolveActivity(context.getPackageManager()) != null) { context.startActivity(mapIntent); } else { diff --git a/app/src/main/java/fr/free/nrw/commons/activity/SingleWebViewActivity.kt b/app/src/main/java/fr/free/nrw/commons/activity/SingleWebViewActivity.kt index 0583ae2f9..b7951adab 100644 --- a/app/src/main/java/fr/free/nrw/commons/activity/SingleWebViewActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/activity/SingleWebViewActivity.kt @@ -28,6 +28,8 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.viewinterop.AndroidView +import fr.free.nrw.commons.CommonsApplication +import fr.free.nrw.commons.CommonsApplication.ActivityLogoutListener import fr.free.nrw.commons.R import fr.free.nrw.commons.di.ApplicationlessInjection import fr.free.nrw.commons.wikidata.cookies.CommonsCookieJar @@ -85,7 +87,12 @@ class SingleWebViewActivity : ComponentActivity() { url = url, successUrl = successUrl, onSuccess = { - // TODO Redirect the user to login screen like we do when the user logout's + //Redirect the user to login screen like we do when the user logout's + val app = applicationContext as CommonsApplication + app.clearApplicationData( + applicationContext, + ActivityLogoutListener(activity = this, ctx = applicationContext) + ) finish() }, modifier = Modifier diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategoriesModel.kt b/app/src/main/java/fr/free/nrw/commons/category/CategoriesModel.kt index fd90be95f..47147944c 100644 --- a/app/src/main/java/fr/free/nrw/commons/category/CategoriesModel.kt +++ b/app/src/main/java/fr/free/nrw/commons/category/CategoriesModel.kt @@ -36,37 +36,35 @@ class CategoriesModel * @return */ fun isSpammyCategory(item: String): Boolean { - // Check for current and previous year to exclude these categories from removal - val now = Calendar.getInstance() - val curYear = now[Calendar.YEAR] - val curYearInString = curYear.toString() - val prevYear = curYear - 1 - val prevYearInString = prevYear.toString() - Timber.d("Previous year: %s", prevYearInString) - - val mentionsDecade = item.matches(".*0s.*".toRegex()) - val recentDecade = item.matches(".*20[0-2]0s.*".toRegex()) - val spammyCategory = - item.matches("(.*)needing(.*)".toRegex()) || - item.matches("(.*)taken on(.*)".toRegex()) // always skip irrelevant categories such as Media_needing_categories_as_of_16_June_2017(Issue #750) + val spammyCategory = item.matches("(.*)needing(.*)".toRegex()) + || item.matches("(.*)taken on(.*)".toRegex()) + + // checks for + // dd/mm/yyyy or yy + // yyyy or yy/mm/dd + // yyyy or yy/mm + // mm/yyyy or yy + // for `yy` it is assumed that 20XX is implicit. + // with separators [., /, -] + val isIrrelevantCategory = + item.contains("""\d{1,2}[-/.]\d{1,2}[-/.]\d{2,4}|\d{2,4}[-/.]\d{1,2}[-/.]\d{1,2}|\d{2,4}[-/.]\d{1,2}|\d{1,2}[-/.]\d{2,4}""".toRegex()) + + if (spammyCategory) { return true } - if (mentionsDecade) { - // Check if the year in the form of XX(X)0s is recent/relevant, i.e. in the 2000s or 2010s/2020s as stated in Issue #1029 - // Example: "2020s" is OK, but "1920s" is not (and should be skipped) - return !recentDecade - } else { - // If it is not an year in decade form (e.g. 19xxs/20xxs), then check if item contains a 4-digit year - // anywhere within the string (.* is wildcard) (Issue #47) - // And that item does not equal the current year or previous year - return item.matches(".*(19|20)\\d{2}.*".toRegex()) && - !item.contains(curYearInString) && - !item.contains(prevYearInString) + if(isIrrelevantCategory){ + return true } + + val hasYear = item.matches("(.*\\d{4}.*)".toRegex()) + val validYearsRange = item.matches(".*(20[0-9]{2}).*".toRegex()) + + // finally if there's 4 digits year exists in XXXX it should only be in 20XX range. + return hasYear && !validYearsRange } /** diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.java b/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.java index 03027f287..047943721 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.java @@ -207,6 +207,9 @@ public class MainActivity extends BaseActivity private boolean loadFragment(Fragment fragment, boolean showBottom) { //showBottom so that we do not show the bottom tray again when constructing //from the saved instance state. + + freeUpFragments(); + if (fragment instanceof ContributionsFragment) { if (activeFragment == ActiveFragment.CONTRIBUTIONS) { // scroll to top if already on the Contributions tab @@ -256,6 +259,31 @@ public class MainActivity extends BaseActivity return false; } + /** + * loadFragment() overload that supports passing extras to fragments + **/ + private boolean loadFragment(Fragment fragment, boolean showBottom, Bundle args) { + if (fragment != null && args != null) { + fragment.setArguments(args); + } + + return loadFragment(fragment, showBottom); + } + + /** + * Old implementation of loadFragment() was causing memory leaks, due to MainActivity holding + * references to cleared fragments. This function frees up all fragment references. + *

+ * Called in loadFragment() before doing the actual loading. + **/ + public void freeUpFragments() { + // free all fragments except contributionsFragment because several tests depend on it. + // hence, contributionsFragment is probably still a leak + nearbyParentFragment = null; + exploreFragment = null; + bookmarkFragment = null; + } + public void hideTabs() { binding.fragmentMainNavTabLayout.setVisibility(View.GONE); } @@ -432,6 +460,42 @@ public class MainActivity extends BaseActivity }); } + /** + * Launch the Explore fragment from Nearby fragment. This method is called when a user clicks + * the 'Show in Explore' option in the 3-dots menu in Nearby. + * + * @param zoom current zoom of Nearby map + * @param latitude current latitude of Nearby map + * @param longitude current longitude of Nearby map + **/ + public void loadExploreMapFromNearby(double zoom, double latitude, double longitude) { + Bundle bundle = new Bundle(); + bundle.putDouble("prev_zoom", zoom); + bundle.putDouble("prev_latitude", latitude); + bundle.putDouble("prev_longitude", longitude); + + loadFragment(ExploreFragment.newInstance(), false, bundle); + setSelectedItemId(NavTab.EXPLORE.code()); + } + + /** + * Launch the Nearby fragment from Explore fragment. This method is called when a user clicks + * the 'Show in Nearby' option in the 3-dots menu in Explore. + * + * @param zoom current zoom of Explore map + * @param latitude current latitude of Explore map + * @param longitude current longitude of Explore map + **/ + public void loadNearbyMapFromExplore(double zoom, double latitude, double longitude) { + Bundle bundle = new Bundle(); + bundle.putDouble("prev_zoom", zoom); + bundle.putDouble("prev_latitude", latitude); + bundle.putDouble("prev_longitude", longitude); + + loadFragment(NearbyParentFragment.newInstance(), false, bundle); + setSelectedItemId(NavTab.NEARBY.code()); + } + @Override protected void onResume() { super.onResume(); 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 74b937f97..ff623d496 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 @@ -24,6 +24,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.MainScope import kotlinx.coroutines.cancel +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch import java.util.TreeMap import kotlin.collections.ArrayList @@ -103,6 +104,18 @@ class ImageAdapter( */ private var imagePositionAsPerIncreasingOrder = 0 + /** + * Stores the number of images currently visible on the screen + */ + private val _currentImagesCount = MutableStateFlow(0) + val currentImagesCount = _currentImagesCount + + /** + * Stores whether images are being loaded or not + */ + private val _isLoadingImages = MutableStateFlow(false) + val isLoadingImages = _isLoadingImages + /** * Coroutine Dispatchers and Scope. */ @@ -184,8 +197,12 @@ class ImageAdapter( // If the position is not already visited, that means the position is new then // finds the next actionable image position from all images if (!alreadyAddedPositions.contains(position)) { - processThumbnailForActionedImage(holder, position, uploadingContributionList) - + processThumbnailForActionedImage( + holder, + position, + uploadingContributionList + ) + _isLoadingImages.value = false // If the position is already visited, that means the image is already present // inside map, so it will fetch the image from the map and load in the holder } else { @@ -231,6 +248,7 @@ class ImageAdapter( position: Int, uploadingContributionList: List, ) { + _isLoadingImages.value = true val next = imageLoader.nextActionableImage( allImages, @@ -252,6 +270,7 @@ class ImageAdapter( actionableImagesMap[next] = allImages[next] alreadyAddedPositions.add(imagePositionAsPerIncreasingOrder) imagePositionAsPerIncreasingOrder++ + _currentImagesCount.value = imagePositionAsPerIncreasingOrder Glide .with(holder.image) .load(allImages[next].uri) @@ -267,6 +286,7 @@ class ImageAdapter( reachedEndOfFolder = true notifyItemRemoved(position) } + _isLoadingImages.value = false } /** @@ -372,6 +392,7 @@ class ImageAdapter( emptyMap: TreeMap, uploadedImages: List = ArrayList(), ) { + _isLoadingImages.value = true allImages = fixedImages val oldImageList: ArrayList = images val newImageList: ArrayList = ArrayList(newImages) @@ -382,6 +403,7 @@ class ImageAdapter( reachedEndOfFolder = false selectedImages = ArrayList() imagePositionAsPerIncreasingOrder = 0 + _currentImagesCount.value = imagePositionAsPerIncreasingOrder val diffResult = DiffUtil.calculateDiff( ImagesDiffCallback(oldImageList, newImageList), @@ -441,6 +463,7 @@ class ImageAdapter( val entry = iterator.next() if (entry.value == image) { imagePositionAsPerIncreasingOrder -= 1 + _currentImagesCount.value = imagePositionAsPerIncreasingOrder iterator.remove() alreadyAddedPositions.removeAt(alreadyAddedPositions.size - 1) notifyItemRemoved(index) 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 3912a4d12..39d0d545a 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 @@ -12,8 +12,12 @@ import android.widget.ProgressBar import android.widget.Switch import androidx.appcompat.app.AlertDialog import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.view.isVisible +import androidx.lifecycle.Lifecycle import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView import fr.free.nrw.commons.contributions.Contribution @@ -38,6 +42,10 @@ import fr.free.nrw.commons.theme.BaseActivity import fr.free.nrw.commons.upload.FileProcessor import fr.free.nrw.commons.upload.FileUtilsWrapper 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 java.util.TreeMap import javax.inject.Inject import kotlin.collections.ArrayList @@ -80,6 +88,12 @@ class ImageFragment : */ var allImages: ArrayList = ArrayList() + /** + * Keeps track of switch state + */ + private val _switchState = MutableStateFlow(false) + val switchState = _switchState.asStateFlow() + /** * View model Factory. */ @@ -214,7 +228,11 @@ class ImageFragment : switch = binding?.switchWidget switch?.visibility = View.VISIBLE - switch?.setOnCheckedChangeListener { _, isChecked -> onChangeSwitchState(isChecked) } + _switchState.value = switch?.isChecked ?: false + switch?.setOnCheckedChangeListener { _, isChecked -> + onChangeSwitchState(isChecked) + _switchState.value = isChecked + } selectorRV = binding?.selectorRv loader = binding?.loader progressLayout = binding?.progressLayout @@ -234,6 +252,28 @@ class ImageFragment : return binding?.root } + /** + * onViewCreated + * Updates empty text view visibility based on image count, switch state, and loading status. + */ + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + viewLifecycleOwner.lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + combine( + imageAdapter.currentImagesCount, + switchState, + imageAdapter.isLoadingImages + ) { imageCount, isChecked, isLoadingImages -> + Triple(imageCount, isChecked, isLoadingImages) + }.collect { (imageCount, isChecked, isLoadingImages) -> + binding?.allImagesUploadedOrMarked?.isVisible = + !isLoadingImages && !isChecked && imageCount == 0 && (switch?.isVisible == true) + } + } + } + } + private fun onChangeSwitchState(checked: Boolean) { if (checked) { showAlreadyActionedImages = true 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 index d444148d4..223d028dc 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/ExploreFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/explore/ExploreFragment.java @@ -1,5 +1,7 @@ package fr.free.nrw.commons.explore; +import static androidx.viewpager.widget.ViewPager.SCROLL_STATE_IDLE; + import android.os.Bundle; import android.view.LayoutInflater; import android.view.Menu; @@ -42,9 +44,13 @@ public class ExploreFragment extends CommonsDaggerSupportFragment { @Named("default_preferences") public JsonKvStore applicationKvStore; - public void setScroll(boolean canScroll){ - if (binding != null) - { + // 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); } } @@ -60,6 +66,7 @@ public class ExploreFragment extends CommonsDaggerSupportFragment { public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); + loadNearbyMapData(); binding = FragmentExploreBinding.inflate(inflater, container, false); viewPagerAdapter = new ViewPagerAdapter(getChildFragmentManager()); @@ -89,6 +96,11 @@ public class ExploreFragment extends CommonsDaggerSupportFragment { }); 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(); } @@ -108,6 +120,13 @@ public class ExploreFragment extends CommonsDaggerSupportFragment { 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); @@ -120,13 +139,35 @@ public class ExploreFragment extends CommonsDaggerSupportFragment { fragmentList.add(mapRootFragment); titleList.add(getString(R.string.explore_tab_title_map).toUpperCase(Locale.ROOT)); - ((MainActivity)getActivity()).showTabs(); + ((MainActivity) getActivity()).showTabs(); ((BaseActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(false); viewPagerAdapter.setTabData(fragmentList, titleList); 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()) { @@ -155,7 +196,38 @@ public class ExploreFragment extends CommonsDaggerSupportFragment { */ @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - inflater.inflate(R.menu.menu_search, menu); + // 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); } @@ -171,6 +243,9 @@ public class ExploreFragment extends CommonsDaggerSupportFragment { 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); } diff --git a/app/src/main/java/fr/free/nrw/commons/explore/ExploreMapRootFragment.java b/app/src/main/java/fr/free/nrw/commons/explore/ExploreMapRootFragment.java index 2653b4409..abf02758d 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/ExploreMapRootFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/explore/ExploreMapRootFragment.java @@ -39,10 +39,22 @@ public class ExploreMapRootFragment extends CommonsDaggerSupportFragment impleme } 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); } @@ -198,7 +210,8 @@ public class ExploreMapRootFragment extends CommonsDaggerSupportFragment impleme ((MainActivity) getActivity()).showTabs(); return true; - } if (mapFragment != null && mapFragment.isVisible()) { + } + if (mapFragment != null && mapFragment.isVisible()) { if (mapFragment.backButtonClicked()) { // Explore map fragment handled the event no further action required. return true; @@ -213,6 +226,10 @@ public class ExploreMapRootFragment extends CommonsDaggerSupportFragment impleme return false; } + public void loadNearbyMapFromExplore() { + mapFragment.loadNearbyMapFromExplore(); + } + @Override public void onDestroy() { super.onDestroy(); diff --git a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.java b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.java index fd1ea1f28..e64b96190 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.java @@ -38,6 +38,7 @@ import fr.free.nrw.commons.Media; import fr.free.nrw.commons.R; import fr.free.nrw.commons.Utils; 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; @@ -115,6 +116,11 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment SystemThemeUtils systemThemeUtils; LocationPermissionsHelper locationPermissionsHelper; + // Nearby map state (if we came from Nearby) + private double prevZoom; + private double prevLatitude; + private double prevLongitude; + private ExploreMapPresenter presenter; public FragmentExploreMapBinding binding; @@ -160,6 +166,7 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment ViewGroup container, Bundle savedInstanceState ) { + loadNearbyMapData(); binding = FragmentExploreMapBinding.inflate(getLayoutInflater()); return binding.getRoot(); } @@ -169,12 +176,14 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment 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)); + 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); + locationPermissionsHelper = new LocationPermissionsHelper(getActivity(), locationManager, + this); if (presenter == null) { presenter = new ExploreMapPresenter(bookmarkLocationDao); } @@ -204,9 +213,14 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment scaleBarOverlay.setBackgroundPaint(barPaint); scaleBarOverlay.enableScaleBar(); binding.mapView.getOverlays().add(scaleBarOverlay); - binding.mapView.getZoomController().setVisibility(CustomZoomButtonsController.Visibility.NEVER); + binding.mapView.getZoomController() + .setVisibility(CustomZoomButtonsController.Visibility.NEVER); binding.mapView.setMultiTouchControls(true); - binding.mapView.getController().setZoom(ZOOM_LEVEL); + + if (!isCameFromNearbyMap()) { + binding.mapView.getController().setZoom(ZOOM_LEVEL); + } + performMapReadyActions(); binding.mapView.getOverlays().add(new MapEventsOverlay(new MapEventsReceiver() { @@ -295,7 +309,7 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment unregisterNetworkReceiver(); } - + /** * Unregisters the networkReceiver */ @@ -328,11 +342,51 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment isPermissionDenied = true; } lastKnownLocation = MapUtils.getDefaultLatLng(); - moveCameraToPosition( - new GeoPoint(lastKnownLocation.getLatitude(), lastKnownLocation.getLongitude())); + + // 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"); + } + } + + /** + * 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; + } + + 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(); @@ -346,7 +400,8 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment */ @SuppressLint("ClickableViewAccessibility") private void initBottomSheets() { - bottomSheetDetailsBehavior = BottomSheetBehavior.from(binding.bottomSheetDetailsBinding.getRoot()); + bottomSheetDetailsBehavior = BottomSheetBehavior.from( + binding.bottomSheetDetailsBinding.getRoot()); bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); binding.bottomSheetDetailsBinding.getRoot().setVisibility(View.VISIBLE); } @@ -404,7 +459,8 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment if (currentLatLng == null) { return; } - if (currentLatLng.equals(getLastMapFocus())) { // Means we are checking around current location + if (currentLatLng.equals( + getLastMapFocus())) { // Means we are checking around current location nearbyPlacesInfoObservable = presenter.loadAttractionsFromLocation(currentLatLng, getLastMapFocus(), true); } else { @@ -416,11 +472,12 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment .observeOn(AndroidSchedulers.mainThread()) .subscribe(explorePlacesInfo -> { mediaList = explorePlacesInfo.mediaList; - if(mediaList == null) { + if (mediaList == null) { showResponseMessage(getString(R.string.no_pictures_in_this_area)); } updateMapMarkers(explorePlacesInfo); - lastMapFocus = new GeoPoint(currentLatLng.getLatitude(), currentLatLng.getLongitude()); + lastMapFocus = new GeoPoint(currentLatLng.getLatitude(), + currentLatLng.getLongitude()); }, throwable -> { Timber.d(throwable); @@ -474,9 +531,9 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment locationManager.requestLocationUpdatesFromProvider(LocationManager.NETWORK_PROVIDER); locationManager.requestLocationUpdatesFromProvider(LocationManager.GPS_PROVIDER); setProgressBarVisibility(true); - } - else { - locationPermissionsHelper.showLocationOffDialog(getActivity(), R.string.ask_to_turn_location_on_text); + } else { + locationPermissionsHelper.showLocationOffDialog(getActivity(), + R.string.ask_to_turn_location_on_text); } presenter.onMapReady(exploreMapController); registerUnregisterLocationListener(false); @@ -508,7 +565,8 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment recenterToUserLocation = true; return; } - recenterMarkerToPosition(new GeoPoint(currentLatLng.getLatitude(), currentLatLng.getLongitude())); + recenterMarkerToPosition( + new GeoPoint(currentLatLng.getLatitude(), currentLatLng.getLongitude())); binding.mapView.getController() .animateTo(new GeoPoint(currentLatLng.getLatitude(), currentLatLng.getLongitude())); if (lastMapFocus != null) { @@ -545,10 +603,12 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment * @param place Place of clicked nearby marker */ private void passInfoToSheet(final Place place) { - binding.bottomSheetDetailsBinding.directionsButton.setOnClickListener(view -> Utils.handleGeoCoordinates(getActivity(), - place.getLocation())); + binding.bottomSheetDetailsBinding.directionsButton.setOnClickListener( + view -> Utils.handleGeoCoordinates(getActivity(), + place.getLocation(), binding.mapView.getZoomLevelDouble())); - binding.bottomSheetDetailsBinding.commonsButton.setVisibility(place.hasCommonsLink() ? View.VISIBLE : View.GONE); + binding.bottomSheetDetailsBinding.commonsButton.setVisibility( + place.hasCommonsLink() ? View.VISIBLE : View.GONE); binding.bottomSheetDetailsBinding.commonsButton.setOnClickListener( view -> Utils.handleWebUrl(getContext(), place.siteLinks.getCommonsLink())); @@ -562,7 +622,8 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment } index++; } - binding.bottomSheetDetailsBinding.title.setText(place.name.substring(5, place.name.lastIndexOf("."))); + 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() @@ -640,40 +701,43 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment * @param nearbyBaseMarker The NearbyBaseMarker object representing the marker to be added. */ private void addMarkerToMap(BaseMarker nearbyBaseMarker) { - 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()); - OverlayItem item = new OverlayItem(nearbyBaseMarker.getPlace().name, null, - 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); + 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()); + OverlayItem item = new OverlayItem(nearbyBaseMarker.getPlace().name, null, + 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); + return true; } - clickedMarker = nearbyBaseMarker; - passInfoToSheet(place); - return true; - } - @Override - public boolean onItemLongPress(int index, OverlayItem item) { - return false; - } - }, getContext()); + @Override + public boolean onItemLongPress(int index, OverlayItem item) { + return false; + } + }, getContext()); - overlay.setFocusItemsOnTap(true); - binding.mapView.getOverlays().add(overlay); // Add the overlay to the map + overlay.setFocusItemsOnTap(true); + binding.mapView.getOverlays().add(overlay); // Add the overlay to the map + } } /** @@ -707,68 +771,72 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment */ @Override public void clearAllMarkers() { - 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; + 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); + @Override + public boolean longPressHelper(GeoPoint p) { + return false; + } + })); + binding.mapView.setMultiTouchControls(true); + } } /** @@ -825,6 +893,18 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment 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( @@ -850,14 +930,17 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment -0.07483536015053005, 1f); } } - moveCameraToPosition(new GeoPoint(latLnge.getLatitude(),latLnge.getLongitude())); + if (!isCameFromNearbyMap()) { + moveCameraToPosition(new GeoPoint(latLnge.getLatitude(), latLnge.getLongitude())); + } 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); + binding.mapView.getMapCenter().getLatitude(), + binding.mapView.getMapCenter().getLongitude(), 100); return mapFocusedLatLng; } @@ -910,9 +993,19 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment }; } - @Override - public void onLocationPermissionDenied(String toastMessage) {} + /** + * helper function to confirm that this fragment has been attached. + **/ + public boolean isAttachedToActivity() { + boolean attached = isVisible() && getActivity() != null; + return attached; + } @Override - public void onLocationPermissionGranted() {} + public void onLocationPermissionDenied(String toastMessage) { + } + + @Override + public void onLocationPermissionGranted() { + } } diff --git a/app/src/main/java/fr/free/nrw/commons/location/LatLng.kt b/app/src/main/java/fr/free/nrw/commons/location/LatLng.kt index 7dd9a49ce..5ca75b38c 100644 --- a/app/src/main/java/fr/free/nrw/commons/location/LatLng.kt +++ b/app/src/main/java/fr/free/nrw/commons/location/LatLng.kt @@ -123,10 +123,13 @@ data class LatLng( /** * Gets a URI for a Google Maps intent at the location. + * + * @paraam zoom The zoom level + * @return URI for the intent */ - fun getGmmIntentUri(): Uri { - return Uri.parse("geo:$latitude,$longitude?z=16") - } + fun getGmmIntentUri(zoom: Double): Uri = Uri.parse( + "geo:$latitude,$longitude?q=$latitude,$longitude&z=${zoom}" + ) override fun writeToParcel(parcel: Parcel, flags: Int) { parcel.writeDouble(latitude) 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 080bc058d..2a7b7713b 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 @@ -430,7 +430,11 @@ class LocationPickerActivity : BaseActivity(), LocationPermissionCallback { else -> null } - position?.let { Utils.handleGeoCoordinates(this, it) } + position?.let { + mapView?.zoomLevelDouble?.let { zoomLevel -> + Utils.handleGeoCoordinates(this, it, zoomLevel) + } ?: Utils.handleGeoCoordinates(this, it) + } } /** 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 4c993fb80..d714b094a 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 @@ -650,10 +650,8 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C } private fun onDepictionsLoaded(idAndCaptions: List) { - binding.depictsLayout.visibility = - if (idAndCaptions.isEmpty()) View.GONE else View.VISIBLE - binding.depictionsEditButton.visibility = - if (idAndCaptions.isEmpty()) View.GONE else View.VISIBLE + binding.depictsLayout.visibility = View.VISIBLE + binding.depictionsEditButton.visibility = View.VISIBLE buildDepictionList(idAndCaptions) } @@ -863,8 +861,22 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C */ private fun buildDepictionList(idAndCaptions: List) { binding.mediaDetailDepictionContainer.removeAllViews() + + // Create a mutable list from the original list + val mutableIdAndCaptions = idAndCaptions.toMutableList() + + if (mutableIdAndCaptions.isEmpty()) { + // Create a placeholder IdAndCaptions object and add it to the list + mutableIdAndCaptions.add( + IdAndCaptions( + id = media?.pageId ?: "", // Use an empty string if media?.pageId is null + captions = mapOf(Locale.getDefault().language to getString(R.string.detail_panel_cats_none)) // Create a Map with the language as the key and the message as the value + ) + ) + } + val locale: String = Locale.getDefault().language - for (idAndCaption: IdAndCaptions in idAndCaptions) { + for (idAndCaption: IdAndCaptions in mutableIdAndCaptions) { binding.mediaDetailDepictionContainer.addView( buildDepictLabel( getDepictionCaption(idAndCaption, locale), @@ -875,6 +887,7 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C } } + private fun getDepictionCaption(idAndCaption: IdAndCaptions, locale: String): String? { // Check if the Depiction Caption is available in user's locale // if not then check for english, else show any available. diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.java b/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.java index f3224de7f..95f19f699 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.java @@ -233,6 +233,11 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment private Place nearestPlace; private volatile boolean stopQuery; + // Explore map data (for if we came from Explore) + private double prevZoom; + private double prevLatitude; + private double prevLongitude; + private final Handler searchHandler = new Handler(); private Runnable searchRunnable; @@ -247,27 +252,28 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment private final ActivityResultLauncher galleryPickLauncherForResult = registerForActivityResult(new StartActivityForResult(), - result -> { - controller.handleActivityResultWithCallback(requireActivity(), callbacks -> { - controller.onPictureReturnedFromGallery(result, requireActivity(), callbacks); + result -> { + controller.handleActivityResultWithCallback(requireActivity(), callbacks -> { + controller.onPictureReturnedFromGallery(result, requireActivity(), callbacks); + }); }); - }); private final ActivityResultLauncher customSelectorLauncherForResult = registerForActivityResult(new StartActivityForResult(), - result -> { - controller.handleActivityResultWithCallback(requireActivity(), callbacks -> { - controller.onPictureReturnedFromCustomSelector(result, requireActivity(), callbacks); + result -> { + controller.handleActivityResultWithCallback(requireActivity(), callbacks -> { + controller.onPictureReturnedFromCustomSelector(result, requireActivity(), + callbacks); + }); }); - }); private final ActivityResultLauncher cameraPickLauncherForResult = registerForActivityResult(new StartActivityForResult(), - result -> { - controller.handleActivityResultWithCallback(requireActivity(), callbacks -> { - controller.onPictureReturnedFromCamera(result, requireActivity(), callbacks); + result -> { + controller.handleActivityResultWithCallback(requireActivity(), callbacks -> { + controller.onPictureReturnedFromCamera(result, requireActivity(), callbacks); + }); }); - }); private ActivityResultLauncher inAppCameraLocationPermissionLauncher = registerForActivityResult( new RequestMultiplePermissions(), @@ -337,12 +343,15 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment @Override public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) { + loadExploreMapData(); + binding = FragmentNearbyParentBinding.inflate(inflater, container, false); view = binding.getRoot(); initNetworkBroadCastReceiver(); scope = LifecycleOwnerKt.getLifecycleScope(getViewLifecycleOwner()); - presenter = new NearbyParentFragmentPresenter(bookmarkLocationDao, placesRepository, nearbyController); + presenter = new NearbyParentFragmentPresenter(bookmarkLocationDao, placesRepository, + nearbyController); progressDialog = new ProgressDialog(getActivity()); progressDialog.setCancelable(false); progressDialog.setMessage("Saving in progress..."); @@ -359,6 +368,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment inflater.inflate(R.menu.nearby_fragment_menu, menu); MenuItem refreshButton = menu.findItem(R.id.item_refresh); MenuItem listMenu = menu.findItem(R.id.list_sheet); + MenuItem showInExploreButton = menu.findItem(R.id.list_item_show_in_explore); MenuItem saveAsGPXButton = menu.findItem(R.id.list_item_gpx); MenuItem saveAsKMLButton = menu.findItem(R.id.list_item_kml); refreshButton.setOnMenuItemClickListener(new OnMenuItemClickListener() { @@ -379,6 +389,17 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment return false; } }); + showInExploreButton.setOnMenuItemClickListener(new OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(@NonNull MenuItem item) { + ((MainActivity) getContext()).loadExploreMapFromNearby( + binding.map.getZoomLevelDouble(), + binding.map.getMapCenter().getLatitude(), + binding.map.getMapCenter().getLongitude() + ); + return false; + } + }); saveAsGPXButton.setOnMenuItemClickListener(new OnMenuItemClickListener() { @Override @@ -467,6 +488,14 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment binding.map.getOverlays().add(scaleBarOverlay); binding.map.getZoomController().setVisibility(Visibility.NEVER); binding.map.getController().setZoom(ZOOM_LEVEL); + // if we came from Explore map using 'Show in Nearby', load Explore map camera position + if (isCameFromExploreMap()) { + moveCameraToPosition( + new GeoPoint(prevLatitude, prevLongitude), + prevZoom, + 1L + ); + } binding.map.getOverlays().add(mapEventsOverlay); binding.map.addMapListener(new MapListener() { @@ -489,11 +518,14 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment } initNearbyFilter(); addCheckBoxCallback(); - moveCameraToPosition(lastMapFocus); + if (!isCameFromExploreMap()) { + moveCameraToPosition(lastMapFocus); + } initRvNearbyList(); onResume(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - binding.tvAttribution.setText(Html.fromHtml(getString(R.string.map_attribution), Html.FROM_HTML_MODE_LEGACY)); + binding.tvAttribution.setText( + Html.fromHtml(getString(R.string.map_attribution), Html.FROM_HTML_MODE_LEGACY)); } else { //noinspection deprecation binding.tvAttribution.setText(Html.fromHtml(getString(R.string.map_attribution))); @@ -545,6 +577,28 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment } } + /** + * Fetch Explore map camera data from fragment arguments if any. + */ + public void loadExploreMapData() { + // 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 Explore map. if present, then the user + * navigated from Explore using 'Show in Nearby'. + * + * @return true if user navigated from Explore map + **/ + public boolean isCameFromExploreMap() { + return prevZoom != 0.0 || prevLatitude != 0.0 || prevLongitude != 0.0; + } + /** * Initialise background based on theme, this should be doe ideally via styles, that would need * another refactor @@ -625,7 +679,9 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment mapCenter = targetP; binding.map.getController().setCenter(targetP); recenterMarkerToPosition(targetP); - moveCameraToPosition(targetP); + if (!isCameFromExploreMap()) { + moveCameraToPosition(targetP); + } } else if (locationManager.isGPSProviderEnabled() || locationManager.isNetworkProviderEnabled()) { locationManager.requestLocationUpdatesFromProvider(LocationManager.NETWORK_PROVIDER); @@ -669,7 +725,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment } else { lastKnownLocation = MapUtils.getDefaultLatLng(); } - if (binding.map != null) { + if (binding.map != null && !isCameFromExploreMap()) { moveCameraToPosition( new GeoPoint(lastKnownLocation.getLatitude(), lastKnownLocation.getLongitude())); } @@ -739,8 +795,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment } /** - * Determines the number of spans (columns) in the RecyclerView based on device orientation - * and adapter item count. + * Determines the number of spans (columns) in the RecyclerView based on device orientation and + * adapter item count. * * @return The number of spans to be used in the RecyclerView. */ @@ -1175,7 +1231,6 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment /** * Clears the Nearby local cache and then calls for pin details to be fetched afresh. - * */ private void emptyCache() { // reload the map once the cache is cleared @@ -1338,7 +1393,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment } /** - * Fetches and updates the data for a specific place, then updates the corresponding marker on the map. + * Fetches and updates the data for a specific place, then updates the corresponding marker on + * the map. * * @param entity The entity ID of the place. * @param place The Place object containing the initial place data. @@ -1469,9 +1525,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment } /** - * Stops any ongoing queries and clears all disposables. - * This method sets the stopQuery flag to true and clears the compositeDisposable - * to prevent any further processing. + * Stops any ongoing queries and clears all disposables. This method sets the stopQuery flag to + * true and clears the compositeDisposable to prevent any further processing. */ @Override public void stopQuery() { @@ -1624,7 +1679,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment new Builder(getContext()) .setMessage(R.string.login_alert_message) .setCancelable(false) - .setNegativeButton(R.string.cancel, (dialog, which) -> {}) + .setNegativeButton(R.string.cancel, (dialog, which) -> { + }) .setPositiveButton(R.string.login, (dialog, which) -> { // logout of the app BaseLogoutListener logoutListener = new BaseLogoutListener(getActivity()); @@ -1743,7 +1799,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment final boolean filterForPlaceState, final boolean filterForAllNoneType) { final boolean displayExists = false; - final boolean displayNeedsPhoto= false; + final boolean displayNeedsPhoto = false; final boolean displayWlm = false; if (selectedLabels == null || selectedLabels.size() == 0) { replaceMarkerOverlays(NearbyController.markerLabelList); @@ -1903,8 +1959,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment /** * Adds multiple markers representing places to the map and handles item gestures. * - * @param markerPlaceGroups The list of marker place groups containing the places and - * their bookmarked status + * @param markerPlaceGroups The list of marker place groups containing the places and their + * bookmarked status */ @Override public void replaceMarkerOverlays(final List markerPlaceGroups) { @@ -1913,7 +1969,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment for (int i = markerPlaceGroups.size() - 1; i >= 0; i--) { newMarkers.add( convertToMarker(markerPlaceGroups.get(i).getPlace(), - markerPlaceGroups.get(i).getIsBookmarked()) + markerPlaceGroups.get(i).getIsBookmarked()) ); } clearAllMarkers(); @@ -2103,7 +2159,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment if (binding.fabCamera.isShown()) { Timber.d("Camera button tapped. Place: %s", selectedPlace.toString()); storeSharedPrefs(selectedPlace); - controller.initiateCameraPick(getActivity(), inAppCameraLocationPermissionLauncher, cameraPickLauncherForResult); + controller.initiateCameraPick(getActivity(), inAppCameraLocationPermissionLauncher, + cameraPickLauncherForResult); } }); @@ -2121,7 +2178,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment if (binding.fabCustomGallery.isShown()) { Timber.d("Gallery button tapped. Place: %s", selectedPlace.toString()); storeSharedPrefs(selectedPlace); - controller.initiateCustomGalleryPickWithPermission(getActivity(), customSelectorLauncherForResult); + controller.initiateCustomGalleryPickWithPermission(getActivity(), + customSelectorLauncherForResult); } }); } @@ -2296,6 +2354,18 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment binding.map.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.map.getController().animateTo(geoPoint, zoom, speed); + } + @Override public void onBottomSheetItemClick(@Nullable View view, int position) { BottomSheetItem item = dataList.get(position); @@ -2309,7 +2379,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment updateBookmarkButtonImage(selectedPlace); break; case R.drawable.ic_directions_black_24dp: - Utils.handleGeoCoordinates(this.getContext(), selectedPlace.getLocation()); + Utils.handleGeoCoordinates(this.getContext(), selectedPlace.getLocation(), + binding.map.getZoomLevelDouble()); break; case R.drawable.ic_wikidata_logo_24dp: Utils.handleWebUrl(this.getContext(), selectedPlace.siteLinks.getWikidataLink()); diff --git a/app/src/main/java/fr/free/nrw/commons/quiz/QuizController.kt b/app/src/main/java/fr/free/nrw/commons/quiz/QuizController.kt index 3cb4f52a6..d9b6b7e52 100644 --- a/app/src/main/java/fr/free/nrw/commons/quiz/QuizController.kt +++ b/app/src/main/java/fr/free/nrw/commons/quiz/QuizController.kt @@ -14,11 +14,15 @@ class QuizController { private val quiz: ArrayList = ArrayList() - private val URL_FOR_SELFIE = "https://i.imgur.com/0fMYcpM.jpg" - private val URL_FOR_TAJ_MAHAL = "https://upload.wikimedia.org/wikipedia/commons/1/15/Taj_Mahal-03.jpg" - private val URL_FOR_BLURRY_IMAGE = "https://i.imgur.com/Kepb5jR.jpg" - private val URL_FOR_SCREENSHOT = "https://upload.wikimedia.org/wikipedia/commons/thumb/8/8b/Social_media_app_mockup_screenshot.svg/500px-Social_media_app_mockup_screenshot.svg.png" - private val URL_FOR_EVENT = "https://upload.wikimedia.org/wikipedia/commons/5/51/HouseBuildingInNorthernVietnam.jpg" + companion object{ + + const val URL_FOR_SELFIE = "https://i.imgur.com/0fMYcpM.jpg" + const val URL_FOR_TAJ_MAHAL = "https://upload.wikimedia.org/wikipedia/commons/1/15/Taj_Mahal-03.jpg" + const val URL_FOR_BLURRY_IMAGE = "https://i.imgur.com/Kepb5jR.jpg" + const val URL_FOR_SCREENSHOT = "https://upload.wikimedia.org/wikipedia/commons/thumb/8/8b/Social_media_app_mockup_screenshot.svg/500px-Social_media_app_mockup_screenshot.svg.png" + const val URL_FOR_EVENT = "https://upload.wikimedia.org/wikipedia/commons/5/51/HouseBuildingInNorthernVietnam.jpg" + } + fun initialize(context: Context) { val q1 = QuizQuestion( diff --git a/app/src/main/java/fr/free/nrw/commons/review/ReviewActivity.kt b/app/src/main/java/fr/free/nrw/commons/review/ReviewActivity.kt index 40eb24ed0..01a1005fe 100644 --- a/app/src/main/java/fr/free/nrw/commons/review/ReviewActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/review/ReviewActivity.kt @@ -45,12 +45,12 @@ class ReviewActivity : BaseActivity() { private var hasNonHiddenCategories = false var media: Media? = null - private val SAVED_MEDIA = "saved_media" + private val savedMedia = "saved_media" override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) media?.let { - outState.putParcelable(SAVED_MEDIA, it) + outState.putParcelable(savedMedia, it) } } @@ -90,8 +90,8 @@ class ReviewActivity : BaseActivity() { PorterDuff.Mode.SRC_IN ) - if (savedInstanceState?.getParcelable(SAVED_MEDIA) != null) { - updateImage(savedInstanceState.getParcelable(SAVED_MEDIA)!!) + if (savedInstanceState?.getParcelable(savedMedia) != null) { + updateImage(savedInstanceState.getParcelable(savedMedia)!!) setUpMediaDetailOnOrientation() } else { runRandomizer() @@ -188,7 +188,7 @@ class ReviewActivity : BaseActivity() { return } - binding.reviewImageView.setImageURI(media.imageUrl) + binding.reviewImageView.setImageURI(media.thumbUrl) reviewController.onImageRefreshed(media) // filename is updated compositeDisposable.add( diff --git a/app/src/main/java/fr/free/nrw/commons/review/ReviewImageFragment.kt b/app/src/main/java/fr/free/nrw/commons/review/ReviewImageFragment.kt index f6da76bba..2bc333505 100644 --- a/app/src/main/java/fr/free/nrw/commons/review/ReviewImageFragment.kt +++ b/app/src/main/java/fr/free/nrw/commons/review/ReviewImageFragment.kt @@ -31,7 +31,7 @@ class ReviewImageFragment : CommonsDaggerSupportFragment() { lateinit var sessionManager: SessionManager // Constant variable used to store user's key name for onSaveInstanceState method - private val SAVED_USER = "saved_user" + private val savedUser = "saved_user" // Variable that stores the value of user private var user: String? = null @@ -129,7 +129,7 @@ class ReviewImageFragment : CommonsDaggerSupportFragment() { question = getString(R.string.review_thanks) user = reviewActivity.reviewController.firstRevision?.user() - ?: savedInstanceState?.getString(SAVED_USER) + ?: savedInstanceState?.getString(savedUser) //if the user is null because of whatsoever reason, review will not be sent anyways if (!user.isNullOrEmpty()) { @@ -172,7 +172,7 @@ class ReviewImageFragment : CommonsDaggerSupportFragment() { override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) //Save user name when configuration changes happen - outState.putString(SAVED_USER, user) + outState.putString(savedUser, user) } private val reviewCallback: ReviewController.ReviewCallback diff --git a/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.kt b/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.kt index 2f293937c..161927d03 100644 --- a/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.kt +++ b/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.kt @@ -33,6 +33,7 @@ import com.karumi.dexter.MultiplePermissionsReport import com.karumi.dexter.PermissionToken import com.karumi.dexter.listener.PermissionRequest import com.karumi.dexter.listener.multi.MultiplePermissionsListener +import fr.free.nrw.commons.BuildConfig.MOBILE_META_URL import fr.free.nrw.commons.R import fr.free.nrw.commons.Utils import fr.free.nrw.commons.activity.SingleWebViewActivity @@ -85,7 +86,6 @@ class SettingsFragment : PreferenceFragmentCompat() { private var languageHistoryListView: ListView? = null private lateinit var inAppCameraLocationPermissionLauncher: ActivityResultLauncher> - private val GET_CONTENT_PICKER_HELP_URL = "https://commons-app.github.io/docs.html#get-content" private val cameraPickLauncherForResult: ActivityResultLauncher = registerForActivityResult(StartActivityForResult()) { result -> @@ -271,6 +271,7 @@ class SettingsFragment : PreferenceFragmentCompat() { findPreference("managed_exif_tags")?.isEnabled = false findPreference("openDocumentPhotoPickerPref")?.isEnabled = false findPreference("inAppCameraLocationPref")?.isEnabled = false + findPreference("vanishAccount")?.isEnabled = false } } @@ -511,6 +512,7 @@ class SettingsFragment : PreferenceFragmentCompat() { @Suppress("LongLine") companion object { + const val GET_CONTENT_PICKER_HELP_URL = "https://commons-app.github.io/docs.html#get-content" private const val VANISH_ACCOUNT_URL = "https://meta.m.wikimedia.org/wiki/Special:Contact/accountvanishapps" private const val VANISH_ACCOUNT_SUCCESS_URL = "https://meta.m.wikimedia.org/wiki/Special:GlobalVanishRequest/vanished" /** diff --git a/app/src/main/java/fr/free/nrw/commons/upload/ImageProcessingService.kt b/app/src/main/java/fr/free/nrw/commons/upload/ImageProcessingService.kt index ba9dc147c..fca10be1e 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/ImageProcessingService.kt +++ b/app/src/main/java/fr/free/nrw/commons/upload/ImageProcessingService.kt @@ -10,7 +10,6 @@ import fr.free.nrw.commons.utils.ImageUtils.IMAGE_KEEP import fr.free.nrw.commons.utils.ImageUtils.IMAGE_OK import fr.free.nrw.commons.utils.ImageUtilsWrapper import io.reactivex.Single -import io.reactivex.functions.Function import io.reactivex.schedulers.Schedulers import org.apache.commons.lang3.StringUtils import timber.log.Timber @@ -26,7 +25,7 @@ class ImageProcessingService @Inject constructor( private val fileUtilsWrapper: FileUtilsWrapper, private val imageUtilsWrapper: ImageUtilsWrapper, private val readFBMD: ReadFBMD, - private val EXIFReader: EXIFReader, + private val exifReader: EXIFReader, private val mediaClient: MediaClient ) { /** @@ -94,7 +93,7 @@ class ImageProcessingService @Inject constructor( * the presence of some basic Exif metadata. */ private fun checkEXIF(filepath: String): Single = - EXIFReader.processMetadata(filepath) + exifReader.processMetadata(filepath) /** diff --git a/app/src/main/java/fr/free/nrw/commons/utils/PermissionUtils.kt b/app/src/main/java/fr/free/nrw/commons/utils/PermissionUtils.kt index daf158fc1..df3b33bf6 100644 --- a/app/src/main/java/fr/free/nrw/commons/utils/PermissionUtils.kt +++ b/app/src/main/java/fr/free/nrw/commons/utils/PermissionUtils.kt @@ -16,6 +16,9 @@ import com.karumi.dexter.listener.PermissionRequest import com.karumi.dexter.listener.multi.MultiplePermissionsListener import fr.free.nrw.commons.R import fr.free.nrw.commons.upload.UploadActivity +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch object PermissionUtils { @@ -130,7 +133,7 @@ object PermissionUtils { vararg permissions: String ) { if (hasPartialAccess(activity)) { - Thread(onPermissionGranted).start() + CoroutineScope(Dispatchers.Main).launch { onPermissionGranted.run() } return } checkPermissionsAndPerformAction( @@ -166,13 +169,15 @@ object PermissionUtils { rationaleMessage: Int, vararg permissions: String ) { + val scope = CoroutineScope(Dispatchers.Main) + Dexter.withActivity(activity) .withPermissions(*permissions) .withListener(object : MultiplePermissionsListener { override fun onPermissionsChecked(report: MultiplePermissionsReport) { when { report.areAllPermissionsGranted() || hasPartialAccess(activity) -> - Thread(onPermissionGranted).start() + scope.launch { onPermissionGranted.run() } report.isAnyPermissionPermanentlyDenied -> { DialogUtil.showAlertDialog( activity, @@ -189,7 +194,7 @@ object PermissionUtils { null, null ) } - else -> Thread(onPermissionDenied).start() + else -> scope.launch { onPermissionDenied?.run() } } } diff --git a/app/src/main/java/fr/free/nrw/commons/utils/SwipableCardView.kt b/app/src/main/java/fr/free/nrw/commons/utils/SwipableCardView.kt index 5a8261c24..bde575386 100644 --- a/app/src/main/java/fr/free/nrw/commons/utils/SwipableCardView.kt +++ b/app/src/main/java/fr/free/nrw/commons/utils/SwipableCardView.kt @@ -21,9 +21,14 @@ abstract class SwipableCardView @JvmOverloads constructor( defStyleAttr: Int = 0 ) : CardView(context, attrs, defStyleAttr) { + + companion object{ + const val MINIMUM_THRESHOLD_FOR_SWIPE = 100f + } + private var x1 = 0f private var x2 = 0f - private val MINIMUM_THRESHOLD_FOR_SWIPE = 100f + init { interceptOnTouchListener() diff --git a/app/src/main/res/layout/fragment_custom_selector.xml b/app/src/main/res/layout/fragment_custom_selector.xml index bbc4c0a07..03381fd24 100644 --- a/app/src/main/res/layout/fragment_custom_selector.xml +++ b/app/src/main/res/layout/fragment_custom_selector.xml @@ -49,6 +49,20 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> + +

+ + + + \ No newline at end of file diff --git a/app/src/main/res/menu/nearby_fragment_menu.xml b/app/src/main/res/menu/nearby_fragment_menu.xml index fe049cde4..e7c23ed89 100644 --- a/app/src/main/res/menu/nearby_fragment_menu.xml +++ b/app/src/main/res/menu/nearby_fragment_menu.xml @@ -12,6 +12,12 @@ android:icon="@drawable/ic_list_white_24dp" /> + + خطأ في جلب المعالم القريبة.
لا توجد عمليات بحث حديثة هل أنت متأكد من أنك تريد مسح سجل بحثك؟ - هل انت متأكد انك تريد الغاء هذا التحميل + هل أنت متأكد أنك تريد إلغاء هذا التحميل؟ هل تريد حذف هذا البحث؟ تم حذف سجل البحث ترشيح للحذف @@ -882,4 +884,7 @@ الاختفاء هو <b>الملاذ الأخير</b> ويجب <b>استخدامه فقط عندما ترغب في التوقف عن التحرير إلى الأبد</b> وأيضًا لإخفاء أكبر عدد ممكن من ارتباطاتك السابقة.<br/><br/> يتم حذف الحساب على ويكيميديا كومنز عن طريق تغيير اسم حسابك بحيث لا يتمكن الآخرون من التعرف على مساهماتك في عملية تسمى اختفاء الحساب. <b>لا يضمن الاختفاء عدم الكشف عن الهوية تمامًا أو إزالة المساهمات في المشاريع</b> . الشرح تم نسخ التسمية التوضيحية إلى الحافظة + مبروك، جميع الصور الموجودة في هذا الألبوم تم تحميلها أو تم وضع علامة عليها بأنها غير قابلة للتحميل. + عرض في استكشاف + عرض في المناطق القريبة diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 4b17a0fec..232f56c49 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -820,4 +820,7 @@ Forsvinding er en <b>sidste udvej</b> og bør <b>kun bruges, når du for altid ønsker at stoppe med at redigere</b> og også for at skjule så mange af dine tidligere tilknytninger som muligt.<br/><br/> Kontosletning på Wikipedia Commons sker ved at ændre dit kontonavn, således at andre ikke kan genkende dine bidrag i en proces, der kaldes kontoforsvinding (Vanishing). <b>Forsvinding garanterer ikke fuldstændig anonymitet eller fjerner bidrag til projekterne</b> . Billedtekst Billedtekst kopieret til udklipsholder + Tillykke, alle billeder i dette album er enten blevet uploadet eller markeret som ikke til upload. + Vis i Udforsk + Vis i I nærheden diff --git a/app/src/main/res/values-diq/strings.xml b/app/src/main/res/values-diq/strings.xml index 836e8ef62..cf48861d0 100644 --- a/app/src/main/res/values-diq/strings.xml +++ b/app/src/main/res/values-diq/strings.xml @@ -349,4 +349,6 @@ Tayêna bıwane Şınasnayış Raçarne + Hesab + Bınnuşte diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 07021189f..5aeda5299 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -855,4 +855,6 @@ Utilisations du fichier Légende Légende copiée dans le presse-papier + Afficher dans Explorer + Afficher à proximité diff --git a/app/src/main/res/values-io/strings.xml b/app/src/main/res/values-io/strings.xml index 2efec0607..057e77e10 100644 --- a/app/src/main/res/values-io/strings.xml +++ b/app/src/main/res/values-io/strings.xml @@ -79,7 +79,7 @@ Vu atingis la maxim granda quanto di probi por sendar arkivo permisata! Voluntez interuptar la kargajo, e probez itere! Ka desmuntar l\'optimizo di la baterio? Sendar plu kam 3 imaji esas plu efikiva kande l\'optimizo di la baterio esas desmuntita. Voluntez desmuntar l\'optimizo di la baterio del ajusti dil utensilo de Commons, por plugrandigar l\'efikeso. \n\nQuale desmuntar l\'optimizo-sistemo di la baterio:\n\n#: Kliktez la butono \"ajusti\" (\'\'Settings\'\') adinfre.\n\n#: Selektez \"ne optimizita (\'\'Not optimized\'\') por omna utensili (\'\'All apps\'\').\n\n#: Serchez \"Commons\" o \"fr.free.nrw.commons\".\n\n#: Kliktez ol e selektez \"ne optimizar\" (\'\'Don\'t optimize\'\').\n\n#: Kliktez \"facita\" (\'\'Done\'\'). - L\'autentikigo faliis, voluntez itere enirar. + Autentikigo faliis. Voluntez itere enirar. Komencis sendar! Sendajo ajornata (modulo \"limitizita konekto\" aktiva) %1$s sendesis! @@ -100,17 +100,19 @@ Fotografar Proxime Mea sendaji + Kopiez ligilo + La ligilo kopiesis a \'\'clipboard\'\'. Partigar Vidar arkivo-pagino Titulo (Bezonata) Voluntez informar deskripto-texto por ca arkivo Deskripto Deskripto-texto - Ne esis posibla facar \'\'log - in\'\' - la reto faliis + Ne esas posibla enirar - la reto faliis Multa sensucesa probi pri konektar. Voluntez probar itere pos kelka minuti. Pardonez, ca uzero blokusesis che Commons Vu mustas uzar vua autentikigo en du etapi. - Eniro faliis + Eniro faliis Kargar Nomizes ca ajusto Modifikuri @@ -118,6 +120,7 @@ Serchar kategorii Serchez kozi quin vua \'\'media\'\' montras (monti, \'\'Taj Mahal\'\', edc.) Registragar + Menuo pri exterfluajo Rinovigar Listar (Nula arkivo sendita til nun) @@ -258,6 +261,7 @@ Konservar en vua enmagaziniguro la fotografuri obtenita uzanta fotografilo del utensilo (\'\'app\'\') Enirez en vua konto Sendez arkivo \'\'log\'\' + Sendez protokolo per e-posto a developeri, por helpar la solvo di problemi dil \'\'app\'\'. Atencez: protokoli povas kontenar informi por identifiko Nula retnavigilo trovita, por apertar la URL Eroro! URL ne trovita Propozar efaco @@ -266,11 +270,12 @@ Saltar Enirar Ka vu deziras ne enirar? - Vu mustus facar \'\'log in\'\' por sendor imaji future. + Future, vu mustus facar \'\'log in\'\' por sendar imaji. Voluntez enirar por uzar ca utensilo Kopiez Wiki-texto a \'clipboard\' Wiki-texto kopiesis a \'clipboard\' Proximeso povas ne funcionar korekte, nam Lokizo ne esas disponebla. + Interreto nedisponebla. Montranta nur elementi enmagazinigita lokale. Aceso a lokizo ne permisita. Voluntez informar manuale vua lokizo por uzar ca resurso*. Permiso bezonata por montrar listo pri vicina loki Permiso bezonata por montrar listo pri vicina imaji @@ -354,18 +359,22 @@ Efacar Sucesi Profilo + Insigni Statistiko Danki recevita Remarkinda imaji Imaji tra \"Loki Vicina\" - Nivelo + Nivelo %d + %s (Nivelo %s) Imaji sendita Imaji ne reversionita Imaji uzita Partigez vua sucesi kun vua amiki! + Vua nivelo augmentas kande vu atingas bezonata postuli. Elementi en la segmento \"statistiko\" ne augmentas vua nivelo. minima quanto bezonata: Quanto di imaji quin vu sendis a Commons, uzanta irga softwaro* por sendar li La procento di imaji quin vu sendis a Commons, qui ne efacesis pose + La quanto di imaji sendita da vu a Commons, qui uzesis en artikli de Wikimedia. Eventis eroro! Avizo de Commons Uzar personalizita autoro-nomo @@ -375,6 +384,7 @@ Vicina Avizi Avizi (lektita) + Montrez proxima avizo Listo Permiso pri enmagazinigo Etapo %1$d de %2$d: %3$s @@ -383,6 +393,8 @@ Arkivo kun la nomo %1$s ja existas. Ka vu deziras durigar?\n\nNoto: Sufixo adequata adjuntesos automatale a la nomo dil imajo. Imaji Loki + Kategorii + Adjuntez/Removez marko-rubandi (\'\'bookmark\'\'-i) Marko-rubandi Vu ne adjuntis marko-rubandi Marko-rubandi @@ -392,7 +404,11 @@ 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. + 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? + Nula deskripturo selektita Cesar kargajo Durar kargajo Serchez ca areo @@ -401,15 +417,38 @@ Ne pluse demandez to Demandar lokala permiso Demandez lokala permiso, kande bezonata por uzar karto montranta proximeso. + Finas la: + Montrez kampanii + Videz la kampanii duranta Permisar Eskartar + Facita + Sendanta danko: Suceso + Danko sendita sucese a %1$s + Faliis pri sendar danko a %1$s + Sendanta danko: Falio + Sendanta danko a %1$s + Ka to obedias la reguli pri autoroyuro? + Ka lua kategorio esas korekta? + Ka vu deziras dankar la kontributero? + Kliktez NO por indikar ca imajo por efaco, se ol ne havas irga utileso. + Ho, to ne mem havas kategorio! + Ca imajo havas %1$s kategorii. + Ol esas kontre la skopo, nam ol esas + To esas violaco di autoroyuro, nam ol esas Sequanta imajo Yes, pro quo ne? + Kliktanta ca butono donos a vu altra imajo recente sendita a Wikimedia Commons Vu povas revizar imaji, por plubonigar la qualeso di Wikimedia Commons.\nLa tri revizo-parametri esas:\n\n- Kad ica imajo havas havas irga relato kun la kuntexto?\nKande tu kliktas NO, vu adjuntos indiko (shablono) por ke ol efacesos.\n\n- Kad ica imajo violacas autoroyuro?\nSe tu klitos YES, vu adjuntos indiko por ke ol efacesos.\n\n- Kad la kategorii di ica imajo esas korekta?\nSe tu kliktos NO, vu adjuntos demando pri adjuntar korekta kategorio ad ol.\n\nSe omno esas korekta, nula shablono adjuntesos al imajo, e vu povos dankar la persono qua sendis ol. + Nula imajo uzita + Nula imajo desfacita + Nula imajo sendita Vu havas nul avizi sen lektar Vu ne lektis irga avizo + Verifikez vua e-postal adreso Vidar lektita Vidar ne-lektata + Eventis eroro dum selekto di imaji Vartez... Kopiita Exempli pri bona imaji por sendar a Commons @@ -431,7 +470,9 @@ Ne povis demandar efaco. komplete neklara Fotografuro de komunikilaro + Hazarda imajo de Interreto Emblemo + Brecho di Libereso di Panoramo Pro ke ol esas Probanta aktualigar kategorii. Aktualigo di kategorio @@ -442,7 +483,14 @@ Ne povis adjuntar kategorii. Aktualigar kategorii + Probanta aktualigar deskripturi. Redaktar deskripturi + + Deskripturo %1$s adjuntesis. + Deskripturi %1$s adjuntesis. + + Ne povis adjuntar deskripturi. + Probanta aktualigar koordinati. Aktualigo di koordinati Aktualigo di deskripturo Aktualigo di surskriburo @@ -451,8 +499,14 @@ Adjuntesis deskripturi. Surskriburo adjuntesis. Ne povis adjuntar koordinati. + Ne povis adjuntar deskripturi. + Ne povis adjuntar deskripturo. + Koordinati dil imajo ne aktualigesis + Ne povis obtenar deskripturi. + Redaktar deskripturi ed informo-texti Partigar imajo uzanta Vu ankore ne facis kontributaji + %s ankore ne facis irga kontributado Konto kreesis! Texto kopiita a \'\'clipboard\'\' Mesajo indikita kom \'lektita\' @@ -462,17 +516,21 @@ Bezonas fotografuro Tipo di lokizo: Ponto, muzeo, hotelo, edc. - Irgu ne funcionis dum \'\'log in\'\'. Vu mustos ridefinar vua pasovorto!! + Irgu faliis dum \'\'log in\'\'. Vu mustos ridefinar vua pasovorto!! \'\'MEDIA\'\' SUBKLASI KLASI PLU ABSTRAKTA Loko proxima trovesis - Ka to esas fotografuro pri %1$s? + Ka ca imaji apartenas a %1$s? + Ka to esas imajo di %1$s? Marko-rubandi Ajusti + Efacita de la marko-rubandi Adjuntita marko-rubandi + Irgu faliis. Ne povis vidar la muropapero Uzar kom skreno-kovrilo Kreanta skreno-kovrilo. Voluntez vartar... + Sequar sistemo Koloro obskura Koloro klara Charjez pluse @@ -492,6 +550,9 @@ Uzero Quanteso Uzar kom \'\'avatar\'\' di la tabelo pri precipua kunlaboranti + Ajusto di avataro + Eroro dum ajusto di nov avataro, voluntez probar itere + Uzar kom avataro Yare Semanale Sempre @@ -502,9 +563,16 @@ Imaji di qualeso Nuliganta sendajo... Cesar kargajo + Montras + Licencizo di \'\'media\'\' + Detali pri \'\'media\'\' + Vidar kategorio-pagino + Vidar pagino dil arkivo Lektez pluse En omna idiomi Selektez lokizo + Selektar lokizo + Montrar en l\'utensilo \'\'app\'\' di mapo Aktualigar lokizo Lokizo dil imajo Verifikez se la lokizo esas korekta @@ -524,8 +592,33 @@ Montrez monumenti SAVEZ PLUSE Bezonas permiso - Vidar uzeropagino + Kontributadi dil uzero: %s + Sucesi dil uzero: %s + Vidar profilo dil uzero + Redaktar deskripturi + Redaktar kategorii + Progresiva selektaji (advanced options) + Aplikar + Restaurar + 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 + Detali + nivelo di API + versiono di Android + Fabrikanto dil aparato + Modelo dil aparato + Nomo dil aparato + Tipo di reto + Danko por sendar vua opiniono + Eroro dum sendo di respondo + Qual es vua opiniono (feedback)? + Vua opiniono (\'\'feedback\'\') + Indikez por ne sendar ol + Itere indikez por sendar ol + Indikanta ke ol ne sendesos + Ca imajo ja sendesis + Ne povis selektar ca imajo por sendar (\'\'upload\'\') Imajo selektita Ca imajo indikesis por ne sendesar Raporto @@ -535,13 +628,49 @@ Avizar ca uzero Informar ca kontenajo Demandar blokuso di ca uzero + Uzez du fingri por augmentar o diminutar \'\'zoom\'\'. Koordinati ne esas l\'exakta, tamen l\'individuo qua sendis ca imajo kredas ke la koordinati quin lu informis esas suficante proxima. Modifikar imajo Aktualigar lokizo + Lokizo aktualigita! + Removar lokizo + Removar avizo pri lokizo + Lokizo efacita! Dankar l\'autoro Eroro sendanta danki al autoro. + La tempo-quanto por vua \'\'log in\'\' finis. Voluntez itere enirar. + Konservo sucesoza di arkivo + Ka vu deziras apertar arkivo GPX? + Ka vu deziras apartar l\'arkivo KML? + Faliis pri konservar arkivo KML. + Faliis pri konservar arkivo GPX. + Konservanta arqkivo KML + Konservanta arkivo GPX %d imajo selektita %d imaji selektita + Diskuto + Dicez irgu pri l\'arkivo \'%1$s\'. Ol esos videbla publike. + Arkivi sendita + Vartanta + Faliis + Ne povis inkluzar datumi pri la loko + Efacar faldilo + Konfirmez efaco + Ka vu deziras efacar faldilo %1$s, kontenanta %2$d arkivi? + Efacez + Nuligez + Faldilo %1$s sucese efacita + Faliis pri efacar faldilo %1$s + Commons + Altra wiki + Uzi dil arkivo + SingleWebViewActivity + Konto + Efacar konto + Avizo pri efaco di konto + Deskripto-texto + Deskripto-texto kopiita a \'\'clipboard\'\' + Gratuli! Omna imaji en ca albumo sive sendesis, sive indikesis por ne sendar. diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml index fd3bbb163..7f2d9a8f1 100644 --- a/app/src/main/res/values-iw/strings.xml +++ b/app/src/main/res/values-iw/strings.xml @@ -85,7 +85,7 @@ מתקבל תוכן שיתופי. עיבוד התמונות עשוי לארוך זמן מה כתלות בגודל התמונות והמכשיר שלך מתקבל תוכן שיתופי. עיבוד התמונות עשוי לארוך זמן מה כתלות בגודל התמונות והמכשיר שלך - לחקור + סיור מראה כללי משוב @@ -311,7 +311,7 @@ האינטרנט אינו זמין. מוצגים רק מקומות שמורים. הגישה למיקום נדחתה. נא להגדיר את המקום שלך ידנית כדי להשתמש ביכולת הזאת. נדרשת הרשאה כדי להציג רשימה של מקומות בסביבה - נדרשת הרשאה להצגת רשימת התמונות בסביבתך + נדרשת הרשאה כדי להציג רשימה של תמונות בסביבה כיוונים ויקינתונים ויקיפדיה @@ -605,12 +605,12 @@ קיים זקוק לתצלום סוג המקום: - גשר, מוזאון, מלון, וכו\'. + גשר, מוזאון, מלון וכו\'. משהו השתבש בכניסה למערכת, עליך לאפס את הסיסמה שלך! מדיה מחלקות יורשות מחלקות מורישות - נמצא בקרבת מקום + נמצא מקום בסביבה האם אלו תמונות של %1$s? האם זאת תמונה של %1$s? סימניות @@ -628,7 +628,7 @@ להפעיל מיקום? נא להפעיל את שירותי המיקום כדי שהיישום יוכל להציג את מיקומך הנוכחי פעולת \"בסביבה\" זקוקה לשירותי מיקומי פועלים כדי לעבוד כמו שצריך - חקירת המפה דורשת הרשאות מיקום כדי להציג תמונות בסביבתך + מפת \"סיור\" דורשת הרשאות מיקום כדי להציג תמונות בסביבתך יש להעניק הרשאת מיקום כדי להגדיר את המיקום אוטומטית. האם צילמת את שתי התמונות באותו המקום? האם ברצונך להשתמש בקו הרוחב וקו האורך של התמונה משמאל? לטעון עוד @@ -851,4 +851,7 @@ אזהרת העלמת חשבון כותרת הכותרת הועתקה ללוח + ברכותינו, כל התמונות באלבום הזה הועלו או שסומנו לא להעלאה. + בתצוגת סיור + בתצוגת בסביבה diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 37a90a327..8ef85d363 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -108,6 +108,8 @@ 写真を撮影 付近 自分のアップロード + リンクをコピー + リンクがクリップボードにコピーされました 共有 ファイルのページを表示 キャプション (必須) @@ -126,6 +128,7 @@ カテゴリを検索 アップロードする素材の被写体を検索(山岳、タージマハールなど) 保存 + オーバーフローメニュー 更新 一覧 (まだ何もアップロードされていません) @@ -313,6 +316,7 @@ 検索 最近の検索: 最近、検索したクエリ + 最近の言語クエリ カテゴリの読み込み中にエラーが発生しました。 描写の読み込み中にエラーが発生しました メデイア @@ -345,6 +349,7 @@ 不正解 このスクリーンショットをアップロードしてもよいですか? アプリをシェアする + 回転 付近の場所を読み込めません 最近の検索はまだありません 本当に検索履歴を消去しますか? @@ -355,11 +360,12 @@ 削除 貢献 プロフィール + バッジ 統計 受け取った感謝 秀逸な画像 「近くの場所」機能でアップロードした画像 - レベル + レベル %d アップロードした画像 却下されなかった画像 使用された画像 @@ -391,6 +397,7 @@ お使いの機器に適したアプリが見つかりませんでした。この機能を使用できる地図アプリをインストールしてください。 画像 位置 + カテゴリ ブックマークに追加/から削除 ブックマーク ブックマークは追加されていません @@ -642,7 +649,7 @@ 完了 戻る 権限が必要です - 利用者ページを表示 + 利用者プロフィールを表示 題材を編集する カテゴリを編集 適用 @@ -683,4 +690,17 @@ %d件の画像が選択されました + すべてのアップロードをキャンセルしています... + アップロード + 保留中 + 失敗しました + 削除 + キャンセル + コモンズ + その他のウィキ + アカウント + アカウント抹消 + アカウント抹消の警告 + キャプション + キャプションをクリップボードにコピーしました diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index cd974dc30..1e482148e 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -709,6 +709,7 @@ 이 파일을 사용하는 문서 계정 계정 버리기 + 버리기는 <b>최후의 수단</b>이며 <b>영원히 편집을 중단하고 싶을 때</b>와 가능한 한 많은 과거 연관성을 숨기고 싶을 때만 사용해야 합니다.<br/><br/>위키미디어 공용에서 계정을 삭제하려면 계정 이름을 변경하여 다른 사람이 기여한 내용을 알아볼 수 없도록 하는 계정 버리기라는 프로세스를 거쳐야 합니다. <b>버리기는 완전한 익명성을 보장하지 않으며 프로젝트에 수행한 기여를 제거하지 않습니다</b>. 캡션 캡션이 클립보드에 복사되었습니다 diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index 89a5af3ad..04c1f931f 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -96,6 +96,8 @@ Nufotografuoti Netoliese Mano įkėlimai + Kopijuoti nuorodą + Nuoroda nukopijuota į mainų sritį. Dalintis Žiūrėti failo puslapį Antraštė (būtina) @@ -345,11 +347,13 @@ Ištrinti Pasiekimai Profilis + Ženkliukai Statistika Gauta padėka Rinktiniai paveikslėliai Vaizdai per „Netoliese esančios vietos“ - Lygis + Lygis %d + %s (%s lygis) Vaizdai įkelti Paveikslėliai negrąžinti Naudoti vaizdai @@ -381,6 +385,7 @@ Jūsų įrenginyje nepavyko rasti suderinamos žemėlapio programos. Norėdami naudotis šia funkcija, įdiekite žemėlapio programą. Nuotraukos Vietos + Kategorijos Pridėti prie / pašalinti iš žymių Žymės Jūs nepridėjote jokių žymių @@ -751,4 +756,18 @@ Laukiama Nepavyko Nepavyko įkelti vietos duomenų + Trinti aplanką + Patvirtinti ištrynimą + Ar tikrai norite ištrinti aplanką %1$s, kuriame yra %2$d elementų? + Ištrinti + Atšaukti + Aplankas %1$s sėkmingai ištrintas + Nepavyko ištrinti aplanko %1$s + Klaida išsiunčiant į šiukšliadėžę aplanko turinį: %1$s + Įkeliant įvyko klaida + Naudojimo būdų nerasta + Vikiteka + Kiti viki + Failo naudojimas + Paskyra diff --git a/app/src/main/res/values-mk/strings.xml b/app/src/main/res/values-mk/strings.xml index 26abb3749..94e8f638b 100644 --- a/app/src/main/res/values-mk/strings.xml +++ b/app/src/main/res/values-mk/strings.xml @@ -817,4 +817,7 @@ Исчезнувањето е <b>крајна мерка</b> и треба да се користи само ако сакате да престанете да уредувате засекогаш/b> и да скриете што повеќе од вашите досегашни врски.<br/><br/>Бришењето сметки на Википедија се врши со менување на името на вашата сметка, така што другите не би можеле да ги препознаат вашите придонеси во постапка наречена „исчезнување“ на сметка.<b>Исчезнувањето не гарантира целосна анонимност и не ги отстранува придонесите на проектите</b>. Толкување Толкувањето е ставено во меѓускладот + Честитаме. Сите слики од овој албум се подигнати или обележани за неподигање. + Прикажи во „Истражи“ + Прикажи во „Во близина“ diff --git a/app/src/main/res/values-ml/strings.xml b/app/src/main/res/values-ml/strings.xml index 200ce0790..775a64bcc 100644 --- a/app/src/main/res/values-ml/strings.xml +++ b/app/src/main/res/values-ml/strings.xml @@ -3,13 +3,31 @@ * Adithyak1997 * Akhilan * Jacob.jose +* Jinoytommanjaly * Kiran Gopi * Praveenp * Santhosh.thottingal --> + കോമൺസ് ഫേസ്ബുക്ക് പേജ് + കോമൺസ് ജിത്ഹബ് സോഴ്സ് കോഡ് കോമൺസ് ലോഗോ കോമൺസ് വെബ്‌സൈറ്റ് + സമർപ്പിക്കുക + മറ്റൊരു വിവരണം ചേർക്കുക + പുതിയ സംഭാവന ചേർക്കുക + ക്യാമറയിൽ നിന്നുള്ള സംഭാവന ചേർക്കുക + ഫോട്ടോകളിൽ നിന്നുള്ള സംഭാവന ചേർക്കുക + മുമ്പത്തെ സംഭാവനകളുടെ ഗാലറിയിൽ നിന്നുള്ള സംഭാവന ചേർക്കുക + തലവാചകം + ഭാഷാ വിവരണം + തലവാചകം + വിവരണം + ചിത്രം + എല്ലാം + ടോഗിൾ അപ്പ് + തിരയൽ കാഴ്ച + ദിവസത്തെ ചിത്രം ഒരു പ്രമാണം അപ്‌ലോഡ് ചെയ്യുന്നു %1$d പ്രമാണങ്ങൾ അപ്‌ലോഡ് ചെയ്യുന്നു @@ -19,6 +37,7 @@ ഒരു അപ്‌ലോഡ് %1$d അപ്‌ലോഡുകൾ + അപ്‌ലോഡുകൾ ആരംഭിക്കുന്നു ഒരു അപ്‌ലോഡ് തുടങ്ങുന്നു %1$d അപ്‌ലോഡുകൾ തുടങ്ങുന്നു @@ -35,6 +54,8 @@ സ്വകാര്യത കോമൺസ് സജ്ജീകരണങ്ങൾ + കോമൺസിലേക്ക് അപ്‌ലോഡ് ചെയ്യുക + അപ്‌ലോഡ് പുരോഗമിക്കുന്നു ഉപയോക്തൃനാമം രഹസ്യവാക്ക് താങ്കളുടെ കോമൺസ് ബീറ്റ അംഗത്വത്തിൽ പ്രവേശിക്കുക @@ -43,9 +64,13 @@ അംഗത്വമെടുക്കുക പ്രവേശിക്കുന്നു ദയവായി കാത്തിരിക്കുക… - പ്രവേശനം വിജയകരം! - പ്രവേശനം പരാജയപ്പെട്ടു! + അടിക്കുറിപ്പുകളും വിവരണങ്ങളും അപ്ഡേറ്റ് ചെയ്യുന്നു + ദയവായി കാത്തിരിക്കുക… + പ്രവേശനം വിജയകരം! + പ്രവേശനം പരാജയപ്പെട്ടു! പ്രമാണം കണ്ടെത്താനായില്ല. ദയവായി മറ്റൊരു പ്രമാണം നോക്കുക. + പരമാവധി വീണ്ടും ശ്രമിക്കാനുള്ള പരിധി എത്തി! അപ്‌ലോഡ് റദ്ദാക്കി വീണ്ടും ശ്രമിക്കുക + ബാറ്ററി ഒപ്റ്റിമൈസേഷൻ ഓഫാക്കണോ? സാധുതാനിർണ്ണയം പരാജയപ്പെട്ടു, ദയവായി വീണ്ടും പ്രവേശിക്കുക അപ്‌ലോഡ് തുടങ്ങി! %1$s അപ്‌ലോഡ് ചെയ്തിരിക്കുന്നു! @@ -65,9 +90,14 @@ ചിത്രം എടുക്കുക സമീപസ്ഥം എന്റെ അപ്‌ലോഡുകൾ + ലിങ്ക് പകർത്തുക + ലിങ്ക് ക്ലിപ്പ്ബോർഡിലേക്ക് പകർത്തി പങ്ക് വെയ്ക്കുക + പ്രമാണ താൾ കാണുക അടിക്കുറിപ്പ് (നിർബന്ധം) + ദയവായി ഈ ഫയലിന് ഒരു അടിക്കുറിപ്പ് നൽകുക വിവരണം + തലവാചകം പ്രവേശിക്കാനായില്ല - നെറ്റ്‌വർക്ക് പരാജയപ്പെട്ടു നിരവധി വിജയകരമല്ലാത്ത ശ്രമങ്ങൾ നടന്നിരിക്കുന്നു. വീണ്ടും ശ്രമിക്കുന്നതിനു മുമ്പ് ഏതാനം മിനിറ്റുകൾ വിശ്രമിക്കുക. ക്ഷമിക്കുക, ഈ ഉപയോക്താവ് കോമൺസിൽ നിന്ന് തടയപ്പെട്ടിരിക്കുകയാണ് @@ -103,6 +133,7 @@ റദ്ദാക്കുക ഡൗൺലോഡ് സ്വതേയുള്ള ഉപയോഗാനുമതി + വിഷയം ആട്രിബ്യൂഷൻ-ഷെയർ‌എലൈക് 4.0 ആട്രിബ്യൂഷൻ 4.0 ആട്രിബ്യൂഷൻ-ഷെയർ‌എലൈക് 3.0 @@ -122,6 +153,7 @@ വർഗ്ഗങ്ങൾ ശേഖരിക്കുന്നു… ഒന്നും തിരഞ്ഞെടുത്തിട്ടില്ല + അടിക്കുറിപ്പില്ല വിവരണമൊന്നുമില്ല സംവാദങ്ങളില്ല അജ്ഞാതമായ അനുമതി diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index ae075e71a..11485b473 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -13,6 +13,7 @@ * Matma Rex * Mazab IZW * Olaf +* PanWor * Rail * Railfail536 * Rainbow P @@ -137,6 +138,8 @@ Zrób zdjęcie W pobliżu Wysłane przeze mnie pliki + Skopiuj link + Link został skopiowany do schowka Udostępnij Pokaż stronę pliku Podpis (wymagany) @@ -155,6 +158,7 @@ Szukaj kategorii Wyszukiwanie elementów, które przedstawiają twoje media (góra, Tadż Mahal itp.) Zapisz + Rozszerzone menu Odśwież Lista (Nie ma jeszcze przesłanych plików) @@ -311,6 +315,7 @@ Skopiuj wikitext do schowka Wikitext został skopiowany do schowka W pobliżu może nie działać poprawnie, Lokalizacja jest niedostępna. + Brak połączenia z internetem. Wyświetlane są tylko miejsca z pamięci podręcznej. Odmowa dostępu do lokalizacji. Aby skorzystać z tej funkcji, ustaw swoją lokalizację ręcznie. Uprawnienie wymagane do wyświetlania listy pobliskich miejsc Uprawnienie wymagane do wyświetlania listy pobliskich zdjęć @@ -394,11 +399,13 @@ Usuń Osiągnięcia Profil + Odznaki Statystyki Otrzymane Dzięki Wyróżnione ilustracje Obrazy za pośrednictwem \"Pobliskie miejsca\" - Poziom + Poziom %d + %s (Poziom %s) Przesłane obrazy Nie wycofane obrazy Wykorzystane obrazy @@ -465,6 +472,8 @@ Zezwól aplikacji na pobieranie lokalizacji, jeśli kamera jej nie rejestruje. Niektóre kamery urządzeń nie rejestrują lokalizacji. W takich przypadkach pozwolenie aplikacji na pobieranie i dołączanie do niej lokalizacji sprawia, że Twój wkład jest bardziej użyteczny. Możesz to zmienić w dowolnym momencie w Ustawieniach Zezwól Odrzuć + Zezwól na dostęp do lokalizacji w Ustawieniach, a następnie spróbuj ponownie.\n\nUwaga: Jeśli aplikacja nie będzie w stanie uzyskać danych o lokalizacji z urządzenia w krótkim czasie, przesłany plik może nie zawierać tych informacji. + Aparat w aplikacji potrzebuje uprawnień na dostęp do lokalizacji, aby dołączyć ją do zdjęć, jeśli nie jest dostępna w danych EXIF. Zezwól aplikacji na dostęp do lokalizacji, a następnie spróbuj ponownie.\n\nUwaga: Jeśli aplikacja nie będzie w stanie uzyskać danych o lokalizacji z urządzenia w krótkim czasie, przesłany plik może nie zawierać tych informacji. Upewnij się, że ten nowy selektor Androida nie usuwa lokalizacji ze zdjęć. Kampanie już nie będą widoczne. Jednak w razie potrzeby możesz ponownie włączyć to powiadomienie w ustawieniach. Ta funkcja wymaga połączenia sieciowego, sprawdź ustawienia połączenia. @@ -781,4 +790,6 @@ Chcesz otworzyć plik KML? Nie udało się zapisać pliku KML. Nie udało się zapisać pliku GPX. + Dyskusja + Usuń diff --git a/app/src/main/res/values-pms/strings.xml b/app/src/main/res/values-pms/strings.xml index 8c388d9ae..76a0be425 100644 --- a/app/src/main/res/values-pms/strings.xml +++ b/app/src/main/res/values-pms/strings.xml @@ -813,4 +813,7 @@ L\'eliminassion a l\'é <b>l\'ùltima arsorsa</b> e a dovrìa <b>esse dovrà mach si chiel a veul chité ëd modifiché për sempe</b> e ëdcò s\'a veul ëstërmé pi che possìbil soe assossiassion passà.<br/><br/>La dëscancelassion ëd cont su Wikimedia a l\'é fàita an modificand sò stranòm an manera che j\'àutri a peulo pa arconòsse soe contribussion ant un process ciamà dëscancelassion ëd cont. <b>La sparission a garantiss pa l\'anonimà complet ni a gava le contribussion dai proget</b>. Legenda Legenda copià an sla taulëtta + Congratulassion, tute le fòto ëd s\'àlbom a son ëstàita carià opura marcà coma da nen carié. + Smon-e andrinta a Explore + Smon-e andrinta a Nearby diff --git a/app/src/main/res/values-ps/strings.xml b/app/src/main/res/values-ps/strings.xml index 4f17da26f..2dc2366a8 100644 --- a/app/src/main/res/values-ps/strings.xml +++ b/app/src/main/res/values-ps/strings.xml @@ -15,7 +15,7 @@ دا انځور به د %1$s په منښتليک سمبال وي. ويکي خونديځ امستنې - کارن-نوم + کارن‌نوم پټنوم ننوتل نومليکنه @@ -88,4 +88,5 @@ امستنې غبرگون وتل + گڼون diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 0131fef5d..1c01761e0 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -333,6 +333,7 @@ Копирование викикода в буфер обмена Викикод скопирован в буфер обмена Функция «Поблизости» может работать некорректно, определение местоположения недоступно. + Интернет недоступен. Показаны только кэшированные места. Доступ к местоположению запрещён. Чтобы использовать эту функцию, укажите своё местоположение вручную. Необходимо разрешение для отображения списка мест поблизости Необходимо разрешение для отображения списка мест поблизости @@ -869,7 +870,9 @@ Викисклад Другие вики Использование файла + SingleWebViewActivity Учётная запись Подпись Подпись скопирована в буфер обмена + Поздравляем, все фотографии в этом альбоме либо загружены, либо помечены как не предназначенные для загрузки. diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index be06b0b99..371744b44 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -820,4 +820,5 @@ Att få kontot att försvinna är en <b>sista utväg</b> och bör <b>endast användas när du vill sluta redigera för alltid</b> och även dölja så många av dina tidigare associationer som möjligt.<br/><br/>Konton raderas på Wikimedia Commons genom att ändra kontonamnet för att göra så att andra inte kan känna igen bidragen i en process som kallas kontoförsvinnande. <b>Försvinnande garanterar inte fullständig anonymitet eller att bidrag tas bort från projekten</b>. Bildtext Bildtext kopierades till urklipp + Grattis! Alla bilder i detta album har antingen laddats upp eller markerats för att inte laddas upp. diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index 1ae869ec6..93757701c 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -48,6 +48,7 @@ * Yfdyh000 * Zhang8569 * ZhaoGang +* 七八年再来一次 * 予弦 * 佛壁灯 * 列维劳德 @@ -61,7 +62,7 @@ 共享资源Facebook页面 共享资源Github源代码 - 共享资源标志 + 共享资源标识 共享资源网站 退出位置选择器 提交 @@ -345,7 +346,7 @@ 跳过教程 互联网不可用 检索通知时出错 - 获取审查图片错误。按刷新键重试。 + 获取审查用图片错误。按刷新键重试。 找不到通知 翻译 语言 @@ -378,13 +379,13 @@ 通过移动端上传 地图 图片已添加到维基数据上的%1$s! - 更新对应维基数据实体失败! + 更新对应维基数据项目失败! 设为壁纸 壁纸已成功设置! 测验 这个图片可以上传吗? 问题 - 成绩 + 结果 如果您继续上传需要删除的图片,您的帐户可能会被封禁。你确定要结束测验吗? 您上传的图片超过%1$s已被删除。如果您继续上传需要删除的图片,您的帐户可能会被封禁。\n\n您是否希望再次查看该教程,然后进行测验以帮助您了解应该或不应上传的图像类型? 自拍没有太多的百科全书价值。除非您已经有关于您的维基百科文章,否则请不要上传自己的照片。 @@ -467,7 +468,7 @@ 由我自己上传在%1$s,使用于%2$d个条目。 欢迎使用共享资源!\n\n通过点击添加按钮以上传您的首个媒体。 未提交分类 - 没有类别的图像很少可用。确实要继续而不选择类别吗? + 不带分类的图片很难有机会被利用到,您确定您要不选择分类来继续吗? 没有选择描写 带有描述的图像更容易被发现并且更可能被使用。您确定不选择描述继续吗? 取消上传 @@ -477,12 +478,12 @@ 搜索这个区域 权限申请 您希望我们获取您当前的位置来显示最近的需要图片的地方吗? - 无法显示没有位置权限的需要图片的最近位置 + 没有位置权限,无法显示需要图片的最近地点 不再询问 申请位置权限 申请附近通知卡查看功能所必需的位置权限。 出了点问题,我们无法获取成就 - 您做出了许多贡献,我们的计算系统无法应对。这是最终的成就。 + 您做出的贡献多到让我们的成就计算系统无法应对,此为最终成果。 结束时间: 显示活动 显示正在进行的活动 @@ -558,7 +559,7 @@ 镜头型号 序列号 软件 - 已拒绝访问媒体位置 + 被拒绝访问媒体位置 我们可能无法自动从你上传的图片中获取位置数据。提交前请为每张图片添加适当的位置 直接在您手机上的维基共享资源应用中上传照片。立即下载共享资源应用:%1$s 分享到... @@ -799,7 +800,7 @@ 用两根手指放大和缩小。 快速长距离滑动来执行以下操作:\n- 向左/右:前往上一个/下一个\n- 向上:选择\n- 向下:标记为不上传 要设置你的排行榜头像,请点击任意图片上三点式菜单中的\"设置为头像\" - 图片坐标并不是准确的坐标,但上传这张图片的人认为它们足够接近。 + 坐标虽不准确,但上传这张图片的人认为已经足够接近了。 存储权限被拒绝 无法分享此项目 功能需要权限 @@ -828,7 +829,7 @@ 已选择%d个图像 已选择%d个图像 - 请记住,每次多图片上传会为其中的所有图片标注相同的分类和描述。如果这些图片并不共享同样的描述和分类,请分别进行多次上传。 + 请记住,多重上传中的所有图片都会标注相同的分类和描述。如果这些图片并不共享同样的描述和分类,请分别多次单独进行上传。 关于多图片上传的提醒 向维基数据报告关于该项的问题 请输入一些评论。 @@ -857,7 +858,16 @@ 这个地点已经有照片了。 现在检查这个地点是否有照片。 加载时出错 + 未找到用法 维基共享资源 其它wiki 文件用途 + 单一网页视图活动 + 账号 + 隐退账号 + 隐退账号警告 + 隐退是一个<b>最后的手段</b>,应该<b>仅在您希望永远停止编辑</b>,并尽可能隐藏您过去的关联时使用。<br/><br/>在维基媒体共享资源上删除账户是通过更改您的账户名称,使其他人无法识别您的贡献,这个过程称为账户隐退。<b>隐退并不能保证完全匿名,也无法删除对项目的贡献</b>。 + 说明 + 已复制到剪贴板 + 恭喜,专辑中的所有图片都已上传或标记为不上传。 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e7504df98..f9b16512f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -867,5 +867,8 @@ Upload your first media by tapping on the add button.
last resort and should only be used when you wish to stop editing forever and also to hide as many of your past associations as possible.

Account deletion on Wikimedia Commons is done by changing your account name to make it so others cannot recognize your contributions in a process called account vanishing. Vanishing does not guarantee complete anonymity or remove contributions to the projects.]]>
Caption Caption copied to clipboard + Congratulations, all pictures in this album have been either uploaded or marked as not for upload. + Show in Explore + Show in Nearby diff --git a/app/src/test/kotlin/fr/free/nrw/commons/category/CategoriesModelTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/category/CategoriesModelTest.kt index 8c336470a..21fdba2f5 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/category/CategoriesModelTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/category/CategoriesModelTest.kt @@ -11,6 +11,7 @@ import fr.free.nrw.commons.upload.GpsCategoryModel import io.reactivex.Single import io.reactivex.subjects.BehaviorSubject import media +import org.junit.Assert import org.junit.Before import org.junit.Test import org.mockito.ArgumentMatchers @@ -331,4 +332,42 @@ class CategoriesModelTest { media(), ) } + + @Test + fun `test valid input with XXXX in it between the expected range 20XX`() { + val input = categoriesModel.isSpammyCategory("Amavenita (ship, 2014)") + Assert.assertFalse(input) + } + + @Test + fun `test valid input with XXXXs in it between the expected range 20XXs`() { + val input = categoriesModel.isSpammyCategory("Amavenita (ship, 2014s)") + Assert.assertFalse(input) + } + + @Test + fun `test invalid category when have needing in the input`() { + val input = categoriesModel.isSpammyCategory("Media needing categories as of 30 March 2017") + Assert.assertTrue(input) + } + + @Test + fun `test invalid category when have taken on in the input`() { + val input = categoriesModel.isSpammyCategory("Photographs taken on 2015-12-08") + Assert.assertTrue(input) + } + + @Test + fun `test invalid category when have yy mm or yy mm dd in the input`() { + // filtering based on [., /, -] separators between the dates. + val input = categoriesModel.isSpammyCategory("Image class 09.14") + Assert.assertTrue(input) + } + + @Test + fun `test invalid category when have years not in 20XX range`() { + val input = categoriesModel.isSpammyCategory("Japan in the 1400s") + Assert.assertTrue(input) + } + } diff --git a/app/src/test/kotlin/fr/free/nrw/commons/explore/ExploreFragmentUnitTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/explore/ExploreFragmentUnitTest.kt index 41c999791..e2ef3bb92 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/explore/ExploreFragmentUnitTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/explore/ExploreFragmentUnitTest.kt @@ -11,16 +11,19 @@ import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentTransaction import androidx.test.core.app.ApplicationProvider import com.google.android.material.tabs.TabLayout +import com.nhaarman.mockitokotlin2.eq import fr.free.nrw.commons.OkHttpConnectionFactory import fr.free.nrw.commons.R import fr.free.nrw.commons.TestCommonsApplication import fr.free.nrw.commons.contributions.MainActivity import fr.free.nrw.commons.createTestClient import org.junit.Assert +import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor import org.mockito.Mock import org.mockito.Mockito.verify import org.mockito.Mockito.`when` @@ -34,6 +37,7 @@ import org.robolectric.annotation.LooperMode import org.robolectric.fakes.RoboMenu import org.robolectric.fakes.RoboMenuItem + @RunWith(RobolectricTestRunner::class) @Config(sdk = [21], application = TestCommonsApplication::class) @LooperMode(LooperMode.Mode.PAUSED) @@ -151,6 +155,14 @@ class ExploreFragmentUnitTest { Shadows.shadowOf(getMainLooper()).idle() val menu: Menu = RoboMenu(context) fragment.onCreateOptionsMenu(menu, inflater) - verify(inflater).inflate(R.menu.menu_search, menu) + + val captor = ArgumentCaptor.forClass( + Int::class.java + ) + verify(inflater).inflate(captor.capture(), eq(menu)) + + val capturedLayout = captor.value + assertTrue(capturedLayout == R.menu.menu_search || capturedLayout == R.menu.explore_fragment_menu) + } }