Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize Image Handling and Open Wikidata Media within app #6187

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
4 changes: 2 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
35 changes: 35 additions & 0 deletions app/src/main/java/fr/free/nrw/commons/Media.kt
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,41 @@ class Media constructor(
captions = captions,
)

constructor(
captions: Map<String, String>,
categories: List<String>?,
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<String, String> = emptyMap(),
depictionIds: List<String> = emptyList(),
categoriesHiddenStatus: Map<String, Boolean> = 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,21 @@ 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
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
Expand All @@ -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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<Place>(WikidataConstants.PLACE_OBJECT, selectedPlace)
val place =
Expand Down
Loading