diff --git a/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.kt b/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.kt index 75c4ac26d..840bc7ca3 100644 --- a/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.kt @@ -65,6 +65,7 @@ class LoginActivity : AccountAuthenticatorActivity() { private val delegate: AppCompatDelegate by lazy { AppCompatDelegate.create(this, null) } + private var lastLoginResult: LoginResult? = null public override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -271,6 +272,7 @@ class LoginActivity : AccountAuthenticatorActivity() { showLoggingProgressBar() loginClient.doLogin(username, password, + lastLoginResult, twoFactorCode, Locale.getDefault().language, object : LoginCallback { @@ -280,9 +282,17 @@ class LoginActivity : AccountAuthenticatorActivity() { onLoginSuccess(loginResult) } - override fun twoFactorPrompt(caught: Throwable, token: String?) = runOnUiThread { + override fun twoFactorPrompt(loginResult: LoginResult, caught: Throwable, token: String?) = runOnUiThread { Timber.d("Requesting 2FA prompt") progressDialog!!.dismiss() + lastLoginResult = loginResult + askUserForTwoFactorAuth() + } + + override fun emailAuthPrompt(loginResult: LoginResult, caught: Throwable, token: String?) { + Timber.d("Requesting email auth prompt") + progressDialog!!.dismiss() + lastLoginResult = loginResult askUserForTwoFactorAuth() } @@ -341,12 +351,13 @@ class LoginActivity : AccountAuthenticatorActivity() { progressDialog!!.dismiss() with(binding!!) { twoFactorContainer.visibility = View.VISIBLE + twoFactorContainer.hint = getString(if (lastLoginResult is LoginResult.EmailAuthResult) R.string.email_auth_code else R.string._2fa_code) loginTwoFactor.visibility = View.VISIBLE loginTwoFactor.requestFocus() } val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY) - showMessageAndCancelDialog(R.string.login_failed_2fa_needed) + showMessageAndCancelDialog(getString(if (lastLoginResult is LoginResult.EmailAuthResult) R.string.login_failed_email_auth_needed else R.string.login_failed_2fa_needed)) } @VisibleForTesting diff --git a/app/src/main/java/fr/free/nrw/commons/auth/csrf/CsrfTokenClient.kt b/app/src/main/java/fr/free/nrw/commons/auth/csrf/CsrfTokenClient.kt index f35e5f003..6353e54ac 100644 --- a/app/src/main/java/fr/free/nrw/commons/auth/csrf/CsrfTokenClient.kt +++ b/app/src/main/java/fr/free/nrw/commons/auth/csrf/CsrfTokenClient.kt @@ -32,7 +32,7 @@ class CsrfTokenClient( try { if (retry > 0) { // Log in explicitly - loginClient.loginBlocking(userName, password, "") + loginClient.loginBlocking(userName, password) } // Get CSRFToken response off the main thread. @@ -92,6 +92,8 @@ class CsrfTokenClient( override fun failure(caught: Throwable?) = retryWithLogin(cb) { caught } override fun twoFactorPrompt() = cb.twoFactorPrompt() + + override fun emailAuthPrompt() = cb.emailAuthPrompt() }, ) @@ -165,10 +167,17 @@ class CsrfTokenClient( } override fun twoFactorPrompt( + loginResult: LoginResult, caught: Throwable, token: String?, ) = callback.twoFactorPrompt() + override fun emailAuthPrompt( + loginResult: LoginResult, + caught: Throwable, + token: String?, + ) = callback.emailAuthPrompt() + // Should not happen here, but call the callback just in case. override fun passwordResetPrompt(token: String?) = callback.failure(LoginFailedException("Logged in with temporary password.")) @@ -190,6 +199,8 @@ class CsrfTokenClient( fun failure(caught: Throwable?) fun twoFactorPrompt() + + fun emailAuthPrompt() } companion object { diff --git a/app/src/main/java/fr/free/nrw/commons/auth/login/LoginCallback.kt b/app/src/main/java/fr/free/nrw/commons/auth/login/LoginCallback.kt index 8092f73ae..8aa3d17a0 100644 --- a/app/src/main/java/fr/free/nrw/commons/auth/login/LoginCallback.kt +++ b/app/src/main/java/fr/free/nrw/commons/auth/login/LoginCallback.kt @@ -4,6 +4,13 @@ interface LoginCallback { fun success(loginResult: LoginResult) fun twoFactorPrompt( + loginResult: LoginResult, + caught: Throwable, + token: String?, + ) + + fun emailAuthPrompt( + loginResult: LoginResult, caught: Throwable, token: String?, ) diff --git a/app/src/main/java/fr/free/nrw/commons/auth/login/LoginClient.kt b/app/src/main/java/fr/free/nrw/commons/auth/login/LoginClient.kt index 2a799c847..a653b8b55 100644 --- a/app/src/main/java/fr/free/nrw/commons/auth/login/LoginClient.kt +++ b/app/src/main/java/fr/free/nrw/commons/auth/login/LoginClient.kt @@ -1,6 +1,7 @@ package fr.free.nrw.commons.auth.login import android.text.TextUtils +import fr.free.nrw.commons.auth.login.LoginResult.EmailAuthResult import fr.free.nrw.commons.auth.login.LoginResult.OAuthResult import fr.free.nrw.commons.auth.login.LoginResult.ResetPasswordResult import fr.free.nrw.commons.wikidata.WikidataConstants.WIKIPEDIA_URL @@ -51,6 +52,7 @@ class LoginClient( password, null, null, + null, response.body()!!.query()!!.loginToken(), userLanguage, cb, @@ -75,6 +77,7 @@ class LoginClient( password: String, retypedPassword: String?, twoFactorCode: String?, + emailAuthCode: String?, loginToken: String?, userLanguage: String, cb: LoginCallback, @@ -82,7 +85,7 @@ class LoginClient( this.userLanguage = userLanguage loginCall = - if (twoFactorCode.isNullOrEmpty() && retypedPassword.isNullOrEmpty()) { + if (twoFactorCode.isNullOrEmpty() && emailAuthCode.isNullOrEmpty() && retypedPassword.isNullOrEmpty()) { loginInterface.postLogIn(userName, password, loginToken, userLanguage, WIKIPEDIA_URL) } else { loginInterface.postLogIn( @@ -90,6 +93,7 @@ class LoginClient( password, retypedPassword, twoFactorCode, + emailAuthCode, loginToken, userLanguage, true, @@ -112,10 +116,18 @@ class LoginClient( when (loginResult) { is OAuthResult -> cb.twoFactorPrompt( + loginResult, LoginFailedException(loginResult.message), loginToken, ) + is EmailAuthResult -> + cb.emailAuthPrompt( + loginResult, + LoginFailedException(loginResult.message), + loginToken + ) + is ResetPasswordResult -> cb.passwordResetPrompt(loginToken) is LoginResult.Result -> @@ -147,6 +159,7 @@ class LoginClient( fun doLogin( username: String, password: String, + lastLoginResult: LoginResult?, twoFactorCode: String, userLanguage: String, loginCallback: LoginCallback, @@ -159,7 +172,10 @@ class LoginClient( ) = if (response.isSuccessful) { val loginToken = response.body()?.query()?.loginToken() loginToken?.let { - login(username, password, null, twoFactorCode, it, userLanguage, loginCallback) + login(username, password, null, + if (lastLoginResult is OAuthResult) twoFactorCode else null, + if (lastLoginResult is EmailAuthResult) twoFactorCode else null, + it, userLanguage, loginCallback) } ?: run { loginCallback.error(IOException("Failed to retrieve login token")) } @@ -181,7 +197,8 @@ class LoginClient( fun loginBlocking( userName: String, password: String, - twoFactorCode: String?, + twoFactorCode: String? = null, + emailAuthCode: String? = null ) { val tokenResponse = getLoginToken().execute() if (tokenResponse @@ -195,7 +212,7 @@ class LoginClient( val loginToken = tokenResponse.body()?.query()?.loginToken() val tempLoginCall = - if (twoFactorCode.isNullOrEmpty()) { + if (twoFactorCode.isNullOrEmpty() && emailAuthCode.isNullOrEmpty()) { loginInterface.postLogIn(userName, password, loginToken, userLanguage, WIKIPEDIA_URL) } else { loginInterface.postLogIn( @@ -203,6 +220,7 @@ class LoginClient( password, null, twoFactorCode, + emailAuthCode, loginToken, userLanguage, true, @@ -214,7 +232,7 @@ class LoginClient( val loginResult = loginResponse.toLoginResult(password) ?: throw IOException("Unexpected response when logging in.") if ("UI" == loginResult.status) { - if (loginResult is OAuthResult) { + if (loginResult is OAuthResult || loginResult is EmailAuthResult) { // TODO: Find a better way to boil up the warning about 2FA throw LoginFailedException(loginResult.message) } diff --git a/app/src/main/java/fr/free/nrw/commons/auth/login/LoginInterface.kt b/app/src/main/java/fr/free/nrw/commons/auth/login/LoginInterface.kt index 07e1cd45c..39cbf7c9f 100644 --- a/app/src/main/java/fr/free/nrw/commons/auth/login/LoginInterface.kt +++ b/app/src/main/java/fr/free/nrw/commons/auth/login/LoginInterface.kt @@ -35,7 +35,8 @@ interface LoginInterface { @Field("password") pass: String?, @Field("retype") retypedPass: String?, @Field("OATHToken") twoFactorCode: String?, - @Field("logintoken") token: String?, + @Field("token") emailAuthToken: String?, + @Field("logintoken") loginToken: String?, @Field("uselang") userLanguage: String?, @Field("logincontinue") loginContinue: Boolean, ): Call diff --git a/app/src/main/java/fr/free/nrw/commons/auth/login/LoginResponse.kt b/app/src/main/java/fr/free/nrw/commons/auth/login/LoginResponse.kt index a96778e38..0fb035eea 100644 --- a/app/src/main/java/fr/free/nrw/commons/auth/login/LoginResponse.kt +++ b/app/src/main/java/fr/free/nrw/commons/auth/login/LoginResponse.kt @@ -2,6 +2,7 @@ package fr.free.nrw.commons.auth.login import com.google.gson.annotations.SerializedName import fr.free.nrw.commons.auth.login.LoginResult.OAuthResult +import fr.free.nrw.commons.auth.login.LoginResult.EmailAuthResult import fr.free.nrw.commons.auth.login.LoginResult.ResetPasswordResult import fr.free.nrw.commons.auth.login.LoginResult.Result import fr.free.nrw.commons.wikidata.mwapi.MwServiceError @@ -27,11 +28,13 @@ internal class ClientLogin { fun toLoginResult(password: String): LoginResult { var userMessage = message if ("UI" == status) { - if (requests != null) { - for (req in requests) { - if ("MediaWiki\\Extension\\OATHAuth\\Auth\\TOTPAuthenticationRequest" == req.id()) { + requests?.forEach { request -> + request.id()?.let { + if (it.endsWith("TOTPAuthenticationRequest")) { return OAuthResult(status, userName, password, message) - } else if ("MediaWiki\\Auth\\PasswordAuthenticationRequest" == req.id()) { + } else if (it.endsWith("EmailAuthAuthenticationRequest")) { + return EmailAuthResult(status, userName, password, message) + } else if (it.endsWith("PasswordAuthenticationRequest")) { return ResetPasswordResult(status, userName, password, message) } } @@ -49,7 +52,7 @@ internal class Request { private val required: String? = null private val provider: String? = null private val account: String? = null - private val fields: Map? = null + internal val fields: Map? = null fun id(): String? = id } @@ -57,5 +60,5 @@ internal class Request { internal class RequestField { private val type: String? = null private val label: String? = null - private val help: String? = null + internal val help: String? = null } diff --git a/app/src/main/java/fr/free/nrw/commons/auth/login/LoginResult.kt b/app/src/main/java/fr/free/nrw/commons/auth/login/LoginResult.kt index 6a7594ec0..99abaeeec 100644 --- a/app/src/main/java/fr/free/nrw/commons/auth/login/LoginResult.kt +++ b/app/src/main/java/fr/free/nrw/commons/auth/login/LoginResult.kt @@ -24,6 +24,13 @@ sealed class LoginResult( message: String?, ) : LoginResult(status, userName, password, message) + class EmailAuthResult( + status: String, + userName: String?, + password: String?, + message: String?, + ) : LoginResult(status, userName, password, message) + class ResetPasswordResult( status: String, userName: String?, 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 f5657dd1b..60758ac20 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 @@ -775,7 +775,11 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment * @param nearbyBaseMarker The NearbyBaseMarker object representing the marker to be removed. */ private void removeMarker(BaseMarker nearbyBaseMarker) { - Place place = nearbyBaseMarker.getPlace(); + if (nearbyBaseMarker == null || nearbyBaseMarker.getPlace().getName() == null) { + return; + } + + String target = nearbyBaseMarker.getPlace().getName(); List overlays = binding.mapView.getOverlays(); ItemizedOverlayWithFocus item; @@ -784,8 +788,7 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment item = (ItemizedOverlayWithFocus) overlays.get(i); OverlayItem overlayItem = item.getItem(0); - if (place.location.getLatitude() == overlayItem.getPoint().getLatitude() - && place.location.getLongitude() == overlayItem.getPoint().getLongitude()) { + if (overlayItem.getTitle().equals(target)) { binding.mapView.getOverlays().remove(i); binding.mapView.invalidate(); break; diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.kt b/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.kt index 020284934..ee0b21210 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.kt @@ -14,6 +14,7 @@ import android.os.Bundle import android.provider.Settings import android.view.View import android.widget.CheckBox +import androidx.activity.OnBackPressedCallback import androidx.appcompat.app.AlertDialog import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager @@ -122,7 +123,7 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C /** * Set the value of the showPermissionDialog variable. * - * @param showPermissionsDialog `true` to indicate to show + * @property isShowPermissionsDialog `true` to indicate to show * Permissions Dialog if permissions are missing, `false` otherwise. */ /** @@ -166,6 +167,8 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C private var _binding: ActivityUploadBinding? = null private val binding: ActivityUploadBinding get() = _binding!! + private lateinit var onBackPressedCallback: OnBackPressedCallback + @SuppressLint("CheckResult") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -173,6 +176,23 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C _binding = ActivityUploadBinding.inflate(layoutInflater) setContentView(binding.root) + // Overrides the back button to make sure the user is prepared to lose their progress + onBackPressedCallback = object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + showAlertDialog( + this@UploadActivity, + getString(R.string.back_button_warning), + getString(R.string.back_button_warning_desc), + getString(R.string.back_button_continue), + getString(R.string.back_button_warning), + null + ) { + finish() + } + } + } + onBackPressedDispatcher.addCallback(this, onBackPressedCallback) + /* If Configuration of device is changed then get the new fragments created by the system and populate the fragments ArrayList @@ -187,7 +207,7 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C } init() - binding.rlContainerTitle.setOnClickListener { v: View? -> onRlContainerTitleClicked() } + binding.rlContainerTitle.setOnClickListener { _: View? -> onRlContainerTitleClicked() } nearbyPopupAnswers = mutableMapOf() //getting the current dpi of the device and if it is less than 320dp i.e. overlapping //threshold, thumbnails automatically minimizes @@ -201,7 +221,7 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C } locationManager!!.requestLocationUpdatesFromProvider(LocationManager.GPS_PROVIDER) locationManager!!.requestLocationUpdatesFromProvider(LocationManager.NETWORK_PROVIDER) - store = BasicKvStore(this, storeNameForCurrentUploadImagesSize).apply { + store = BasicKvStore(this, STORE_NAME_FOR_CURRENT_UPLOAD_IMAGE_SIZE).apply { clearAll() } checkStoragePermissions() @@ -241,7 +261,7 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C override fun onPageSelected(position: Int) { currentSelectedPosition = position - if (position >= uploadableFiles!!.size) { + if (position >= uploadableFiles.size) { binding.cvContainerTopCard.visibility = View.GONE } else { thumbnailsAdapter!!.notifyDataSetChanged() @@ -274,7 +294,7 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .filter { result: Boolean? -> result!! } - .subscribe { result: Boolean? -> + .subscribe { _: Boolean? -> showAlertDialog( this, getString(R.string.block_notification_title), @@ -284,7 +304,7 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C }) } - fun checkStoragePermissions() { + private fun checkStoragePermissions() { // Check if all required permissions are granted val hasAllPermissions = hasPermission(this, PERMISSIONS_STORAGE) val hasPartialAccess = hasPartialAccess(this) @@ -355,7 +375,7 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C showLongToast(this, messageResourceId) } - override fun getUploadableFiles(): List? { + override fun getUploadableFiles(): List { return uploadableFiles } @@ -367,6 +387,14 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C override fun onUploadMediaDeleted(index: Int) { fragments!!.removeAt(index) //Remove the corresponding fragment uploadableFiles.removeAt(index) //Remove the files from the list + + val isMediaDetailFragment = fragments!!.getOrNull(currentSelectedPosition)?.let { + it is UploadMediaDetailFragment + } ?: false + if(!isMediaDetailFragment) { + // Should hide the top card current fragment is not the media detail fragment + showHideTopCard(false) + } thumbnailsAdapter!!.notifyItemRemoved(index) //Notify the thumbnails adapter uploadImagesAdapter!!.notifyDataSetChanged() //Notify the ViewPager } @@ -375,8 +403,8 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C binding.tvTopCardTitle.text = resources .getQuantityString( R.plurals.upload_count_title, - uploadableFiles!!.size, - uploadableFiles!!.size + uploadableFiles.size, + uploadableFiles.size ) } @@ -444,15 +472,16 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C receiveInternalSharedItems() } - if (uploadableFiles == null || uploadableFiles!!.isEmpty()) { + if (uploadableFiles.isEmpty()) { handleNullMedia() } else { //Show thumbnails - if (uploadableFiles!!.size > 1) { - if (!defaultKvStore.getBoolean("hasAlreadyLaunchedCategoriesDialog")) { //If there is only file, no need to show the image thumbnails + if (uploadableFiles.size > 1) { + if (!defaultKvStore.getBoolean("hasAlreadyLaunchedCategoriesDialog")) { + // If there is only file, no need to show the image thumbnails showAlertDialogForCategories() } - if (uploadableFiles!!.size > 3 && + if (uploadableFiles.size > 3 && !defaultKvStore.getBoolean("hasAlreadyLaunchedBigMultiupload") ) { showAlertForBattery() @@ -464,8 +493,8 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C binding.tvTopCardTitle.text = resources .getQuantityString( R.plurals.upload_count_title, - uploadableFiles!!.size, - uploadableFiles!!.size + uploadableFiles.size, + uploadableFiles.size ) @@ -474,7 +503,7 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C } - for (uploadableFile in uploadableFiles!!) { + for (uploadableFile in uploadableFiles) { val uploadMediaDetailFragment = UploadMediaDetailFragment() if (!uploadIsOfAPlace) { @@ -497,8 +526,8 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C object : UploadMediaDetailFragmentCallback { override fun deletePictureAtIndex(index: Int) { store!!.putInt( - keyForCurrentUploadImagesSize, - (store!!.getInt(keyForCurrentUploadImagesSize) - 1) + KEY_FOR_CURRENT_UPLOAD_IMAGE_SIZE, + (store!!.getInt(KEY_FOR_CURRENT_UPLOAD_IMAGE_SIZE) - 1) ) presenter!!.deletePictureAtIndex(index) } @@ -576,11 +605,11 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C fragments!!.add(mediaLicenseFragment!!) } else { for (i in 1 until fragments!!.size) { - fragments!![i]!!.callback = object : UploadBaseFragment.Callback { + fragments!![i].callback = object : UploadBaseFragment.Callback { override fun onNextButtonClicked(index: Int) { if (index < fragments!!.size - 1) { binding.vpUpload.setCurrentItem(index + 1, false) - fragments!![index + 1]!!.onBecameVisible() + fragments!![index + 1].onBecameVisible() (binding.rvThumbnails.layoutManager as LinearLayoutManager) .scrollToPositionWithOffset( if ((index > 0)) index - 1 else 0, @@ -594,7 +623,7 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C override fun onPreviousButtonClicked(index: Int) { if (index != 0) { binding.vpUpload.setCurrentItem(index - 1, true) - fragments!![index - 1]!!.onBecameVisible() + fragments!![index - 1].onBecameVisible() (binding.rvThumbnails.layoutManager as LinearLayoutManager) .scrollToPositionWithOffset( if ((index > 3)) index - 2 else 0, @@ -632,11 +661,12 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C binding.vpUpload.offscreenPageLimit = fragments!!.size } // Saving size of uploadableFiles - store!!.putInt(keyForCurrentUploadImagesSize, uploadableFiles!!.size) + store!!.putInt(KEY_FOR_CURRENT_UPLOAD_IMAGE_SIZE, uploadableFiles.size) } /** - * Changes current image when one image upload is cancelled, to highlight next image in the top thumbnail. + * Changes current image when one image upload is cancelled, to highlight next image in the top + * thumbnail. * Fixes: [Issue](https://github.com/commons-app/apps-android-commons/issues/5511) * * @param index Index of image to be removed @@ -771,7 +801,7 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C override fun onNextButtonClicked(index: Int) { if (index < fragments!!.size - 1) { binding.vpUpload.setCurrentItem(index + 1, false) - fragments!![index + 1]!!.onBecameVisible() + fragments!![index + 1].onBecameVisible() (binding.rvThumbnails.layoutManager as LinearLayoutManager) .scrollToPositionWithOffset(if ((index > 0)) index - 1 else 0, 0) if (index < fragments!!.size - 4) { @@ -786,10 +816,10 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C override fun onPreviousButtonClicked(index: Int) { if (index != 0) { binding.vpUpload.setCurrentItem(index - 1, true) - fragments!![index - 1]!!.onBecameVisible() + fragments!![index - 1].onBecameVisible() (binding.rvThumbnails.layoutManager as LinearLayoutManager) .scrollToPositionWithOffset(if ((index > 3)) index - 2 else 0, 0) - if ((index != 1) && ((index - 1) < uploadableFiles!!.size)) { + if ((index != 1) && ((index - 1) < uploadableFiles.size)) { // Shows the top card if it was hidden because of the last image being deleted and // now the user has hit previous button to go back to the media details showHideTopCard(true) @@ -797,7 +827,10 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C } } - override fun onThumbnailDeleted(position: Int) = presenter!!.deletePictureAtIndex(position) + override fun onThumbnailDeleted(position: Int) { + presenter!!.deletePictureAtIndex(position) + thumbnailsAdapter?.notifyDataSetChanged() + } /** * The adapter used to show image upload intermediate fragments @@ -824,11 +857,11 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C } - fun onRlContainerTitleClicked() { + private fun onRlContainerTitleClicked() { binding.rvThumbnails.visibility = if (isTitleExpanded) View.GONE else View.VISIBLE isTitleExpanded = !isTitleExpanded - binding.ibToggleTopCard.rotation = binding.ibToggleTopCard.rotation + 180 + binding.ibToggleTopCard.rotation += 180 } override fun onDestroy() { @@ -845,21 +878,7 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C if (uploadCategoriesFragment != null) { uploadCategoriesFragment!!.callback = null } - } - - /** - * Overrides the back button to make sure the user is prepared to lose their progress - */ - @SuppressLint("MissingSuperCall") - override fun onBackPressed() { - showAlertDialog( - this, - getString(R.string.back_button_warning), - getString(R.string.back_button_warning_desc), - getString(R.string.back_button_continue), - getString(R.string.back_button_warning), - null - ) { finish() } + onBackPressedCallback.remove() } /** @@ -879,7 +898,7 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C .setView(view) .setTitle(getString(R.string.multiple_files_depiction_header)) .setMessage(getString(R.string.multiple_files_depiction)) - .setPositiveButton("OK") { dialog: DialogInterface?, which: Int -> + .setPositiveButton("OK") { _: DialogInterface?, _: Int -> if (checkBox.isChecked) { // Save the user's choice to not show the dialog again defaultKvStore.putBoolean("hasAlreadyLaunchedCategoriesDialog", true) @@ -913,14 +932,14 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C getString(R.string.cancel), { /* Since opening the right settings page might be device dependent, using - https://github.com/WaseemSabir/BatteryPermissionHelper - directly appeared like a promising idea. - However, this simply closed the popup and did not make - the settings page appear on a Pixel as well as a Xiaomi device. - Used the standard intent instead of using this library as - it shows a list of all the apps on the device and allows users to - turn battery optimisation off. - */ + https://github.com/WaseemSabir/BatteryPermissionHelper + directly appeared like a promising idea. + However, this simply closed the popup and did not make + the settings page appear on a Pixel as well as a Xiaomi device. + Used the standard intent instead of using this library as + it shows a list of all the apps on the device and allows users to + turn battery optimisation off. + */ val batteryOptimisationSettingsIntent = Intent( Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS ) @@ -958,7 +977,8 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C Also, location information is discarded if the difference between current location and location recorded just before capturing the image is greater than 100 meters */ - if (isLocationTagUnchecked || locationDifference > 100 || !defaultKvStore.getBoolean("inAppCameraLocationPref") + if (isLocationTagUnchecked || locationDifference > 100 + || !defaultKvStore.getBoolean("inAppCameraLocationPref") || !isInAppCameraUpload ) { currLocation = null @@ -979,8 +999,8 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C @JvmField var nearbyPopupAnswers: MutableMap? = null - const val keyForCurrentUploadImagesSize: String = "CurrentUploadImagesSize" - const val storeNameForCurrentUploadImagesSize: String = "CurrentUploadImageQualities" + const val KEY_FOR_CURRENT_UPLOAD_IMAGE_SIZE: String = "CurrentUploadImagesSize" + const val STORE_NAME_FOR_CURRENT_UPLOAD_IMAGE_SIZE: String = "CurrentUploadImageQualities" /** * Sets the flag indicating whether the upload is of a specific place. diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadPresenter.kt b/app/src/main/java/fr/free/nrw/commons/upload/UploadPresenter.kt index 9ee8fb483..5d721f408 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/UploadPresenter.kt +++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadPresenter.kt @@ -146,34 +146,31 @@ class UploadPresenter @Inject internal constructor( override fun deletePictureAtIndex(index: Int) { val uploadableFiles = view.getUploadableFiles() - if (index == uploadableFiles!!.size - 1) { - // If the next fragment to be shown is not one of the MediaDetailsFragment - // lets hide the top card so that it doesn't appear on the other fragments - view.showHideTopCard(false) - } - view.setImageCancelled(true) - repository.deletePicture(uploadableFiles[index].getFilePath()) - if (uploadableFiles.size == 1) { - view.showMessage(R.string.upload_cancelled) - view.finish() - return - } - - presenter.updateImageQualitiesJSON(uploadableFiles.size, index) - view.onUploadMediaDeleted(index) - if (index != uploadableFiles.size && index != 0) { - // if the deleted image was not the last item to be uploaded, check quality of next - repository.getUploadItem(index)?.let { - presenter.checkImageQuality(it, index) + uploadableFiles?.let { + view.setImageCancelled(true) + repository.deletePicture(uploadableFiles[index].getFilePath()) + if (uploadableFiles.size == 1) { + view.showMessage(R.string.upload_cancelled) + view.finish() + return } - } - if (uploadableFiles.size < 2) { - view.showHideTopCard(false) - } + presenter.updateImageQualitiesJSON(uploadableFiles.size, index) + view.onUploadMediaDeleted(index) + if (index != uploadableFiles.size && index != 0) { + // if the deleted image was not the last item to be uploaded, check quality of next + repository.getUploadItem(index)?.let { + presenter.checkImageQuality(it, index) + } + } - //In case lets update the number of uploadable media - view.updateTopCardTitle() + if (uploadableFiles.size < 2) { + view.showHideTopCard(false) + } + + //In case lets update the number of uploadable media + view.updateTopCardTitle() + } } override fun onAttachView(view: UploadContract.View) { diff --git a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.kt b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.kt index af850a7e3..4a4c13ba7 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.kt +++ b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.kt @@ -532,7 +532,7 @@ class UploadMediaDetailFragment : UploadBaseFragment(), UploadMediaDetailsContra basicKvStore!!.putBoolean(keyForShowingAlertDialog, false) if (isInternetConnectionEstablished(requireActivity())) { val sizeOfUploads = basicKvStore!!.getInt( - UploadActivity.keyForCurrentUploadImagesSize + UploadActivity.KEY_FOR_CURRENT_UPLOAD_IMAGE_SIZE ) for (i in indexOfFragment until sizeOfUploads) { presenter.getImageQuality( diff --git a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaPresenter.kt b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaPresenter.kt index 90c426091..77999cf2f 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaPresenter.kt +++ b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaPresenter.kt @@ -310,7 +310,7 @@ class UploadMediaPresenter @Inject constructor( private fun storeImageQuality( imageResult: Int, uploadItemIndex: Int, activity: Activity, uploadItem: UploadItem ) { - val store = BasicKvStore(activity, UploadActivity.storeNameForCurrentUploadImagesSize) + val store = BasicKvStore(activity, UploadActivity.STORE_NAME_FOR_CURRENT_UPLOAD_IMAGE_SIZE) val value = store.getString(UPLOAD_QUALITIES_KEY, null) try { val jsonObject = value.asJsonObject().apply { @@ -339,8 +339,10 @@ class UploadMediaPresenter @Inject constructor( */ override fun checkImageQuality(uploadItem: UploadItem, index: Int) { if ((uploadItem.imageQuality != IMAGE_OK) && (uploadItem.imageQuality != IMAGE_KEEP)) { - val value = basicKvStoreFactory?.let { it(UploadActivity.storeNameForCurrentUploadImagesSize) } + + val value = basicKvStoreFactory?.let { it(UploadActivity.STORE_NAME_FOR_CURRENT_UPLOAD_IMAGE_SIZE) } ?.getString(UPLOAD_QUALITIES_KEY, null) + try { val imageQuality = value.asJsonObject()["UploadItem$index"] as Int view.showProgress(false) @@ -363,8 +365,9 @@ class UploadMediaPresenter @Inject constructor( * @param index Index of the UploadItem which was deleted */ override fun updateImageQualitiesJSON(size: Int, index: Int) { - val value = basicKvStoreFactory?.let { it(UploadActivity.storeNameForCurrentUploadImagesSize) } + val value = basicKvStoreFactory?.let { it(UploadActivity.STORE_NAME_FOR_CURRENT_UPLOAD_IMAGE_SIZE) } ?.getString(UPLOAD_QUALITIES_KEY, null) + try { val jsonObject = value.asJsonObject().apply { for (i in index until (size - 1)) { @@ -372,7 +375,8 @@ class UploadMediaPresenter @Inject constructor( } remove("UploadItem" + (size - 1)) } - basicKvStoreFactory?.let { it(UploadActivity.storeNameForCurrentUploadImagesSize) } + + basicKvStoreFactory?.let { it(UploadActivity.STORE_NAME_FOR_CURRENT_UPLOAD_IMAGE_SIZE) } ?.putString(UPLOAD_QUALITIES_KEY, jsonObject.toString()) } catch (e: Exception) { Timber.e(e) diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index 7cfd761a7..0da9f5d9f 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -146,6 +146,7 @@ android:layout_marginEnd="@dimen/standard_gap" android:layout_marginRight="@dimen/standard_gap" android:layout_marginBottom="@dimen/standard_gap" + android:hint="@string/_2fa_code" android:visibility="gone" app:passwordToggleEnabled="false" tools:visibility="visible"> @@ -154,9 +155,7 @@ android:id="@+id/login_two_factor" android:layout_width="match_parent" android:layout_height="wrap_content" - android:hint="@string/_2fa_code" android:imeOptions="flagNoExtractUi" - android:inputType="number" android:visibility="gone" tools:visibility="visible" /> diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 577b031cb..5c9e7bec9 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -168,6 +168,7 @@ الكثير من المحاولات غير الناجحة. الرجاء المحاولة مرة أخرى في بضع دقائق. عذراً، لقد تم منع هذا المستخدم على كومنز يجب توفير رمز التحقق المزدوج. + تم إرسال رمز التحقق إلى بريدك الإلكتروني. يُرجى إدخال الرمز لتسجيل الدخول. فشل تسجيل الدخول ارفع اسم هذه المجموعة @@ -273,6 +274,7 @@ انضم لمختبري اصدارات Beta (بيتا) يمكنك الاشتراك في القناة التجريبية على جوجل بلاي والحصول على إمكانية الوصول المبكر إلى الميزات الجديدة وإصلاحات الأخطاء رمز التحقق المزدوج 2FA + رمز التحقق من البريد الإلكتروني أترغب فعلا في الخروج؟ صورة الوسائط فشلت لم يتم العثور على تصنيفات فرعية. diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 232f56c49..2e535b879 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -119,6 +119,7 @@ For mange mislykkede forsøg. Prøv igen om et par minutter. Beklager, denne bruger er blevet blokeret på Commons Du skal angive din tofaktorgodkendelseskode. + En login-bekræftelseskode er blevet sendt til din e-mailadresse. Angiv koden for at logge ind. Login mislykkedes Upload Navngiv dette sæt @@ -224,6 +225,7 @@ Bliv betatester Registrer dig på vores betakanal på Google Play og få tidlig adgang til nye funktioner og fejlrettelser 2FA-kode + E-mail-bekræftelseskode Ønsker du at logge ud? Mediebillede mislykkedes Ingen underkategorier fundet diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 8ef85d363..5219cf91e 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -119,7 +119,7 @@ ログインできません - ネットワークのエラーです 失敗した回数が多すぎます。数分後にもう一度お試しください。 申し訳ありませんが、この利用者はコモンズでブロックされています。 - 2段階認証コードを入力してください。 + 二要素認証コードを入力してください。 ログイン失敗 アップロード このセットに名前をつけてください @@ -221,7 +221,7 @@ 情報なし ベータ版テスターになる Google Playのベータ版チャンネルにオプトインして、新機能やバグ修正プログラムに早期にアクセス - 2段階認証コード + 2FAコード ログアウトしてもよろしいですか? メディアイメージが失敗しました 下位カテゴリは見つかりませんでした diff --git a/app/src/main/res/values-ku/strings.xml b/app/src/main/res/values-ku/strings.xml index 2dc2fb995..54364b533 100644 --- a/app/src/main/res/values-ku/strings.xml +++ b/app/src/main/res/values-ku/strings.xml @@ -179,7 +179,7 @@ Spasî Hate Wergirtin Wêneyên Bijartî Wêneyên bi riya \"Cihên Nêz\" - Derece + Derece %d Wêneyên Barkirî Wêneyê din Belê, çima na diff --git a/app/src/main/res/values-lb/strings.xml b/app/src/main/res/values-lb/strings.xml index 65574cdb9..afca8fcd4 100644 --- a/app/src/main/res/values-lb/strings.xml +++ b/app/src/main/res/values-lb/strings.xml @@ -99,6 +99,7 @@ Ze dacks ouni Succès probéiert. Probéiert wgl. an e puer Minutten nach eng Kéier. Pardon, dëse Benotzer ass op Commons gespaart Dir musst de Code vun Ärer Zwee-Facteur-Authentifizéierung uginn. + Dir hutt ee Login-Verifikatiounscode per E-Mail geschéckt kritt. Gitt wgl. de Code an, fir Iech anzeloggen. Aloggen huet net funktionéiert Eroplueden Gitt dëser Biller een Numm @@ -192,6 +193,7 @@ Beta-Tester ginn Schreift Iech op GooglePlay a fir eise Beta-Kanal a kritt fréi Zougang zu neie Funktiounen a Verbesserunge vu Feeler 2FA-Code + E-Mail-Verifikatiounscode Wëllt dir Iech wierklech ausloggen? Keng Ënnerkategorie fonnt Bierg Zao diff --git a/app/src/main/res/values-mk/strings.xml b/app/src/main/res/values-mk/strings.xml index 94e8f638b..2a48ddd1e 100644 --- a/app/src/main/res/values-mk/strings.xml +++ b/app/src/main/res/values-mk/strings.xml @@ -113,6 +113,7 @@ Направени се премногу неуспешни обиди. Обидете се пак за некоја минута. За жал, корисникот е блокиран на Ризницата Мора да го укажете вашиот код за двочинителска заверка. + На вашата е-поштенска адреса ви испративме потврден код за најава. Внесете го кодот за да се најавите. Најавата не успеа Подигни Дајте му име на овој комплет @@ -218,6 +219,7 @@ Станете бета-испробувач Пријавете се на нашиот бета-канал на Google Play и добивајте ран пристап до нови можности и исправки на грешки 2ЧЗ-код + Испрати го потврдниот код на е-пошта Дали навистина сакате да се одјавите? Сликата не успеа Не пронајдов поткатегории diff --git a/app/src/main/res/values-pms/strings.xml b/app/src/main/res/values-pms/strings.xml index 76a0be425..d6dd2b3ff 100644 --- a/app/src/main/res/values-pms/strings.xml +++ b/app/src/main/res/values-pms/strings.xml @@ -111,6 +111,7 @@ Tròpi tentativ falì. Për piasì, ch\'a preuva torna da-sì chèiche minute. An dëspias, s\'utent-sì a l\'é stàit blocà ansima a Commons A dev fornì sò còdes d\'autentificassion a doi fator. + Un còdes ëd verìfica ëd conession a l\'é stàit mandà a soa adrëssa ëd pòsta eletrònica. Për piasì, ch\'a anserissa col còdes për intré ant ël sistema. Falì a rintré ant ël sistema Carié Deje un nòm a s\'ansem @@ -216,6 +217,7 @@ Dventé në sperimentador Beta Anscriv-se a nòstr canal beta su Google Play a oten-e n\'acess antissipà a le neuve fonsionalità e coression ëd givo Còdes 2FA + Mandé un còdes ëd verifica Veul-lo për da bon seurte dal sistema? Faliment ëd la plancia dël mojen Gnun-e sot-categorìe trovà diff --git a/app/src/main/res/values-ps/strings.xml b/app/src/main/res/values-ps/strings.xml index eda0d954e..31e12027e 100644 --- a/app/src/main/res/values-ps/strings.xml +++ b/app/src/main/res/values-ps/strings.xml @@ -47,7 +47,14 @@ %d upload %d پورته کول - دا انځور به د %1$s په منښتليک سمبال وي. + + دا انځور به د منښتليک %1$s لاندې وي + دا انځورونه به د %1$s منښتليک لاندې وي + + + %1$d راپورته کول + %1$d راپورته کېدنې + سپړنه ښکارېدنه ټولګړی diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 2949a707a..021a8ca3c 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -696,7 +696,7 @@ WLM Essa imagem será enviada ao concurso Wiki Loves Monuments Monumentos de exibição - Estamos no mês no Wiki Loves Monuments! + Chegou o mês do Wiki Loves Monuments! SABER MAIS Wiki Loves Monuments O Wiki Loves Monuments é um concurso internacional organizado pela Wikimedia sobre fotografias de monumentos diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index ca11a4ec9..b2fd85c23 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -136,6 +136,7 @@ 失敗次數過多。請於幾分鐘後重試。 很抱歉,該使用者已被維基共享資源封禁 必須提供您的雙重驗證代碼。 + 登入驗證碼已發送到您的電子郵件地址。請提供驗證碼以登入。 登入失敗 上傳 給這個集合命名 @@ -241,6 +242,7 @@ 成為 Beta 測試員 選擇加入我們在 Google Play 上的測試版頻道並儘早訪問新功能和錯誤修復 2FA 代碼 + 電子郵件驗證碼 您確定要登出嗎? 媒體圖片失敗 找不到子分類 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f9b16512f..805649dff 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -111,6 +111,7 @@ Too many unsuccessful attempts. Please try again in a few minutes. Sorry, this user has been blocked on Commons You must provide your two factor authentication code. + A login verification code has been sent to your email address. Please provide the code to log in. Log-in failed Upload Name this set @@ -218,6 +219,7 @@ Opt-in to our beta channel on Google Play and get early access to new features and bug fixes https://play.google.com/apps/testing/fr.free.nrw.commons 2FA Code + Email verification code Do you really want to logout? Media Image Failed No subcategories found diff --git a/app/src/test/kotlin/fr/free/nrw/commons/upload/UploadActivityUnitTests.kt b/app/src/test/kotlin/fr/free/nrw/commons/upload/UploadActivityUnitTests.kt index 1173d09b0..97fe68862 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/upload/UploadActivityUnitTests.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/upload/UploadActivityUnitTests.kt @@ -262,15 +262,4 @@ class UploadActivityUnitTests { method.isAccessible = true method.invoke(activity) } - - @Test - @Throws(Exception::class) - fun testOnBackPressed() { - val method: Method = - UploadActivity::class.java.getDeclaredMethod( - "onBackPressed", - ) - method.isAccessible = true - method.invoke(activity) - } }