diff --git a/app/build.gradle b/app/build.gradle index 2bde0d4f18..dbb5458bd9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -175,8 +175,8 @@ dependencies { testImplementation "androidx.work:work-testing:$work_version" //Glide - implementation 'com.github.bumptech.glide:glide:4.12.0' - annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0' + implementation 'com.github.bumptech.glide:glide:4.16.0' + annotationProcessor 'com.github.bumptech.glide:compiler:4.16.0' kaptTest "androidx.databinding:databinding-compiler:8.0.2" kaptAndroidTest "androidx.databinding:databinding-compiler:8.0.2" diff --git a/app/src/main/java/fr/free/nrw/commons/Media.kt b/app/src/main/java/fr/free/nrw/commons/Media.kt index 025302cfdb..293321c271 100644 --- a/app/src/main/java/fr/free/nrw/commons/Media.kt +++ b/app/src/main/java/fr/free/nrw/commons/Media.kt @@ -90,6 +90,41 @@ class Media constructor( captions = captions, ) + constructor( + captions: Map, + categories: List?, + filename: String?, + fallbackDescription: String?, + author: String?, + user: String?, + dateUploaded: Date? = Date(), + license: String? = null, + licenseUrl: String? = null, + imageUrl: String? = null, + thumbUrl: String? = null, + coordinates: LatLng? = null, + descriptions: Map = emptyMap(), + depictionIds: List = emptyList(), + categoriesHiddenStatus: Map = emptyMap() + ) : this( + pageId = UUID.randomUUID().toString(), + filename = filename, + fallbackDescription = fallbackDescription, + dateUploaded = dateUploaded, + author = author, + user = user, + categories = categories, + captions = captions, + license = license, + licenseUrl = licenseUrl, + imageUrl = imageUrl, + thumbUrl = thumbUrl, + coordinates = coordinates, + descriptions = descriptions, + depictionIds = depictionIds, + categoriesHiddenStatus = categoriesHiddenStatus + ) + /** * Gets media display title * @return Media title 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 40c9785db4..77ff1df0cc 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 @@ -16,6 +16,7 @@ import android.view.KeyEvent import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.view.ViewTreeObserver import android.view.ViewTreeObserver.OnGlobalLayoutListener import android.widget.ArrayAdapter import android.widget.Button @@ -405,9 +406,14 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C * Gets the height of the frame layout as soon as the view is ready and updates aspect ratio * of the picture. */ - view.post { - frameLayoutHeight = binding.mediaDetailFrameLayout.measuredHeight - updateAspectRatio(binding.mediaDetailScrollView.width) + view.post{ + val width = binding.mediaDetailScrollView.width + if (width > 0) { + frameLayoutHeight = binding.mediaDetailFrameLayout.measuredHeight + updateAspectRatio(width) + } else { + view.postDelayed({ updateAspectRatio(binding.root.width) }, 1) + } } return view diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java index 545e96624e..b4b4e9c57d 100644 --- a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java @@ -185,10 +185,12 @@ public void onCreate(Bundle savedInstanceState) { * or a fragment */ private void initProvider() { - if (getParentFragment() != null) { + if (getParentFragment() instanceof MediaDetailProvider) { provider = (MediaDetailProvider) getParentFragment(); - } else { + } else if (getActivity() instanceof MediaDetailProvider) { provider = (MediaDetailProvider) getActivity(); + } else { + throw new ClassCastException("Parent must implement MediaDetailProvider"); } } diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.kt b/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.kt index d2e73441db..12ab600b27 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.kt +++ b/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.kt @@ -39,11 +39,13 @@ import androidx.appcompat.app.AlertDialog import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.content.ContextCompat import androidx.core.content.FileProvider +import androidx.fragment.app.Fragment import androidx.lifecycle.LifecycleCoroutineScope import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager +import com.bumptech.glide.Glide import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback import com.google.android.material.snackbar.Snackbar @@ -51,6 +53,7 @@ import com.jakewharton.rxbinding2.view.RxView import com.jakewharton.rxbinding3.appcompat.queryTextChanges import fr.free.nrw.commons.CommonsApplication import fr.free.nrw.commons.MapController.NearbyPlacesInfo +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 @@ -67,6 +70,10 @@ import fr.free.nrw.commons.location.LocationPermissionsHelper.LocationPermission import fr.free.nrw.commons.location.LocationServiceManager import fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType import fr.free.nrw.commons.location.LocationUpdateListener +import fr.free.nrw.commons.media.MediaClient +import fr.free.nrw.commons.media.MediaDetailPagerFragment +import fr.free.nrw.commons.media.MediaDetailPagerFragment.MediaDetailProvider +import fr.free.nrw.commons.navtab.NavTab import fr.free.nrw.commons.nearby.BottomSheetAdapter import fr.free.nrw.commons.nearby.BottomSheetAdapter.ItemClickListener import fr.free.nrw.commons.nearby.CheckBoxTriStates @@ -118,17 +125,25 @@ import timber.log.Timber import java.io.File import java.io.FileOutputStream import java.io.IOException +import java.net.URLDecoder +import java.nio.charset.StandardCharsets import java.text.SimpleDateFormat import java.util.Date import java.util.Locale +import java.util.UUID import java.util.concurrent.TimeUnit import javax.inject.Inject import javax.inject.Named import kotlin.concurrent.Volatile -class NearbyParentFragment : CommonsDaggerSupportFragment(), NearbyParentFragmentContract.View, - WikidataP18EditListener, LocationUpdateListener, LocationPermissionCallback, ItemClickListener { +class NearbyParentFragment : CommonsDaggerSupportFragment(), + NearbyParentFragmentContract.View, + WikidataP18EditListener, + LocationUpdateListener, + LocationPermissionCallback, + ItemClickListener, + MediaDetailPagerFragment.MediaDetailProvider { var binding: FragmentNearbyParentBinding? = null val mapEventsOverlay: MapEventsOverlay = MapEventsOverlay(object : MapEventsReceiver { @@ -163,6 +178,13 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(), NearbyParentFragmen @Named("default_preferences") lateinit var applicationKvStore: JsonKvStore + @Inject + lateinit var mediaClient: MediaClient + + lateinit var mediaDetails: MediaDetailPagerFragment + + lateinit var media: Media + @Inject lateinit var bookmarkLocationDao: BookmarkLocationsDao @@ -716,6 +738,10 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(), NearbyParentFragmen presenter?.attachView(this) registerNetworkReceiver() + binding?.coordinatorLayout?.visibility = View.VISIBLE + binding?.map?.setMultiTouchControls(true) + binding?.map?.isClickable = true + if (isResumed && (activity as? MainActivity)?.activeFragment == ActiveFragment.NEARBY) { if (activity?.let { locationPermissionsHelper?.checkLocationPermission(it) } == true) { locationPermissionGranted() @@ -1853,7 +1879,31 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(), NearbyParentFragmen } fun backButtonClicked(): Boolean { - return presenter!!.backButtonClicked() + if (::mediaDetails.isInitialized && mediaDetails.isVisible) { + removeFragment(mediaDetails) + + binding?.coordinatorLayout?.visibility = View.VISIBLE + binding?.map?.setMultiTouchControls(true) + binding?.map?.isClickable = true + + val transaction = childFragmentManager.beginTransaction() + val fragmentContainer = childFragmentManager.findFragmentById(R.id.coordinator_layout) + + if (fragmentContainer != null) { + transaction.show(fragmentContainer) + } + + transaction.commit() + childFragmentManager.executePendingTransactions() + + (activity as? MainActivity)?.showTabs() + (activity as? MainActivity)?.supportActionBar?.setDisplayHomeAsUpEnabled(false) + return true + } else { + (activity as? MainActivity)?.setSelectedItemId(NavTab.NEARBY.code()) + } + + return presenter?.backButtonClicked() ?: false } override fun onLocationPermissionDenied(toastMessage: String) { @@ -2299,7 +2349,23 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(), NearbyParentFragmen bottomSheetAdapter!!.setClickListener(this) binding!!.bottomSheetDetails.bottomSheetRecyclerView.adapter = bottomSheetAdapter updateBookmarkButtonImage(selectedPlace!!) - binding!!.bottomSheetDetails.icon.setImageResource(selectedPlace!!.label.icon) + + selectedPlace?.pic?.substringAfterLast("/")?.takeIf { it.isNotEmpty() }?.let { imageName -> + Glide.with(binding!!.bottomSheetDetails.icon.context) + .clear(binding!!.bottomSheetDetails.icon) + Glide.with(binding!!.bottomSheetDetails.icon.context) + .load("https://commons.wikimedia.org/wiki/Special:Redirect/file/$imageName?width=25") + .placeholder(fr.free.nrw.commons.R.drawable.ic_refresh_24dp_nearby) + .error(selectedPlace!!.label.icon) + .into(binding!!.bottomSheetDetails.icon) + + binding!!.bottomSheetDetails.icon.setOnClickListener { + handleMediaClick(imageName) + } + } ?: run { + binding!!.bottomSheetDetails.icon.setImageResource(selectedPlace!!.label.icon) + } + binding!!.bottomSheetDetails.title.text = selectedPlace!!.name binding!!.bottomSheetDetails.category.text = selectedPlace!!.distance // Remove label since it is double information @@ -2354,6 +2420,101 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(), NearbyParentFragmen } } + private fun handleMediaClick(imageName: String) { + val decodedImageName = URLDecoder.decode(imageName, StandardCharsets.UTF_8.toString()) + + mediaClient.getMedia("File:$decodedImageName") + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ mediaResponse -> + if (mediaResponse != null) { + // Create a Media object from the response + media = Media( + pageId = mediaResponse.pageId ?: UUID.randomUUID().toString(), + thumbUrl = mediaResponse.thumbUrl, + imageUrl = mediaResponse.imageUrl, + filename = mediaResponse.filename, + fallbackDescription = mediaResponse.fallbackDescription, + dateUploaded = mediaResponse.dateUploaded, + license = mediaResponse.license, + licenseUrl = mediaResponse.licenseUrl, + author = mediaResponse.author, + user = mediaResponse.user, + categories = mediaResponse.categories, + coordinates = mediaResponse.coordinates, + captions = mediaResponse.captions ?: emptyMap(), + descriptions = mediaResponse.descriptions ?: emptyMap(), + depictionIds = mediaResponse.depictionIds ?: emptyList(), + categoriesHiddenStatus = mediaResponse.categoriesHiddenStatus ?: emptyMap() + ) + // Remove existing fragment before showing new details + if (::mediaDetails.isInitialized && mediaDetails.isAdded) { + removeFragment(mediaDetails) + } + showMediaDetails() + } else { + Timber.e("Fetched media is null for image: $decodedImageName") + } + }, { throwable -> + Timber.e(throwable, "Error fetching media for image: $decodedImageName") + }) + } + + private fun showMediaDetails() { + binding?.map?.setMultiTouchControls(false) + binding?.map?.isClickable = false + + mediaDetails = MediaDetailPagerFragment.newInstance(false, true) + + + val transaction = childFragmentManager.beginTransaction() + + val fragmentContainer = childFragmentManager.findFragmentById(R.id.coordinator_layout) + if (fragmentContainer != null) { + transaction.hide(fragmentContainer) + } + + // Replace instead of add to ensure new fragment is used + transaction.replace(R.id.coordinator_layout, mediaDetails, "MediaDetailFragmentTag") + transaction.addToBackStack("Nearby_Parent_Fragment_Tag").commit() + childFragmentManager.executePendingTransactions() + + (activity as? MainActivity)?.supportActionBar?.setDisplayHomeAsUpEnabled(true) + + if (mediaDetails.isAdded) { + mediaDetails.showImage(0) + } else { + Timber.e("Error: MediaDetailPagerFragment is NOT added") + } + } + + override fun getMediaAtPosition(i: Int): Media? { + return media + } + + override fun getTotalMediaCount(): Int { + return 2 + } + + override fun getContributionStateAt(position: Int): Int? { + return null + } + + override fun refreshNominatedMedia(index: Int) { + if (this::mediaDetails.isInitialized && !binding?.map?.isClickable!! == true) { + removeFragment(mediaDetails) + showMediaDetails() + } + } + + private fun removeFragment(fragment: Fragment) { + childFragmentManager + .beginTransaction() + .remove(fragment) + .commit() + childFragmentManager.executePendingTransactions() + } + private fun storeSharedPrefs(selectedPlace: Place) { applicationKvStore!!.putJson(WikidataConstants.PLACE_OBJECT, selectedPlace) val place =