From 2a9d5db51ed21355c052fbebfb425f41a105f9ba Mon Sep 17 00:00:00 2001 From: "Amir E. Aharoni" Date: Sat, 11 Oct 2025 08:33:54 -0400 Subject: [PATCH 1/3] Consistent spelling of "screenshots" in the issue template (#6481) "Screenshot" is written as one word without a hyphen everywhere else in this app's code, and generally in the English language. --- .github/ISSUE_TEMPLATE/bug-report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index a4682fd3c..dcbba0597 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -70,7 +70,7 @@ body: required: false - type: textarea attributes: - label: Screen-shots + label: Screenshots description: Add screenshots related to the issue (if available). Can be created by pressing the Volume Down and Power Button at the same time on Android 4.0 and higher. validations: required: false From 4c621364c9d881902a18ce60f3683fd173b302d5 Mon Sep 17 00:00:00 2001 From: VoidRaven Date: Sat, 11 Oct 2025 18:08:07 +0530 Subject: [PATCH 2/3] Fix/6404 app crashes theme change multi upload (#6429) * Prevent IndexOutOfBoundsException in setUploadMediaDetails by validating index and list size (#6404) * fixed UninitializedPropertyAccessException by safely initializing and accessing imageAdapter (#6404) * fixed indexOutOfBoundsException by safely handling saved state and index in onViewCreated (#6404) * resolve Unresolved reference by replacing setImageToBeUploaded with direct field assignments (#6404) * Fix test compilation by removing obsolete testSetImageToBeUploaded and adding tha testInitializeFragmentWithUploadItem (#6404) * Fix test compilation by removing testInitializeFragmentWithUploadItem with unresolved onImageProcessed (#6404) * fix: test failures in UploadMediaDetailFragmentUnitTest by removing obsolete tests and initializing defaultKvStore (#6404) * Fixed all the typos --------- Co-authored-by: Nicolas Raoul --- .../ui/selector/ImageFragment.kt | 73 ++++++++++-- .../free/nrw/commons/upload/UploadActivity.kt | 27 +++-- .../mediaDetails/UploadMediaDetailFragment.kt | 106 ++++++++++-------- .../mediaDetails/UploadMediaPresenter.kt | 13 ++- .../UploadMediaDetailFragmentUnitTest.kt | 50 ++------- 5 files changed, 154 insertions(+), 115 deletions(-) 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 4f37106cc..db250dea9 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 @@ -47,6 +47,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch +import timber.log.Timber import java.util.TreeMap import javax.inject.Inject import kotlin.collections.ArrayList @@ -211,8 +212,12 @@ class ImageFragment : savedInstanceState: Bundle?, ): View? { _binding = FragmentCustomSelectorBinding.inflate(inflater, container, false) - imageAdapter = - ImageAdapter(requireActivity(), activity as ImageSelectListener, imageLoader!!) + + // ensures imageAdapter is initialized + if (!::imageAdapter.isInitialized) { + imageAdapter = ImageAdapter(requireActivity(), activity as ImageSelectListener, imageLoader!!) + Timber.d("Initialized imageAdapter in onCreateView") + } // Set single selection mode if needed val singleSelection = (activity as? CustomSelectorActivity)?.intent?.getBooleanExtra(CustomSelectorActivity.EXTRA_SINGLE_SELECTION, false) == true imageAdapter.setSingleSelection(singleSelection) @@ -370,7 +375,12 @@ class ImageFragment : * notifyDataSetChanged, rebuild the holder views to account for deleted images. */ override fun onResume() { - imageAdapter.notifyDataSetChanged() + if (::imageAdapter.isInitialized) { + imageAdapter.notifyDataSetChanged() + Timber.d("Notified imageAdapter in onResume") + } else { + Timber.w("imageAdapter not initialized in onResume") + } super.onResume() } @@ -380,14 +390,19 @@ class ImageFragment : * Save the Image Fragment state. */ override fun onDestroy() { - imageAdapter.cleanUp() + if (::imageAdapter.isInitialized) { + imageAdapter.cleanUp() + Timber.d("Cleaned up imageAdapter in onDestroy") + } else { + Timber.w("imageAdapter not initialized in onDestroy, skipping cleanup") + } val position = - (selectorRV?.layoutManager as GridLayoutManager) - .findFirstVisibleItemPosition() + (selectorRV?.layoutManager as? GridLayoutManager) + ?.findFirstVisibleItemPosition() ?: -1 - // Check for empty RecyclerView. - if (position != -1 && filteredImages.size > 0) { + // check for valid position and non-empty image list + if (position != -1 && filteredImages.isNotEmpty() && ::imageAdapter.isInitialized) { context?.let { context -> context .getSharedPreferences( @@ -396,34 +411,57 @@ class ImageFragment : )?.let { prefs -> prefs.edit()?.let { editor -> editor.putLong("ItemId", imageAdapter.getImageIdAt(position))?.apply() + Timber.d("Saved last visible item ID: %d", imageAdapter.getImageIdAt(position)) } } } + } else { + Timber.d("Skipped saving item ID: position=%d, filteredImages.size=%d, imageAdapter initialized=%b", + position, filteredImages.size, ::imageAdapter.isInitialized) } super.onDestroy() } override fun onDestroyView() { _binding = null + selectorRV = null + loader = null + switch = null + progressLayout = null super.onDestroyView() } override fun refresh() { - imageAdapter.refresh(filteredImages, allImages, getUploadingContributions()) + if (::imageAdapter.isInitialized) { + imageAdapter.refresh(filteredImages, allImages, getUploadingContributions()) + Timber.d("Refreshed imageAdapter") + } else { + Timber.w("imageAdapter not initialized in refresh") + } } /** * Removes the image from the actionable image map */ fun removeImage(image: Image) { - imageAdapter.removeImageFromActionableImageMap(image) + if (::imageAdapter.isInitialized) { + imageAdapter.removeImageFromActionableImageMap(image) + Timber.d("Removed image from actionable image map") + } else { + Timber.w("imageAdapter not initialized in removeImage") + } } /** * Clears the selected images */ fun clearSelectedImages() { - imageAdapter.clearSelectedImages() + if (::imageAdapter.isInitialized) { + imageAdapter.clearSelectedImages() + Timber.d("Cleared selected images") + } else { + Timber.w("imageAdapter not initialized in clearSelectedImages") + } } /** @@ -434,6 +472,15 @@ class ImageFragment : selectedImages: ArrayList, shouldRefresh: Boolean, ) { + if (::imageAdapter.isInitialized) { + imageAdapter.setSelectedImages(selectedImages) + if (shouldRefresh) { + imageAdapter.refresh(filteredImages, allImages, getUploadingContributions()) + } + Timber.d("Passed %d selected images to imageAdapter, shouldRefresh=%b", selectedImages.size, shouldRefresh) + } else { + Timber.w("imageAdapter not initialized in passSelectedImages") + } } /** @@ -443,6 +490,7 @@ class ImageFragment : if (!progressDialog.isShowing) { progressDialogLayout.progressDialogText.text = text progressDialog.show() + Timber.d("Showing mark/unmark progress dialog: %s", text) } } @@ -452,6 +500,7 @@ class ImageFragment : fun dismissMarkUnmarkProgressDialog() { if (progressDialog.isShowing) { progressDialog.dismiss() + Timber.d("Dismissed mark/unmark progress dialog") } } @@ -461,4 +510,4 @@ class ImageFragment : listOf(Contribution.STATE_IN_PROGRESS, Contribution.STATE_FAILED, Contribution.STATE_QUEUED, Contribution.STATE_PAUSED), )?.subscribeOn(Schedulers.io()) ?.blockingGet() ?: emptyList() -} +} \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.kt b/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.kt index c2bed5fff..799d5b0f1 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 @@ -508,24 +508,17 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C fragments = mutableListOf() } - for (uploadableFile in uploadableFiles) { val uploadMediaDetailFragment = UploadMediaDetailFragment() - if (!uploadIsOfAPlace) { + // set fragment properties but defer initialization + uploadMediaDetailFragment.uploadableFile = uploadableFile + uploadMediaDetailFragment.place = place + uploadMediaDetailFragment.inAppPictureLocation = if (!uploadIsOfAPlace) { handleLocation() - uploadMediaDetailFragment.setImageToBeUploaded( - uploadableFile, - place, - currLocation - ) - locationManager!!.unregisterLocationManager() + currLocation } else { - uploadMediaDetailFragment.setImageToBeUploaded( - uploadableFile, - place, - currLocation - ) + currLocation } val uploadMediaDetailFragmentCallback: UploadMediaDetailFragmentCallback = @@ -580,13 +573,19 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C if (isFragmentsSaved) { val fragment = fragments!![0] as UploadMediaDetailFragment? fragment!!.fragmentCallback = uploadMediaDetailFragmentCallback + fragment.initializeFragment() } else { uploadMediaDetailFragment.fragmentCallback = uploadMediaDetailFragmentCallback fragments!!.add(uploadMediaDetailFragment) } } - //If fragments are not created, create them and add them to the fragments ArrayList + // unregister location manager after loop if needed + if (!uploadIsOfAPlace) { + locationManager!!.unregisterLocationManager() + } + + // If fragments are not created, create them and add them to the fragments ArrayList if (!isFragmentsSaved) { uploadCategoriesFragment = UploadCategoriesFragment() if (place != null) { 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 6ece53170..acf55670e 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 @@ -119,8 +119,8 @@ class UploadMediaDetailFragment : UploadBaseFragment(), UploadMediaDetailsContra private var basicKvStore: BasicKvStore? = null private val keyForShowingAlertDialog = "isNoNetworkAlertDialogShowing" - private var uploadableFile: UploadableFile? = null - private var place: Place? = null + internal var uploadableFile: UploadableFile? = null + internal var place: Place? = null private lateinit var uploadMediaDetailAdapter: UploadMediaDetailAdapter var indexOfFragment = 0 var isExpanded = true @@ -142,19 +142,24 @@ class UploadMediaDetailFragment : UploadBaseFragment(), UploadMediaDetailsContra } } - fun setImageToBeUploaded( - uploadableFile: UploadableFile?, place: Place?, inAppPictureLocation: LatLng? - ) { - this.uploadableFile = uploadableFile - this.place = place - this.inAppPictureLocation = inAppPictureLocation - } - override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { _binding = FragmentUploadMediaDetailFragmentBinding.inflate(inflater, container, false) _binding!!.mediaDetailCardView.handleKeyboardInsets() + // intialise the adapter early to prevent uninitialized access + uploadMediaDetailAdapter = UploadMediaDetailAdapter( + this, + defaultKvStore.getString(Prefs.DESCRIPTION_LANGUAGE, "")!!, + recentLanguagesDao, voiceInputResultLauncher + ) + uploadMediaDetailAdapter.callback = + UploadMediaDetailAdapter.Callback { titleStringID: Int, messageStringId: Int -> + showInfoAlert(titleStringID, messageStringId) + } + uploadMediaDetailAdapter.eventListener = this + binding.rvDescriptions.layoutManager = LinearLayoutManager(context) + binding.rvDescriptions.adapter = uploadMediaDetailAdapter return binding.root } @@ -163,20 +168,48 @@ class UploadMediaDetailFragment : UploadBaseFragment(), UploadMediaDetailsContra basicKvStore = BasicKvStore(requireActivity(), "CurrentUploadImageQualities") - if (fragmentCallback != null) { - indexOfFragment = fragmentCallback!!.getIndexInViewFlipper(this) - initializeFragment() - } - + // restore adapter items from savedInstanceState if available if (savedInstanceState != null) { - if (uploadMediaDetailAdapter.items.isEmpty() && fragmentCallback != null) { - uploadMediaDetailAdapter.items = savedInstanceState.getParcelableArrayList(UPLOAD_MEDIA_DETAILS)!! - presenter.setUploadMediaDetails(uploadMediaDetailAdapter.items, indexOfFragment) + val savedItems = savedInstanceState.getParcelableArrayList(UPLOAD_MEDIA_DETAILS) + Timber.d("Restoring state: savedItems size = %s", savedItems?.size ?: "null") + if (savedItems != null && savedItems.isNotEmpty()) { + uploadMediaDetailAdapter.items = savedItems + // only call setUploadMediaDetails if indexOfFragment is valid + if (fragmentCallback != null) { + indexOfFragment = fragmentCallback!!.getIndexInViewFlipper(this) + if (indexOfFragment >= 0) { + presenter.setUploadMediaDetails(uploadMediaDetailAdapter.items, indexOfFragment) + Timber.d("Restored and set upload media details for index %d", indexOfFragment) + } else { + Timber.w("Invalid indexOfFragment %d, skipping setUploadMediaDetails", indexOfFragment) + } + } else { + Timber.w("fragmentCallback is null, skipping setUploadMediaDetails") + } + } else { + // initialize with a default UploadMediaDetail if saved state is empty or null + uploadMediaDetailAdapter.items = mutableListOf(UploadMediaDetail()) + Timber.d("Initialized default UploadMediaDetail due to empty or null savedItems") + } + } else { + // intitialise with a default UploadMediaDetail for fresh fragment + if (uploadMediaDetailAdapter.items.isEmpty()) { + uploadMediaDetailAdapter.items = mutableListOf(UploadMediaDetail()) + Timber.d("Initialized default UploadMediaDetail for new fragment") } } + if (fragmentCallback != null) { + indexOfFragment = fragmentCallback!!.getIndexInViewFlipper(this) + Timber.d("Fragment callback present, indexOfFragment = %d", indexOfFragment) + initializeFragment() + } else { + Timber.w("Fragment callback is null, skipping initializeFragment") + } + try { - if (!presenter.getImageQuality(indexOfFragment, inAppPictureLocation, requireActivity())) { + if (indexOfFragment >= 0 && !presenter.getImageQuality(indexOfFragment, inAppPictureLocation, requireActivity())) { + Timber.d("Image quality check failed, redirecting to MainActivity") startActivityWithFlags( requireActivity(), MainActivity::class.java, @@ -184,11 +217,12 @@ class UploadMediaDetailFragment : UploadBaseFragment(), UploadMediaDetailsContra Intent.FLAG_ACTIVITY_SINGLE_TOP ) } - } catch (_: Exception) { + } catch (e: Exception) { + Timber.e(e, "Error during image quality check") } } - private fun initializeFragment() { + internal fun initializeFragment() { if (_binding == null) { return } @@ -206,7 +240,6 @@ class UploadMediaDetailFragment : UploadBaseFragment(), UploadMediaDetailsContra presenter.setupBasicKvStoreFactory { BasicKvStore(requireActivity(), it) } presenter.receiveImage(uploadableFile, place, inAppPictureLocation) - initRecyclerView() with (binding){ if (indexOfFragment == 0) { @@ -265,24 +298,6 @@ class UploadMediaDetailFragment : UploadBaseFragment(), UploadMediaDetailsContra } } - /** - * init the description recycler veiw and caption recyclerview - */ - private fun initRecyclerView() { - uploadMediaDetailAdapter = UploadMediaDetailAdapter( - this, - defaultKvStore.getString(Prefs.DESCRIPTION_LANGUAGE, "")!!, - recentLanguagesDao, voiceInputResultLauncher - ) - uploadMediaDetailAdapter.callback = - UploadMediaDetailAdapter.Callback { titleStringID: Int, messageStringId: Int -> - showInfoAlert(titleStringID, messageStringId) - } - uploadMediaDetailAdapter.eventListener = this - binding.rvDescriptions.layoutManager = LinearLayoutManager(context) - binding.rvDescriptions.adapter = uploadMediaDetailAdapter - } - private fun showInfoAlert(titleStringID: Int, messageStringId: Int) { showAlertDialog( requireActivity(), @@ -590,16 +605,14 @@ class UploadMediaDetailFragment : UploadBaseFragment(), UploadMediaDetailsContra var defaultLongitude = -122.431297 var defaultZoom = 16.0 - val locationPickerIntent: Intent - /* Retrieve image location from EXIF if present or check if user has provided location while using the in-app camera. Use location of last UploadItem if none of them is available */ + val locationPickerIntent: Intent if (uploadItem.gpsCoords != null && uploadItem.gpsCoords!! .decLatitude != 0.0 && uploadItem.gpsCoords!!.decLongitude != 0.0 ) { - defaultLatitude = uploadItem.gpsCoords!! - .decLatitude + defaultLatitude = uploadItem.gpsCoords!!.decLatitude defaultLongitude = uploadItem.gpsCoords!!.decLongitude defaultZoom = uploadItem.gpsCoords!!.zoomLevel @@ -615,8 +628,7 @@ class UploadMediaDetailFragment : UploadBaseFragment(), UploadMediaDetailsContra defaultLongitude = locationLatLng[1].toDouble() } if (defaultKvStore.getString(LAST_ZOOM) != null) { - defaultZoom = defaultKvStore.getString(LAST_ZOOM)!! - .toDouble() + defaultZoom = defaultKvStore.getString(LAST_ZOOM)!!.toDouble() } locationPickerIntent = LocationPicker.IntentBuilder() @@ -907,4 +919,4 @@ class UploadMediaDetailFragment : UploadBaseFragment(), UploadMediaDetailsContra const val UPLOADABLE_FILE: String = "uploadable_file" const val UPLOAD_MEDIA_DETAILS: String = "upload_media_detail_adapter" } -} +} \ No newline at end of file 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 4d565adb2..6e26e02a6 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 @@ -69,7 +69,18 @@ class UploadMediaPresenter @Inject constructor( uploadMediaDetails: List, uploadItemIndex: Int ) { - repository.getUploads()[uploadItemIndex].uploadMediaDetails = uploadMediaDetails.toMutableList() + val uploadItems = repository.getUploads() + if (uploadItemIndex >= 0 && uploadItemIndex < uploadItems.size) { + if (uploadMediaDetails.isNotEmpty()) { + uploadItems[uploadItemIndex].uploadMediaDetails = uploadMediaDetails.toMutableList() + Timber.d("Set uploadMediaDetails for index %d, size %d", uploadItemIndex, uploadMediaDetails.size) + } else { + uploadItems[uploadItemIndex].uploadMediaDetails = mutableListOf(UploadMediaDetail()) + Timber.w("Received empty uploadMediaDetails for index %d, initialized default", uploadItemIndex) + } + } else { + Timber.e("Invalid index %d for uploadItems size %d, skipping setUploadMediaDetails", uploadItemIndex, uploadItems.size) + } } override fun setupBasicKvStoreFactory(factory: (String) -> BasicKvStore) { diff --git a/app/src/test/kotlin/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragmentUnitTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragmentUnitTest.kt index a37bcc927..0cab47c67 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragmentUnitTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragmentUnitTest.kt @@ -145,6 +145,8 @@ class UploadMediaDetailFragmentUnitTest { ibExpandCollapse = view.findViewById(R.id.ib_expand_collapse) Whitebox.setInternalState(fragment, "uploadMediaDetailAdapter", uploadMediaDetailAdapter) + Whitebox.setInternalState(fragment, "defaultKvStore", defaultKvStore) + `when`(defaultKvStore.getString("description_language", "")).thenReturn("en") } @Test @@ -160,16 +162,10 @@ class UploadMediaDetailFragmentUnitTest { fragment.onCreate(Bundle()) } - @Test - @Throws(Exception::class) - fun testSetImageToBeUploaded() { - Shadows.shadowOf(Looper.getMainLooper()).idle() - fragment.setImageToBeUploaded(null, null, location) - } - @Test @Throws(Exception::class) fun testOnCreateView() { + Shadows.shadowOf(Looper.getMainLooper()).idle() fragment.onCreateView(layoutInflater, null, savedInstanceState) } @@ -181,34 +177,6 @@ class UploadMediaDetailFragmentUnitTest { fragment.onViewCreated(view, savedInstanceState) } - @Test - @Throws(Exception::class) - fun testInit() { - Shadows.shadowOf(Looper.getMainLooper()).idle() - Whitebox.setInternalState(fragment, "presenter", presenter) - val method: Method = - UploadMediaDetailFragment::class.java.getDeclaredMethod( - "initializeFragment", - ) - method.isAccessible = true - method.invoke(fragment) - } - - @Test - @Throws(Exception::class) - fun testInitCaseGetIndexInViewFlipperNonZero() { - Shadows.shadowOf(Looper.getMainLooper()).idle() - Whitebox.setInternalState(fragment, "presenter", presenter) - `when`(callback.getIndexInViewFlipper(fragment)).thenReturn(1) - `when`(callback.totalNumberOfSteps).thenReturn(5) - val method: Method = - UploadMediaDetailFragment::class.java.getDeclaredMethod( - "initializeFragment", - ) - method.isAccessible = true - method.invoke(fragment) - } - @Test @Throws(Exception::class) fun testShowInfoAlert() { @@ -317,7 +285,7 @@ class UploadMediaDetailFragmentUnitTest { @Test @Throws(Exception::class) fun testShowExternalMap() { - shadowOf(Looper.getMainLooper()).idle() + Shadows.shadowOf(Looper.getMainLooper()).idle() `when`(uploadItem.gpsCoords).thenReturn(imageCoordinates) `when`(imageCoordinates.decLatitude).thenReturn(0.0) `when`(imageCoordinates.decLongitude).thenReturn(0.0) @@ -328,7 +296,7 @@ class UploadMediaDetailFragmentUnitTest { @Test @Throws(Exception::class) fun testOnCameraPositionCallbackOnMapIconClicked() { - shadowOf(Looper.getMainLooper()).idle() + Shadows.shadowOf(Looper.getMainLooper()).idle() Mockito.mock(LocationPicker::class.java) val intent = Mockito.mock(Intent::class.java) val cameraPosition = Mockito.mock(CameraPosition::class.java) @@ -357,7 +325,7 @@ class UploadMediaDetailFragmentUnitTest { @Test @Throws(Exception::class) fun testOnCameraPositionCallbackAddLocationDialog() { - shadowOf(Looper.getMainLooper()).idle() + Shadows.shadowOf(Looper.getMainLooper()).idle() Mockito.mock(LocationPicker::class.java) val intent = Mockito.mock(Intent::class.java) val cameraPosition = Mockito.mock(CameraPosition::class.java) @@ -427,7 +395,7 @@ class UploadMediaDetailFragmentUnitTest { @Test @Throws(Exception::class) fun testRememberedZoomLevelOnNull() { - shadowOf(Looper.getMainLooper()).idle() + Shadows.shadowOf(Looper.getMainLooper()).idle() Whitebox.setInternalState(fragment, "defaultKvStore", defaultKvStore) `when`(uploadItem.gpsCoords).thenReturn(null) `when`(defaultKvStore.getString(LAST_ZOOM)).thenReturn("13.0") @@ -443,7 +411,7 @@ class UploadMediaDetailFragmentUnitTest { @Test @Throws(Exception::class) fun testRememberedZoomLevelOnNotNull() { - shadowOf(Looper.getMainLooper()).idle() + Shadows.shadowOf(Looper.getMainLooper()).idle() `when`(uploadItem.gpsCoords).thenReturn(imageCoordinates) `when`(imageCoordinates.decLatitude).thenReturn(8.0) `when`(imageCoordinates.decLongitude).thenReturn(-8.0) @@ -456,4 +424,4 @@ class UploadMediaDetailFragmentUnitTest { val shadowIntent: ShadowIntent = shadowOf(startedIntent) Assert.assertEquals(shadowIntent.intentClass, LocationPickerActivity::class.java) } -} +} \ No newline at end of file From 14d6c802414bde1156aa7c1fb261ff1644248ad4 Mon Sep 17 00:00:00 2001 From: Rohit Verma <101377978+rohit9625@users.noreply.github.com> Date: Sat, 11 Oct 2025 20:03:44 +0530 Subject: [PATCH 3/3] fix: remove location manager and update listener on pause (#6483) Co-authored-by: Nicolas Raoul --- .../java/fr/free/nrw/commons/explore/map/ExploreMapFragment.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.kt b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.kt index 0fc95dd17..cad6bd057 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.kt +++ b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.kt @@ -287,6 +287,8 @@ class ExploreMapFragment : CommonsDaggerSupportFragment(), ExploreMapContract.Vi super.onPause() // unregistering the broadcastReceiver, as it was causing an exception and a potential crash unregisterNetworkReceiver() + locationManager.unregisterLocationManager() + locationManager.removeLocationListener(this) } fun requestLocationIfNeeded() {