Skip to content

Commit 7bf9276

Browse files
fix: resolve IndexOutOfBounds error when removing images from top card (#6124)
replace deprecated onBackPressed with onBackPressedCallback remove unit test for deprecated onBackPressed method remove if-check before deleting picture to prevent hiding top thumbnail card hide the thumbnail card on fragments other than MediaDetailFragment Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
1 parent 51da9e4 commit 7bf9276

File tree

5 files changed

+106
-96
lines changed

5 files changed

+106
-96
lines changed

app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.kt

+76-56
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import android.os.Bundle
1414
import android.provider.Settings
1515
import android.view.View
1616
import android.widget.CheckBox
17+
import androidx.activity.OnBackPressedCallback
1718
import androidx.appcompat.app.AlertDialog
1819
import androidx.fragment.app.Fragment
1920
import androidx.fragment.app.FragmentManager
@@ -122,7 +123,7 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C
122123
/**
123124
* Set the value of the showPermissionDialog variable.
124125
*
125-
* @param showPermissionsDialog `true` to indicate to show
126+
* @property isShowPermissionsDialog `true` to indicate to show
126127
* Permissions Dialog if permissions are missing, `false` otherwise.
127128
*/
128129
/**
@@ -166,13 +167,32 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C
166167
private var _binding: ActivityUploadBinding? = null
167168
private val binding: ActivityUploadBinding get() = _binding!!
168169

170+
private lateinit var onBackPressedCallback: OnBackPressedCallback
171+
169172
@SuppressLint("CheckResult")
170173
override fun onCreate(savedInstanceState: Bundle?) {
171174
super.onCreate(savedInstanceState)
172175

173176
_binding = ActivityUploadBinding.inflate(layoutInflater)
174177
setContentView(binding.root)
175178

179+
// Overrides the back button to make sure the user is prepared to lose their progress
180+
onBackPressedCallback = object : OnBackPressedCallback(true) {
181+
override fun handleOnBackPressed() {
182+
showAlertDialog(
183+
this@UploadActivity,
184+
getString(R.string.back_button_warning),
185+
getString(R.string.back_button_warning_desc),
186+
getString(R.string.back_button_continue),
187+
getString(R.string.back_button_warning),
188+
null
189+
) {
190+
finish()
191+
}
192+
}
193+
}
194+
onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
195+
176196
/*
177197
If Configuration of device is changed then get the new fragments
178198
created by the system and populate the fragments ArrayList
@@ -187,7 +207,7 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C
187207
}
188208

189209
init()
190-
binding.rlContainerTitle.setOnClickListener { v: View? -> onRlContainerTitleClicked() }
210+
binding.rlContainerTitle.setOnClickListener { _: View? -> onRlContainerTitleClicked() }
191211
nearbyPopupAnswers = mutableMapOf()
192212
//getting the current dpi of the device and if it is less than 320dp i.e. overlapping
193213
//threshold, thumbnails automatically minimizes
@@ -201,7 +221,7 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C
201221
}
202222
locationManager!!.requestLocationUpdatesFromProvider(LocationManager.GPS_PROVIDER)
203223
locationManager!!.requestLocationUpdatesFromProvider(LocationManager.NETWORK_PROVIDER)
204-
store = BasicKvStore(this, storeNameForCurrentUploadImagesSize).apply {
224+
store = BasicKvStore(this, STORE_NAME_FOR_CURRENT_UPLOAD_IMAGE_SIZE).apply {
205225
clearAll()
206226
}
207227
checkStoragePermissions()
@@ -241,7 +261,7 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C
241261

242262
override fun onPageSelected(position: Int) {
243263
currentSelectedPosition = position
244-
if (position >= uploadableFiles!!.size) {
264+
if (position >= uploadableFiles.size) {
245265
binding.cvContainerTopCard.visibility = View.GONE
246266
} else {
247267
thumbnailsAdapter!!.notifyDataSetChanged()
@@ -274,7 +294,7 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C
274294
.subscribeOn(Schedulers.io())
275295
.observeOn(AndroidSchedulers.mainThread())
276296
.filter { result: Boolean? -> result!! }
277-
.subscribe { result: Boolean? ->
297+
.subscribe { _: Boolean? ->
278298
showAlertDialog(
279299
this,
280300
getString(R.string.block_notification_title),
@@ -284,7 +304,7 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C
284304
})
285305
}
286306

287-
fun checkStoragePermissions() {
307+
private fun checkStoragePermissions() {
288308
// Check if all required permissions are granted
289309
val hasAllPermissions = hasPermission(this, PERMISSIONS_STORAGE)
290310
val hasPartialAccess = hasPartialAccess(this)
@@ -355,7 +375,7 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C
355375
showLongToast(this, messageResourceId)
356376
}
357377

358-
override fun getUploadableFiles(): List<UploadableFile>? {
378+
override fun getUploadableFiles(): List<UploadableFile> {
359379
return uploadableFiles
360380
}
361381

@@ -367,6 +387,14 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C
367387
override fun onUploadMediaDeleted(index: Int) {
368388
fragments!!.removeAt(index) //Remove the corresponding fragment
369389
uploadableFiles.removeAt(index) //Remove the files from the list
390+
391+
val isMediaDetailFragment = fragments!!.getOrNull(currentSelectedPosition)?.let {
392+
it is UploadMediaDetailFragment
393+
} ?: false
394+
if(!isMediaDetailFragment) {
395+
// Should hide the top card current fragment is not the media detail fragment
396+
showHideTopCard(false)
397+
}
370398
thumbnailsAdapter!!.notifyItemRemoved(index) //Notify the thumbnails adapter
371399
uploadImagesAdapter!!.notifyDataSetChanged() //Notify the ViewPager
372400
}
@@ -375,8 +403,8 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C
375403
binding.tvTopCardTitle.text = resources
376404
.getQuantityString(
377405
R.plurals.upload_count_title,
378-
uploadableFiles!!.size,
379-
uploadableFiles!!.size
406+
uploadableFiles.size,
407+
uploadableFiles.size
380408
)
381409
}
382410

@@ -444,15 +472,16 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C
444472
receiveInternalSharedItems()
445473
}
446474

447-
if (uploadableFiles == null || uploadableFiles!!.isEmpty()) {
475+
if (uploadableFiles.isEmpty()) {
448476
handleNullMedia()
449477
} else {
450478
//Show thumbnails
451-
if (uploadableFiles!!.size > 1) {
452-
if (!defaultKvStore.getBoolean("hasAlreadyLaunchedCategoriesDialog")) { //If there is only file, no need to show the image thumbnails
479+
if (uploadableFiles.size > 1) {
480+
if (!defaultKvStore.getBoolean("hasAlreadyLaunchedCategoriesDialog")) {
481+
// If there is only file, no need to show the image thumbnails
453482
showAlertDialogForCategories()
454483
}
455-
if (uploadableFiles!!.size > 3 &&
484+
if (uploadableFiles.size > 3 &&
456485
!defaultKvStore.getBoolean("hasAlreadyLaunchedBigMultiupload")
457486
) {
458487
showAlertForBattery()
@@ -464,8 +493,8 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C
464493
binding.tvTopCardTitle.text = resources
465494
.getQuantityString(
466495
R.plurals.upload_count_title,
467-
uploadableFiles!!.size,
468-
uploadableFiles!!.size
496+
uploadableFiles.size,
497+
uploadableFiles.size
469498
)
470499

471500

@@ -474,7 +503,7 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C
474503
}
475504

476505

477-
for (uploadableFile in uploadableFiles!!) {
506+
for (uploadableFile in uploadableFiles) {
478507
val uploadMediaDetailFragment = UploadMediaDetailFragment()
479508

480509
if (!uploadIsOfAPlace) {
@@ -497,8 +526,8 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C
497526
object : UploadMediaDetailFragmentCallback {
498527
override fun deletePictureAtIndex(index: Int) {
499528
store!!.putInt(
500-
keyForCurrentUploadImagesSize,
501-
(store!!.getInt(keyForCurrentUploadImagesSize) - 1)
529+
KEY_FOR_CURRENT_UPLOAD_IMAGE_SIZE,
530+
(store!!.getInt(KEY_FOR_CURRENT_UPLOAD_IMAGE_SIZE) - 1)
502531
)
503532
presenter!!.deletePictureAtIndex(index)
504533
}
@@ -576,11 +605,11 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C
576605
fragments!!.add(mediaLicenseFragment!!)
577606
} else {
578607
for (i in 1 until fragments!!.size) {
579-
fragments!![i]!!.callback = object : UploadBaseFragment.Callback {
608+
fragments!![i].callback = object : UploadBaseFragment.Callback {
580609
override fun onNextButtonClicked(index: Int) {
581610
if (index < fragments!!.size - 1) {
582611
binding.vpUpload.setCurrentItem(index + 1, false)
583-
fragments!![index + 1]!!.onBecameVisible()
612+
fragments!![index + 1].onBecameVisible()
584613
(binding.rvThumbnails.layoutManager as LinearLayoutManager)
585614
.scrollToPositionWithOffset(
586615
if ((index > 0)) index - 1 else 0,
@@ -594,7 +623,7 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C
594623
override fun onPreviousButtonClicked(index: Int) {
595624
if (index != 0) {
596625
binding.vpUpload.setCurrentItem(index - 1, true)
597-
fragments!![index - 1]!!.onBecameVisible()
626+
fragments!![index - 1].onBecameVisible()
598627
(binding.rvThumbnails.layoutManager as LinearLayoutManager)
599628
.scrollToPositionWithOffset(
600629
if ((index > 3)) index - 2 else 0,
@@ -632,11 +661,12 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C
632661
binding.vpUpload.offscreenPageLimit = fragments!!.size
633662
}
634663
// Saving size of uploadableFiles
635-
store!!.putInt(keyForCurrentUploadImagesSize, uploadableFiles!!.size)
664+
store!!.putInt(KEY_FOR_CURRENT_UPLOAD_IMAGE_SIZE, uploadableFiles.size)
636665
}
637666

638667
/**
639-
* Changes current image when one image upload is cancelled, to highlight next image in the top thumbnail.
668+
* Changes current image when one image upload is cancelled, to highlight next image in the top
669+
* thumbnail.
640670
* Fixes: [Issue](https://github.com/commons-app/apps-android-commons/issues/5511)
641671
*
642672
* @param index Index of image to be removed
@@ -771,7 +801,7 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C
771801
override fun onNextButtonClicked(index: Int) {
772802
if (index < fragments!!.size - 1) {
773803
binding.vpUpload.setCurrentItem(index + 1, false)
774-
fragments!![index + 1]!!.onBecameVisible()
804+
fragments!![index + 1].onBecameVisible()
775805
(binding.rvThumbnails.layoutManager as LinearLayoutManager)
776806
.scrollToPositionWithOffset(if ((index > 0)) index - 1 else 0, 0)
777807
if (index < fragments!!.size - 4) {
@@ -786,18 +816,21 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C
786816
override fun onPreviousButtonClicked(index: Int) {
787817
if (index != 0) {
788818
binding.vpUpload.setCurrentItem(index - 1, true)
789-
fragments!![index - 1]!!.onBecameVisible()
819+
fragments!![index - 1].onBecameVisible()
790820
(binding.rvThumbnails.layoutManager as LinearLayoutManager)
791821
.scrollToPositionWithOffset(if ((index > 3)) index - 2 else 0, 0)
792-
if ((index != 1) && ((index - 1) < uploadableFiles!!.size)) {
822+
if ((index != 1) && ((index - 1) < uploadableFiles.size)) {
793823
// Shows the top card if it was hidden because of the last image being deleted and
794824
// now the user has hit previous button to go back to the media details
795825
showHideTopCard(true)
796826
}
797827
}
798828
}
799829

800-
override fun onThumbnailDeleted(position: Int) = presenter!!.deletePictureAtIndex(position)
830+
override fun onThumbnailDeleted(position: Int) {
831+
presenter!!.deletePictureAtIndex(position)
832+
thumbnailsAdapter?.notifyDataSetChanged()
833+
}
801834

802835
/**
803836
* The adapter used to show image upload intermediate fragments
@@ -824,11 +857,11 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C
824857
}
825858

826859

827-
fun onRlContainerTitleClicked() {
860+
private fun onRlContainerTitleClicked() {
828861
binding.rvThumbnails.visibility =
829862
if (isTitleExpanded) View.GONE else View.VISIBLE
830863
isTitleExpanded = !isTitleExpanded
831-
binding.ibToggleTopCard.rotation = binding.ibToggleTopCard.rotation + 180
864+
binding.ibToggleTopCard.rotation += 180
832865
}
833866

834867
override fun onDestroy() {
@@ -845,21 +878,7 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C
845878
if (uploadCategoriesFragment != null) {
846879
uploadCategoriesFragment!!.callback = null
847880
}
848-
}
849-
850-
/**
851-
* Overrides the back button to make sure the user is prepared to lose their progress
852-
*/
853-
@SuppressLint("MissingSuperCall")
854-
override fun onBackPressed() {
855-
showAlertDialog(
856-
this,
857-
getString(R.string.back_button_warning),
858-
getString(R.string.back_button_warning_desc),
859-
getString(R.string.back_button_continue),
860-
getString(R.string.back_button_warning),
861-
null
862-
) { finish() }
881+
onBackPressedCallback.remove()
863882
}
864883

865884
/**
@@ -879,7 +898,7 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C
879898
.setView(view)
880899
.setTitle(getString(R.string.multiple_files_depiction_header))
881900
.setMessage(getString(R.string.multiple_files_depiction))
882-
.setPositiveButton("OK") { dialog: DialogInterface?, which: Int ->
901+
.setPositiveButton("OK") { _: DialogInterface?, _: Int ->
883902
if (checkBox.isChecked) {
884903
// Save the user's choice to not show the dialog again
885904
defaultKvStore.putBoolean("hasAlreadyLaunchedCategoriesDialog", true)
@@ -913,14 +932,14 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C
913932
getString(R.string.cancel),
914933
{
915934
/* Since opening the right settings page might be device dependent, using
916-
https://github.com/WaseemSabir/BatteryPermissionHelper
917-
directly appeared like a promising idea.
918-
However, this simply closed the popup and did not make
919-
the settings page appear on a Pixel as well as a Xiaomi device.
920-
Used the standard intent instead of using this library as
921-
it shows a list of all the apps on the device and allows users to
922-
turn battery optimisation off.
923-
*/
935+
https://github.com/WaseemSabir/BatteryPermissionHelper
936+
directly appeared like a promising idea.
937+
However, this simply closed the popup and did not make
938+
the settings page appear on a Pixel as well as a Xiaomi device.
939+
Used the standard intent instead of using this library as
940+
it shows a list of all the apps on the device and allows users to
941+
turn battery optimisation off.
942+
*/
924943
val batteryOptimisationSettingsIntent = Intent(
925944
Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS
926945
)
@@ -958,7 +977,8 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C
958977
Also, location information is discarded if the difference between
959978
current location and location recorded just before capturing the image
960979
is greater than 100 meters */
961-
if (isLocationTagUnchecked || locationDifference > 100 || !defaultKvStore.getBoolean("inAppCameraLocationPref")
980+
if (isLocationTagUnchecked || locationDifference > 100
981+
|| !defaultKvStore.getBoolean("inAppCameraLocationPref")
962982
|| !isInAppCameraUpload
963983
) {
964984
currLocation = null
@@ -979,8 +999,8 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C
979999
@JvmField
9801000
var nearbyPopupAnswers: MutableMap<Place, Boolean>? = null
9811001

982-
const val keyForCurrentUploadImagesSize: String = "CurrentUploadImagesSize"
983-
const val storeNameForCurrentUploadImagesSize: String = "CurrentUploadImageQualities"
1002+
const val KEY_FOR_CURRENT_UPLOAD_IMAGE_SIZE: String = "CurrentUploadImagesSize"
1003+
const val STORE_NAME_FOR_CURRENT_UPLOAD_IMAGE_SIZE: String = "CurrentUploadImageQualities"
9841004

9851005
/**
9861006
* Sets the flag indicating whether the upload is of a specific place.

0 commit comments

Comments
 (0)