extraMap = new HashMap<>(4);
+ extraMap.put(QUEUE_TIME, Long.toString(fetchState.responseTime - fetchState.submitTime));
+ extraMap
+ .put(FETCH_TIME, Long.toString(fetchState.fetchCompleteTime - fetchState.responseTime));
+ extraMap
+ .put(TOTAL_TIME, Long.toString(fetchState.fetchCompleteTime - fetchState.submitTime));
+ extraMap.put(IMAGE_SIZE, Integer.toString(byteSize));
+ return extraMap;
+ }
+
+ protected void fetchWithRequest(
+ final OkHttpNetworkFetchState fetchState,
+ final NetworkFetcher.Callback callback,
+ final Request request) {
+ final Call call = mCallFactory.newCall(request);
+
+ fetchState
+ .getContext()
+ .addCallbacks(
+ new BaseProducerContextCallbacks() {
+ @Override
+ public void onCancellationRequested() {
+ onFetchCancellationRequested(call);
+ }
+ });
+
+ call.enqueue(
+ new okhttp3.Callback() {
+ @Override
+ public void onResponse(final Call call, final Response response) {
+ onFetchResponse(fetchState, call, response, callback);
+ }
+
+ @Override
+ public void onFailure(final Call call, final IOException e) {
+ handleException(call, e, callback);
+ }
+ });
+ }
+
+ private void onFetchCancellationRequested(final Call call) {
+ if (Looper.myLooper() != Looper.getMainLooper()) {
+ call.cancel();
+ } else {
+ mCancellationExecutor.execute(call::cancel);
+ }
+ }
+
+ private void onFetchResponse(final OkHttpNetworkFetchState fetchState, final Call call,
+ final Response response,
+ final NetworkFetcher.Callback callback) {
+ fetchState.responseTime = SystemClock.elapsedRealtime();
+ try (final ResponseBody body = response.body()) {
+ if (!response.isSuccessful()) {
+ handleException(
+ call, new IOException("Unexpected HTTP code " + response),
+ callback);
+ return;
+ }
+
+ final BytesRange responseRange =
+ BytesRange.fromContentRangeHeader(response.header("Content-Range"));
+ if (responseRange != null
+ && !(responseRange.from == 0
+ && responseRange.to == BytesRange.TO_END_OF_CONTENT)) {
+ // Only treat as a partial image if the range is not all of the content
+ fetchState.setResponseBytesRange(responseRange);
+ fetchState.setOnNewResultStatusFlags(Consumer.IS_PARTIAL_RESULT);
+ }
+
+ long contentLength = body.contentLength();
+ if (contentLength < 0) {
+ contentLength = 0;
+ }
+ callback.onResponse(body.byteStream(), (int) contentLength);
+ } catch (final Exception e) {
+ handleException(call, e, callback);
+ }
+ }
+
+ /**
+ * Handles exceptions.
+ *
+ * OkHttp notifies callers of cancellations via an IOException. If IOException is caught
+ * after request cancellation, then the exception is interpreted as successful cancellation and
+ * onCancellation is called. Otherwise onFailure is called.
+ */
+ private void handleException(final Call call, final Exception e, final Callback callback) {
+ if (call.isCanceled()) {
+ callback.onCancellation();
+ } else {
+ callback.onFailure(e);
+ }
+ }
+
+ public static class OkHttpNetworkFetchState extends FetchState {
+
+ public long submitTime;
+ public long responseTime;
+ public long fetchCompleteTime;
+
+ public OkHttpNetworkFetchState(
+ final Consumer consumer, final ProducerContext producerContext) {
+ super(consumer, producerContext);
+ }
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/media/CustomOkHttpNetworkFetcher.kt b/app/src/main/java/fr/free/nrw/commons/media/CustomOkHttpNetworkFetcher.kt
deleted file mode 100644
index c8de4022b..000000000
--- a/app/src/main/java/fr/free/nrw/commons/media/CustomOkHttpNetworkFetcher.kt
+++ /dev/null
@@ -1,199 +0,0 @@
-package fr.free.nrw.commons.media
-
-import android.os.Looper
-import android.os.SystemClock
-import com.facebook.imagepipeline.common.BytesRange
-import com.facebook.imagepipeline.image.EncodedImage
-import com.facebook.imagepipeline.producers.BaseNetworkFetcher
-import com.facebook.imagepipeline.producers.BaseProducerContextCallbacks
-import com.facebook.imagepipeline.producers.Consumer
-import com.facebook.imagepipeline.producers.FetchState
-import com.facebook.imagepipeline.producers.NetworkFetcher
-import com.facebook.imagepipeline.producers.ProducerContext
-import fr.free.nrw.commons.CommonsApplication
-import fr.free.nrw.commons.kvstore.JsonKvStore
-import okhttp3.CacheControl
-import okhttp3.Call
-import okhttp3.Callback
-import okhttp3.OkHttpClient
-import okhttp3.Request
-import okhttp3.Response
-import timber.log.Timber
-import java.io.IOException
-import java.util.concurrent.Executor
-import javax.inject.Inject
-import javax.inject.Named
-import javax.inject.Singleton
-
-// Custom implementation of Fresco's Network fetcher to skip downloading of images when limited connection mode is enabled
-// https://github.com/facebook/fresco/blob/master/imagepipeline-backends/imagepipeline-okhttp3/src/main/java/com/facebook/imagepipeline/backends/okhttp3/OkHttpNetworkFetcher.java
-@Singleton
-class CustomOkHttpNetworkFetcher
-@JvmOverloads constructor(
- private val mCallFactory: Call.Factory,
- private val mCancellationExecutor: Executor,
- private val defaultKvStore: JsonKvStore,
- disableOkHttpCache: Boolean = true
-) : BaseNetworkFetcher() {
-
- private val mCacheControl =
- if (disableOkHttpCache) CacheControl.Builder().noStore().build() else null
- private val isLimitedConnectionMode: Boolean
- get() = defaultKvStore.getBoolean(
- CommonsApplication.IS_LIMITED_CONNECTION_MODE_ENABLED,
- false
- )
-
- /**
- * @param okHttpClient client to use
- */
- @Inject
- constructor(
- okHttpClient: OkHttpClient,
- @Named("default_preferences") defaultKvStore: JsonKvStore
- ) : this(okHttpClient, okHttpClient.dispatcher.executorService, defaultKvStore)
-
- /**
- * @param mCallFactory custom [Call.Factory] for fetching image from the network
- * @param mCancellationExecutor executor on which fetching cancellation is performed if
- * cancellation is requested from the UI Thread
- * @param disableOkHttpCache true if network requests should not be cached by OkHttp
- */
- override fun createFetchState(consumer: Consumer, context: ProducerContext) =
- OkHttpNetworkFetchState(consumer, context)
-
- override fun fetch(
- fetchState: OkHttpNetworkFetchState, callback: NetworkFetcher.Callback
- ) {
- fetchState.submitTime = SystemClock.elapsedRealtime()
-
- try {
- if (isLimitedConnectionMode) {
- Timber.d("Skipping loading of image as limited connection mode is enabled")
- callback.onFailure(Exception("Failing image request as limited connection mode is enabled"))
- return
- }
-
- val requestBuilder = Request.Builder().url(fetchState.uri.toString()).get()
-
- if (mCacheControl != null) {
- requestBuilder.cacheControl(mCacheControl)
- }
-
- val bytesRange = fetchState.context.imageRequest.bytesRange
- if (bytesRange != null) {
- requestBuilder.addHeader("Range", bytesRange.toHttpRangeHeaderValue())
- }
-
- fetchWithRequest(fetchState, callback, requestBuilder.build())
- } catch (e: Exception) {
- // handle error while creating the request
- callback.onFailure(e)
- }
- }
-
- override fun onFetchCompletion(fetchState: OkHttpNetworkFetchState, byteSize: Int) {
- fetchState.fetchCompleteTime = SystemClock.elapsedRealtime()
- }
-
- override fun getExtraMap(fetchState: OkHttpNetworkFetchState, byteSize: Int) =
- fetchState.toExtraMap(byteSize)
-
- private fun fetchWithRequest(
- fetchState: OkHttpNetworkFetchState, callback: NetworkFetcher.Callback, request: Request
- ) {
- val call = mCallFactory.newCall(request)
-
- fetchState.context.addCallbacks(object : BaseProducerContextCallbacks() {
- override fun onCancellationRequested() {
- onFetchCancellationRequested(call)
- }
- })
-
- call.enqueue(object : Callback {
- override fun onResponse(call: Call, response: Response) =
- onFetchResponse(fetchState, call, response, callback)
-
- override fun onFailure(call: Call, e: IOException) =
- handleException(call, e, callback)
- })
- }
-
- private fun onFetchCancellationRequested(call: Call) {
- if (Looper.myLooper() != Looper.getMainLooper()) {
- call.cancel()
- } else {
- mCancellationExecutor.execute { call.cancel() }
- }
- }
-
- private fun onFetchResponse(
- fetchState: OkHttpNetworkFetchState,
- call: Call,
- response: Response,
- callback: NetworkFetcher.Callback
- ) {
- fetchState.responseTime = SystemClock.elapsedRealtime()
- try {
- response.body.use { body ->
- if (!response.isSuccessful) {
- handleException(call, IOException("Unexpected HTTP code $response"), callback)
- return
- }
- val responseRange =
- BytesRange.fromContentRangeHeader(response.header("Content-Range"))
- if (responseRange != null && !(responseRange.from == 0 && responseRange.to == BytesRange.TO_END_OF_CONTENT)) {
- // Only treat as a partial image if the range is not all of the content
- fetchState.responseBytesRange = responseRange
- fetchState.onNewResultStatusFlags = Consumer.IS_PARTIAL_RESULT
- }
-
- var contentLength = body!!.contentLength()
- if (contentLength < 0) {
- contentLength = 0
- }
- callback.onResponse(body.byteStream(), contentLength.toInt())
- }
- } catch (e: Exception) {
- handleException(call, e, callback)
- }
- }
-
- /**
- * Handles exceptions.
- *
- * OkHttp notifies callers of cancellations via an IOException. If IOException is caught
- * after request cancellation, then the exception is interpreted as successful cancellation and
- * onCancellation is called. Otherwise onFailure is called.
- */
- private fun handleException(call: Call, e: Exception, callback: NetworkFetcher.Callback) {
- if (call.isCanceled()) {
- callback.onCancellation()
- } else {
- callback.onFailure(e)
- }
- }
-}
-
-class OkHttpNetworkFetchState(
- consumer: Consumer?, producerContext: ProducerContext?
-) : FetchState(consumer, producerContext) {
- var submitTime: Long = 0
- var responseTime: Long = 0
- var fetchCompleteTime: Long = 0
-
- fun toExtraMap(byteSize: Int) = buildMap {
- put(QUEUE_TIME, (responseTime - submitTime).toString())
- put(FETCH_TIME, (fetchCompleteTime - responseTime).toString())
- put(TOTAL_TIME, (fetchCompleteTime - submitTime).toString())
- put(IMAGE_SIZE, byteSize.toString())
- }
-
- companion object {
- private const val QUEUE_TIME = "queue_time"
- private const val FETCH_TIME = "fetch_time"
- private const val TOTAL_TIME = "total_time"
- private const val IMAGE_SIZE = "image_size"
- }
-}
-
diff --git a/app/src/main/java/fr/free/nrw/commons/media/IdAndCaptions.kt b/app/src/main/java/fr/free/nrw/commons/media/IdAndCaptions.kt
new file mode 100644
index 000000000..fe96eb8cb
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/media/IdAndCaptions.kt
@@ -0,0 +1,6 @@
+package fr.free.nrw.commons.media
+
+data class IdAndCaptions(
+ val id: String,
+ val captions: Map,
+)
diff --git a/app/src/main/java/fr/free/nrw/commons/media/IdAndLabels.kt b/app/src/main/java/fr/free/nrw/commons/media/IdAndLabels.kt
deleted file mode 100644
index c989ee7e3..000000000
--- a/app/src/main/java/fr/free/nrw/commons/media/IdAndLabels.kt
+++ /dev/null
@@ -1,18 +0,0 @@
-package fr.free.nrw.commons.media
-
-data class IdAndLabels(
- val id: String,
- val labels: Map,
-) {
- // if a label is available in user's locale, return it
- // if not then check for english, else show any available.
- fun getLocalizedLabel(locale: String): String? {
- if (labels[locale] != null) {
- return labels[locale]
- }
- if (labels["en"] != null) {
- return labels["en"]
- }
- return labels.values.firstOrNull() ?: id
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailAdapter.kt b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailAdapter.kt
deleted file mode 100644
index ccc176154..000000000
--- a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailAdapter.kt
+++ /dev/null
@@ -1,76 +0,0 @@
-package fr.free.nrw.commons.media
-
-import android.view.ViewGroup
-import androidx.fragment.app.Fragment
-import androidx.fragment.app.FragmentManager
-import androidx.fragment.app.FragmentStatePagerAdapter
-import fr.free.nrw.commons.media.MediaDetailFragment.Companion.forMedia
-import timber.log.Timber
-
-// FragmentStatePagerAdapter allows user to swipe across collection of images (no. of images undetermined)
-class MediaDetailAdapter(
- val mediaDetailPagerFragment: MediaDetailPagerFragment,
- fm: FragmentManager
-) : FragmentStatePagerAdapter(fm) {
- /**
- * Keeps track of the current displayed fragment.
- */
- private var currentFragment: Fragment? = null
-
- override fun getItem(i: Int): Fragment {
- if (i == 0) {
- // See bug https://code.google.com/p/android/issues/detail?id=27526
- if (mediaDetailPagerFragment.activity == null) {
- Timber.d("Skipping getItem. Returning as activity is destroyed!")
- return Fragment()
- }
- mediaDetailPagerFragment.binding!!.mediaDetailsPager.postDelayed(
- { mediaDetailPagerFragment.requireActivity().invalidateOptionsMenu() }, 5
- )
- }
- return if (mediaDetailPagerFragment.isFromFeaturedRootFragment) {
- forMedia(
- mediaDetailPagerFragment.position + i,
- mediaDetailPagerFragment.editable, mediaDetailPagerFragment.isFeaturedImage,
- mediaDetailPagerFragment.isWikipediaButtonDisplayed
- )
- } else {
- forMedia(
- i, mediaDetailPagerFragment.editable,
- mediaDetailPagerFragment.isFeaturedImage,
- mediaDetailPagerFragment.isWikipediaButtonDisplayed
- )
- }
- }
-
- override fun getCount(): Int {
- if (mediaDetailPagerFragment.activity == null) {
- Timber.d("Skipping getCount. Returning as activity is destroyed!")
- return 0
- }
- return mediaDetailPagerFragment.mediaDetailProvider!!.getTotalMediaCount()
- }
-
- /**
- * If current fragment is of type MediaDetailFragment, return it, otherwise return null.
- *
- * @return MediaDetailFragment
- */
- val currentMediaDetailFragment: MediaDetailFragment?
- get() = currentFragment as? MediaDetailFragment
-
- /**
- * Called to inform the adapter of which item is currently considered to be the "primary", that
- * is the one show to the user as the current page.
- */
- override fun setPrimaryItem(
- container: ViewGroup, position: Int,
- obj: Any
- ) {
- // Update the current fragment if changed
- if (currentFragment !== obj) {
- currentFragment = (obj as Fragment)
- }
- super.setPrimaryItem(container, position, obj)
- }
-}
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 41e65ae4e..77ff1df0c 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
@@ -74,9 +75,11 @@ import fr.free.nrw.commons.BuildConfig
import fr.free.nrw.commons.CameraPosition
import fr.free.nrw.commons.CommonsApplication
import fr.free.nrw.commons.CommonsApplication.Companion.instance
+import fr.free.nrw.commons.locationpicker.LocationPicker
import fr.free.nrw.commons.Media
import fr.free.nrw.commons.MediaDataExtractor
import fr.free.nrw.commons.R
+import fr.free.nrw.commons.Utils
import fr.free.nrw.commons.actions.ThanksClient
import fr.free.nrw.commons.auth.SessionManager
import fr.free.nrw.commons.auth.csrf.InvalidLoginTokenException
@@ -100,7 +103,7 @@ import fr.free.nrw.commons.explore.depictions.WikidataItemDetailsActivity
import fr.free.nrw.commons.kvstore.JsonKvStore
import fr.free.nrw.commons.language.AppLanguageLookUpTable
import fr.free.nrw.commons.location.LocationServiceManager
-import fr.free.nrw.commons.locationpicker.LocationPicker
+import fr.free.nrw.commons.media.MediaDetailPagerFragment.MediaDetailProvider
import fr.free.nrw.commons.profile.ProfileActivity
import fr.free.nrw.commons.review.ReviewHelper
import fr.free.nrw.commons.settings.Prefs
@@ -114,13 +117,8 @@ import fr.free.nrw.commons.utils.LangCodeUtils.getLocalizedResources
import fr.free.nrw.commons.utils.PermissionUtils.PERMISSIONS_STORAGE
import fr.free.nrw.commons.utils.PermissionUtils.checkPermissionsAndPerformAction
import fr.free.nrw.commons.utils.PermissionUtils.hasPermission
-import fr.free.nrw.commons.utils.ViewUtil
import fr.free.nrw.commons.utils.ViewUtil.showShortToast
import fr.free.nrw.commons.utils.ViewUtilWrapper
-import fr.free.nrw.commons.utils.copyToClipboard
-import fr.free.nrw.commons.utils.handleGeoCoordinates
-import fr.free.nrw.commons.utils.handleWebUrl
-import fr.free.nrw.commons.utils.setUnderlinedText
import fr.free.nrw.commons.wikidata.mwapi.MwQueryPage.Revision
import io.reactivex.Observable
import io.reactivex.Single
@@ -128,7 +126,6 @@ import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import org.apache.commons.lang3.StringUtils
import timber.log.Timber
-import java.lang.String.format
import java.util.Date
import java.util.Locale
import java.util.Objects
@@ -318,7 +315,14 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C
_binding = FragmentMediaDetailBinding.inflate(inflater, container, false)
val view: View = binding.root
- binding.seeMore.setUnderlinedText(R.string.nominated_see_more)
+
+ Utils.setUnderlinedText(binding.seeMore, R.string.nominated_see_more, requireContext())
+
+ if (isCategoryImage) {
+ binding.authorLinearLayout.visibility = View.VISIBLE
+ } else {
+ binding.authorLinearLayout.visibility = View.GONE
+ }
if (!sessionManager.isUserLoggedIn) {
binding.categoryEditButton.visibility = View.GONE
@@ -541,7 +545,6 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C
}
)
binding.progressBarEdit.visibility = View.GONE
- binding.descriptionEdit.visibility = View.VISIBLE
}
override fun onConfigurationChanged(newConfig: Configuration) {
@@ -619,9 +622,10 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
- { idAndCaptions: List -> onDepictionsLoaded(idAndCaptions) },
+ { idAndCaptions: List -> onDepictionsLoaded(idAndCaptions) },
{ t: Throwable? -> Timber.e(t) })
)
+ // compositeDisposable.add(disposable);
}
private fun onDiscussionLoaded(discussion: String) {
@@ -651,7 +655,7 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C
}
}
- private fun onDepictionsLoaded(idAndCaptions: List) {
+ private fun onDepictionsLoaded(idAndCaptions: List) {
binding.depictsLayout.visibility = View.VISIBLE
binding.depictionsEditButton.visibility = View.VISIBLE
buildDepictionList(idAndCaptions)
@@ -809,27 +813,10 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C
categoryNames.clear()
categoryNames.addAll(media.categories!!)
- // Show author or uploader information for licensing compliance
- val authorName = media.getAttributedAuthor()
- val uploaderName = media.user
-
- when {
- !authorName.isNullOrEmpty() -> {
- // Show author if available
- binding.mediaDetailAuthorLabel.text = getString(R.string.media_detail_author)
- binding.mediaDetailAuthor.text = authorName
- binding.authorLinearLayout.visibility = View.VISIBLE
- }
- !uploaderName.isNullOrEmpty() -> {
- // Show uploader as fallback
- binding.mediaDetailAuthorLabel.text = getString(R.string.media_detail_uploader)
- binding.mediaDetailAuthor.text = uploaderName
- binding.authorLinearLayout.visibility = View.VISIBLE
- }
- else -> {
- // Hide if neither author nor uploader is available
- binding.authorLinearLayout.visibility = View.GONE
- }
+ if (media.author == null || media.author == "") {
+ binding.authorLinearLayout.visibility = View.GONE
+ } else {
+ binding.mediaDetailAuthor.text = media.author
}
}
@@ -878,24 +865,24 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C
* Populates media details fragment with depiction list
* @param idAndCaptions
*/
- private fun buildDepictionList(idAndCaptions: List) {
+ private fun buildDepictionList(idAndCaptions: List) {
binding.mediaDetailDepictionContainer.removeAllViews()
// Create a mutable list from the original list
val mutableIdAndCaptions = idAndCaptions.toMutableList()
if (mutableIdAndCaptions.isEmpty()) {
- // Create a placeholder IdAndLabels object and add it to the list
+ // Create a placeholder IdAndCaptions object and add it to the list
mutableIdAndCaptions.add(
- IdAndLabels(
+ IdAndCaptions(
id = media?.pageId ?: "", // Use an empty string if media?.pageId is null
- labels = mapOf(Locale.getDefault().language to getString(R.string.detail_panel_cats_none)) // Create a Map with the language as the key and the message as the value
+ captions = mapOf(Locale.getDefault().language to getString(R.string.detail_panel_cats_none)) // Create a Map with the language as the key and the message as the value
)
)
}
val locale: String = Locale.getDefault().language
- for (idAndCaption in mutableIdAndCaptions) {
+ for (idAndCaption: IdAndCaptions in mutableIdAndCaptions) {
binding.mediaDetailDepictionContainer.addView(
buildDepictLabel(
getDepictionCaption(idAndCaption, locale),
@@ -907,22 +894,22 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C
}
- private fun getDepictionCaption(idAndCaption: IdAndLabels, locale: String): String? {
+ private fun getDepictionCaption(idAndCaption: IdAndCaptions, locale: String): String? {
// Check if the Depiction Caption is available in user's locale
// if not then check for english, else show any available.
- if (idAndCaption.labels[locale] != null) {
- return idAndCaption.labels[locale]
+ if (idAndCaption.captions[locale] != null) {
+ return idAndCaption.captions[locale]
}
- if (idAndCaption.labels["en"] != null) {
- return idAndCaption.labels["en"]
+ if (idAndCaption.captions["en"] != null) {
+ return idAndCaption.captions["en"]
}
- return idAndCaption.labels.values.iterator().next()
+ return idAndCaption.captions.values.iterator().next()
}
private fun onMediaDetailLicenceClicked() {
val url: String? = media!!.licenseUrl
if (!StringUtils.isBlank(url) && activity != null) {
- handleWebUrl(requireContext(), Uri.parse(url))
+ Utils.handleWebUrl(activity, Uri.parse(url))
} else {
viewUtil.showShortToast(requireActivity(), getString(R.string.null_url))
}
@@ -930,17 +917,17 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C
private fun onMediaDetailCoordinatesClicked() {
if (media!!.coordinates != null && activity != null) {
- handleGeoCoordinates(requireContext(), media!!.coordinates!!)
+ Utils.handleGeoCoordinates(activity, media!!.coordinates)
}
}
private fun onCopyWikicodeClicked() {
val data: String =
"[[" + media!!.filename + "|thumb|" + media!!.fallbackDescription + "]]"
- requireContext().copyToClipboard("wikiCode", data)
+ Utils.copy("wikiCode", data, context)
Timber.d("Generated wikidata copy code: %s", data)
- Toast.makeText(requireContext(), getString(R.string.wikicode_copied), Toast.LENGTH_SHORT)
+ Toast.makeText(context, getString(R.string.wikicode_copied), Toast.LENGTH_SHORT)
.show()
}
@@ -1027,12 +1014,12 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C
val message: String = if (result) {
context.getString(
R.string.send_thank_success_message,
- media!!.user
+ media!!.displayTitle
)
} else {
context.getString(
R.string.send_thank_failure_message,
- media!!.user
+ media!!.displayTitle
)
}
@@ -1661,7 +1648,7 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C
getString(R.string.cancel),
{
val reason: String = input.text.toString()
- onDeleteClickedDialogText(reason)
+ onDeleteClickeddialogtext(reason)
},
{},
input
@@ -1715,48 +1702,26 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C
resultSingle
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
- .subscribe(this::handleDeletionResult, this::handleDeletionError);
- }
-
- /**
- * Disables Progress Bar and Update delete button text.
- */
- private fun disableProgressBar() {
- activity?.run {
- runOnUiThread(Runnable {
- binding.progressBarDeletion.visibility = View.GONE
- })
- } ?: return // Prevent NullPointerException when fragment is not attached to activity
- }
-
- private fun handleDeletionResult(success: Boolean) {
- if (success) {
- binding.nominateDeletion.text = getString(R.string.nominated_for_deletion_btn)
- ViewUtil.showLongSnackbar(requireView(), getString(R.string.nominated_for_deletion))
- disableProgressBar()
- checkAndClearDeletionFlag()
- } else {
- disableProgressBar()
- }
- }
-
- private fun handleDeletionError(throwable: Throwable) {
- throwable.printStackTrace()
- disableProgressBar()
- checkAndClearDeletionFlag()
- }
-
- private fun checkAndClearDeletionFlag() {
- if (applicationKvStore
- .getBoolean(format(NOMINATING_FOR_DELETION_MEDIA, media!!.imageUrl), false)
- ) {
- applicationKvStore.remove(format(NOMINATING_FOR_DELETION_MEDIA, media!!.imageUrl))
- callback!!.nominatingForDeletion(index)
- }
+ .subscribe { _ ->
+ if (applicationKvStore.getBoolean(
+ String.format(
+ NOMINATING_FOR_DELETION_MEDIA, media!!.imageUrl
+ ), false
+ )
+ ) {
+ applicationKvStore.remove(
+ String.format(
+ NOMINATING_FOR_DELETION_MEDIA,
+ media!!.imageUrl
+ )
+ )
+ callback!!.nominatingForDeletion(index)
+ }
+ }
}
@SuppressLint("CheckResult")
- private fun onDeleteClickedDialogText(reason: String) {
+ private fun onDeleteClickeddialogtext(reason: String) {
applicationKvStore.putBoolean(
String.format(
NOMINATING_FOR_DELETION_MEDIA,
@@ -1773,12 +1738,27 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C
resultSingletext
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
- .subscribe(this::handleDeletionResult, this::handleDeletionError);
+ .subscribe { _ ->
+ if (applicationKvStore.getBoolean(
+ String.format(
+ NOMINATING_FOR_DELETION_MEDIA, media!!.imageUrl
+ ), false
+ )
+ ) {
+ applicationKvStore.remove(
+ String.format(
+ NOMINATING_FOR_DELETION_MEDIA,
+ media!!.imageUrl
+ )
+ )
+ callback!!.nominatingForDeletion(index)
+ }
+ }
}
private fun onSeeMoreClicked() {
if (binding.nominatedDeletionBanner.visibility == View.VISIBLE && activity != null) {
- handleWebUrl(requireContext(), Uri.parse(media!!.pageTitle.mobileUri))
+ Utils.handleWebUrl(activity, Uri.parse(media!!.pageTitle.mobileUri))
}
}
@@ -2129,17 +2109,22 @@ fun FileUsagesContainer(
val uriHandle = LocalUriHandler.current
Column(modifier = modifier) {
+
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
+
Text(
text = stringResource(R.string.usages_on_commons_heading),
textAlign = TextAlign.Center,
style = MaterialTheme.typography.titleSmall
)
- IconButton(onClick = { isCommonsListExpanded = !isCommonsListExpanded }) {
+
+ IconButton(onClick = {
+ isCommonsListExpanded = !isCommonsListExpanded
+ }) {
Icon(
imageVector = if (isCommonsListExpanded) Icons.Default.KeyboardArrowUp
else Icons.Default.KeyboardArrowDown,
@@ -2153,8 +2138,11 @@ fun FileUsagesContainer(
MediaDetailViewModel.FileUsagesContainerState.Loading -> {
LinearProgressIndicator()
}
+
is MediaDetailViewModel.FileUsagesContainerState.Success -> {
+
val data = commonsContainerState.data
+
if (data.isNullOrEmpty()) {
ListItem(headlineContent = {
Text(
@@ -2174,7 +2162,7 @@ fun FileUsagesContainer(
headlineContent = {
Text(
modifier = Modifier.clickable {
- usage.link?.let { uriHandle.openUri(it) }
+ uriHandle.openUri(usage.link!!)
},
text = usage.title,
style = MaterialTheme.typography.titleSmall.copy(
@@ -2182,11 +2170,11 @@ fun FileUsagesContainer(
textDecoration = TextDecoration.Underline
)
)
- }
- )
+ })
}
}
}
+
is MediaDetailViewModel.FileUsagesContainerState.Error -> {
ListItem(headlineContent = {
Text(
@@ -2196,10 +2184,12 @@ fun FileUsagesContainer(
)
})
}
+
MediaDetailViewModel.FileUsagesContainerState.Initial -> {}
}
}
+
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
@@ -2210,7 +2200,10 @@ fun FileUsagesContainer(
textAlign = TextAlign.Center,
style = MaterialTheme.typography.titleSmall
)
- IconButton(onClick = { isOtherWikisListExpanded = !isOtherWikisListExpanded }) {
+
+ IconButton(onClick = {
+ isOtherWikisListExpanded = !isOtherWikisListExpanded
+ }) {
Icon(
imageVector = if (isOtherWikisListExpanded) Icons.Default.KeyboardArrowUp
else Icons.Default.KeyboardArrowDown,
@@ -2224,8 +2217,11 @@ fun FileUsagesContainer(
MediaDetailViewModel.FileUsagesContainerState.Loading -> {
LinearProgressIndicator()
}
+
is MediaDetailViewModel.FileUsagesContainerState.Success -> {
+
val data = globalContainerState.data
+
if (data.isNullOrEmpty()) {
ListItem(headlineContent = {
Text(
@@ -2244,20 +2240,16 @@ fun FileUsagesContainer(
},
headlineContent = {
Text(
- modifier = Modifier.clickable {
- usage.link?.let { uriHandle.openUri(it) }
- },
text = usage.title,
style = MaterialTheme.typography.titleSmall.copy(
- color = Color(0xFF5A6AEC),
textDecoration = TextDecoration.Underline
)
)
- }
- )
+ })
}
}
}
+
is MediaDetailViewModel.FileUsagesContainerState.Error -> {
ListItem(headlineContent = {
Text(
@@ -2267,8 +2259,10 @@ fun FileUsagesContainer(
)
})
}
+
MediaDetailViewModel.FileUsagesContainerState.Initial -> {}
}
}
+
}
-}
\ No newline at end of file
+}
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
new file mode 100644
index 000000000..cba582a35
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java
@@ -0,0 +1,678 @@
+package fr.free.nrw.commons.media;
+
+import static fr.free.nrw.commons.Utils.handleWebUrl;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.widget.ProgressBar;
+import android.content.ActivityNotFoundException;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Toast;
+import androidx.appcompat.app.ActionBar;
+import androidx.appcompat.app.AlertDialog;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.annotation.NonNull;
+import androidx.core.content.ContextCompat;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+import androidx.fragment.app.FragmentStatePagerAdapter;
+import androidx.viewpager.widget.ViewPager;
+import com.google.android.material.snackbar.Snackbar;
+import fr.free.nrw.commons.CommonsApplication;
+import fr.free.nrw.commons.Media;
+import fr.free.nrw.commons.R;
+import fr.free.nrw.commons.Utils;
+import fr.free.nrw.commons.auth.SessionManager;
+import fr.free.nrw.commons.bookmarks.models.Bookmark;
+import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesContentProvider;
+import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao;
+import fr.free.nrw.commons.contributions.Contribution;
+import fr.free.nrw.commons.contributions.MainActivity;
+import fr.free.nrw.commons.databinding.FragmentMediaDetailPagerBinding;
+import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
+import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient;
+import fr.free.nrw.commons.profile.ProfileActivity;
+import fr.free.nrw.commons.utils.DownloadUtils;
+import fr.free.nrw.commons.utils.ImageUtils;
+import fr.free.nrw.commons.utils.NetworkUtils;
+import fr.free.nrw.commons.utils.ViewUtil;
+import io.reactivex.Observable;
+import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.disposables.CompositeDisposable;
+import io.reactivex.schedulers.Schedulers;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Objects;
+import java.util.concurrent.Callable;
+import javax.inject.Inject;
+import timber.log.Timber;
+
+public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment implements ViewPager.OnPageChangeListener, MediaDetailFragment.Callback {
+
+ @Inject BookmarkPicturesDao bookmarkDao;
+
+ @Inject
+ protected OkHttpJsonApiClient okHttpJsonApiClient;
+
+ @Inject
+ protected SessionManager sessionManager;
+
+ private static CompositeDisposable compositeDisposable = new CompositeDisposable();
+
+ private FragmentMediaDetailPagerBinding binding;
+
+ private boolean editable;
+ private boolean isFeaturedImage;
+ private boolean isWikipediaButtonDisplayed;
+ MediaDetailAdapter adapter;
+ private Bookmark bookmark;
+ private MediaDetailProvider provider;
+ private boolean isFromFeaturedRootFragment;
+ private int position;
+
+ /**
+ * ProgressBar used to indicate the loading status of media items.
+ */
+ private ProgressBar imageProgressBar;
+
+ private ArrayList removedItems=new ArrayList();
+
+ public void clearRemoved(){
+ removedItems.clear();
+ }
+ public ArrayList getRemovedItems() {
+ return removedItems;
+ }
+
+
+ /**
+ * Use this factory method to create a new instance of this fragment using the provided
+ * parameters.
+ *
+ * This method will create a new instance of MediaDetailPagerFragment and the arguments will be
+ * saved to a bundle which will be later available in the {@link #onCreate(Bundle)}
+ * @param editable
+ * @param isFeaturedImage
+ * @return
+ */
+ public static MediaDetailPagerFragment newInstance(boolean editable, boolean isFeaturedImage) {
+ MediaDetailPagerFragment mediaDetailPagerFragment = new MediaDetailPagerFragment();
+ Bundle args = new Bundle();
+ args.putBoolean("is_editable", editable);
+ args.putBoolean("is_featured_image", isFeaturedImage);
+ mediaDetailPagerFragment.setArguments(args);
+ return mediaDetailPagerFragment;
+ }
+
+ public MediaDetailPagerFragment() {
+ // Required empty public constructor
+ };
+
+
+ @Override
+ public View onCreateView(LayoutInflater inflater,
+ ViewGroup container,
+ Bundle savedInstanceState) {
+ binding = FragmentMediaDetailPagerBinding.inflate(inflater, container, false);
+ binding.mediaDetailsPager.addOnPageChangeListener(this);
+ // Initialize the ProgressBar by finding it in the layout
+ imageProgressBar = binding.getRoot().findViewById(R.id.itemProgressBar);
+ adapter = new MediaDetailAdapter(getChildFragmentManager());
+
+ // ActionBar is now supported in both activities - if this crashes something is quite wrong
+ final ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar();
+ if (actionBar != null) {
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ }
+ else {
+ throw new AssertionError("Action bar should not be null!");
+ }
+
+ // If fragment is associated with ProfileActivity, then hide the tabLayout
+ if (getActivity() instanceof ProfileActivity) {
+ ((ProfileActivity)getActivity()).setTabLayoutVisibility(false);
+ }
+
+ // Else if fragment is associated with MainActivity then hide that tab layout
+ else if (getActivity() instanceof MainActivity) {
+ ((MainActivity)getActivity()).hideTabs();
+ }
+
+ binding.mediaDetailsPager.setAdapter(adapter);
+
+ if (savedInstanceState != null) {
+ final int pageNumber = savedInstanceState.getInt("current-page");
+ binding.mediaDetailsPager.setCurrentItem(pageNumber, false);
+ getActivity().invalidateOptionsMenu();
+ }
+ adapter.notifyDataSetChanged();
+
+ return binding.getRoot();
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putInt("current-page", binding.mediaDetailsPager.getCurrentItem());
+ outState.putBoolean("editable", editable);
+ outState.putBoolean("isFeaturedImage", isFeaturedImage);
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (savedInstanceState != null) {
+ editable = savedInstanceState.getBoolean("editable", false);
+ isFeaturedImage = savedInstanceState.getBoolean("isFeaturedImage", false);
+
+ }
+ setHasOptionsMenu(true);
+ initProvider();
+ }
+
+ /**
+ * initialise the provider, based on from where the fragment was started, as in from an activity
+ * or a fragment
+ */
+ private void initProvider() {
+ if (getParentFragment() instanceof MediaDetailProvider) {
+ provider = (MediaDetailProvider) getParentFragment();
+ } else if (getActivity() instanceof MediaDetailProvider) {
+ provider = (MediaDetailProvider) getActivity();
+ } else {
+ throw new ClassCastException("Parent must implement MediaDetailProvider");
+ }
+ }
+
+ public MediaDetailProvider getMediaDetailProvider() {
+ return provider;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (getActivity() == null) {
+ Timber.d("Returning as activity is destroyed!");
+ return true;
+ }
+
+ Media m = provider.getMediaAtPosition(binding.mediaDetailsPager.getCurrentItem());
+ MediaDetailFragment mediaDetailFragment = this.adapter.getCurrentMediaDetailFragment();
+ switch (item.getItemId()) {
+ case R.id.menu_bookmark_current_image:
+ boolean bookmarkExists = bookmarkDao.updateBookmark(bookmark);
+ Snackbar snackbar = bookmarkExists ? Snackbar.make(getView(), R.string.add_bookmark, Snackbar.LENGTH_LONG) : Snackbar.make(getView(), R.string.remove_bookmark, Snackbar.LENGTH_LONG);
+ snackbar.show();
+ updateBookmarkState(item);
+ return true;
+ case R.id.menu_copy_link:
+ String uri = m.getPageTitle().getCanonicalUri();
+ Utils.copy("shareLink", uri, requireContext());
+ Timber.d("Copied share link to clipboard: %s", uri);
+ Toast.makeText(requireContext(), getString(R.string.menu_link_copied),
+ Toast.LENGTH_SHORT).show();
+ return true;
+ case R.id.menu_share_current_image:
+ Intent shareIntent = new Intent(Intent.ACTION_SEND);
+ shareIntent.setType("text/plain");
+ shareIntent.putExtra(Intent.EXTRA_TEXT, m.getDisplayTitle() + " \n" + m.getPageTitle().getCanonicalUri());
+ startActivity(Intent.createChooser(shareIntent, "Share image via..."));
+
+ //Add media detail to backstack when the share button is clicked
+ //So that when the share is cancelled or completed the media detail page is on top
+ // of back stack fixing:https://github.com/commons-app/apps-android-commons/issues/2296
+ FragmentManager supportFragmentManager = getActivity().getSupportFragmentManager();
+ if (supportFragmentManager.getBackStackEntryCount() < 2) {
+ supportFragmentManager
+ .beginTransaction()
+ .addToBackStack(MediaDetailPagerFragment.class.getName())
+ .commit();
+ supportFragmentManager.executePendingTransactions();
+ }
+ return true;
+ case R.id.menu_browser_current_image:
+ // View in browser
+ handleWebUrl(requireContext(), Uri.parse(m.getPageTitle().getMobileUri()));
+ return true;
+ case R.id.menu_download_current_image:
+ // Download
+ if (!NetworkUtils.isInternetConnectionEstablished(getActivity())) {
+ ViewUtil.showShortSnackbar(getView(), R.string.no_internet);
+ return false;
+ }
+ DownloadUtils.downloadMedia(getActivity(), m);
+ return true;
+ case R.id.menu_set_as_wallpaper:
+ // Set wallpaper
+ setWallpaper(m);
+ return true;
+ case R.id.menu_set_as_avatar:
+ // Set avatar
+ setAvatar(m);
+ return true;
+ case R.id.menu_view_user_page:
+ if (m != null && m.getUser() != null) {
+ ProfileActivity.startYourself(getActivity(), m.getUser(),
+ !Objects.equals(sessionManager.getUserName(), m.getUser()));
+ }
+ return true;
+ case R.id.menu_view_report:
+ showReportDialog(m);
+ case R.id.menu_view_set_white_background:
+ if (mediaDetailFragment != null) {
+ mediaDetailFragment.onImageBackgroundChanged(ContextCompat.getColor(getContext(), R.color.white));
+ }
+ return true;
+ case R.id.menu_view_set_black_background:
+ if (mediaDetailFragment != null) {
+ mediaDetailFragment.onImageBackgroundChanged(ContextCompat.getColor(getContext(), R.color.black));
+ }
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ private void showReportDialog(final Media media) {
+ if (media == null) {
+ return;
+ }
+ final AlertDialog.Builder builder = new AlertDialog.Builder(requireActivity());
+ final String[] values = requireContext().getResources()
+ .getStringArray(R.array.report_violation_options);
+ builder.setTitle(R.string.report_violation);
+ builder.setItems(R.array.report_violation_options, (dialog, which) -> {
+ sendReportEmail(media, values[which]);
+ });
+ builder.setNegativeButton(R.string.cancel, (dialog, which) -> {});
+ builder.setCancelable(false);
+ builder.show();
+ }
+
+ private void sendReportEmail(final Media media, final String type) {
+ final String technicalInfo = getTechInfo(media, type);
+
+ final Intent feedbackIntent = new Intent(Intent.ACTION_SENDTO);
+ feedbackIntent.setType("message/rfc822");
+ feedbackIntent.setData(Uri.parse("mailto:"));
+ feedbackIntent.putExtra(Intent.EXTRA_EMAIL,
+ new String[]{CommonsApplication.REPORT_EMAIL});
+ feedbackIntent.putExtra(Intent.EXTRA_SUBJECT,
+ CommonsApplication.REPORT_EMAIL_SUBJECT);
+ feedbackIntent.putExtra(Intent.EXTRA_TEXT, technicalInfo);
+ try {
+ startActivity(feedbackIntent);
+ } catch (final ActivityNotFoundException e) {
+ Toast.makeText(getActivity(), R.string.no_email_client, Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ private String getTechInfo(final Media media, final String type) {
+ final StringBuilder builder = new StringBuilder();
+
+ builder.append("Report type: ")
+ .append(type)
+ .append("\n\n");
+
+ builder.append("Image that you want to report: ")
+ .append(media.getImageUrl())
+ .append("\n\n");
+
+ builder.append("User that you want to report: ")
+ .append(media.getUser())
+ .append("\n\n");
+
+ if (sessionManager.getUserName() != null) {
+ builder.append("Your username: ")
+ .append(sessionManager.getUserName())
+ .append("\n\n");
+ }
+
+ builder.append("Violation reason: ")
+ .append("\n");
+
+ builder.append("----------------------------------------------")
+ .append("\n")
+ .append("(please write reason here)")
+ .append("\n")
+ .append("----------------------------------------------")
+ .append("\n\n")
+ .append("Thank you for your report! Our team will investigate as soon as possible.")
+ .append("\n")
+ .append("Please note that images also have a `Nominate for deletion` button.");
+
+ return builder.toString();
+ }
+
+ /**
+ * Set the media as the device's wallpaper if the imageUrl is not null
+ * Fails silently if setting the wallpaper fails
+ * @param media
+ */
+ private void setWallpaper(Media media) {
+ if (media.getImageUrl() == null || media.getImageUrl().isEmpty()) {
+ Timber.d("Media URL not present");
+ return;
+ }
+ ImageUtils.setWallpaperFromImageUrl(getActivity(), Uri.parse(media.getImageUrl()));
+ }
+
+ /**
+ * Set the media as user's leaderboard avatar
+ * @param media
+ */
+ private void setAvatar(Media media) {
+ if (media.getImageUrl() == null || media.getImageUrl().isEmpty()) {
+ Timber.d("Media URL not present");
+ return;
+ }
+ ImageUtils.setAvatarFromImageUrl(getActivity(), media.getImageUrl(),
+ Objects.requireNonNull(sessionManager.getCurrentAccount()).name,
+ okHttpJsonApiClient, compositeDisposable);
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ if (!editable) { // Disable menu options for editable views
+ menu.clear(); // see http://stackoverflow.com/a/8495697/17865
+ inflater.inflate(R.menu.fragment_image_detail, menu);
+ if (binding.mediaDetailsPager != null) {
+ MediaDetailProvider provider = getMediaDetailProvider();
+ if(provider == null) {
+ return;
+ }
+ final int position;
+ if (isFromFeaturedRootFragment) {
+ position = this.position;
+ } else {
+ position = binding.mediaDetailsPager.getCurrentItem();
+ }
+
+ Media m = provider.getMediaAtPosition(position);
+ if (m != null) {
+ // Enable default set of actions, then re-enable different set of actions only if it is a failed contrib
+ menu.findItem(R.id.menu_browser_current_image).setEnabled(true).setVisible(true);
+ menu.findItem(R.id.menu_copy_link).setEnabled(true).setVisible(true);
+ menu.findItem(R.id.menu_share_current_image).setEnabled(true).setVisible(true);
+ menu.findItem(R.id.menu_download_current_image).setEnabled(true).setVisible(true);
+ menu.findItem(R.id.menu_bookmark_current_image).setEnabled(true).setVisible(true);
+ menu.findItem(R.id.menu_set_as_wallpaper).setEnabled(true).setVisible(true);
+ if (m.getUser() != null) {
+ menu.findItem(R.id.menu_view_user_page).setEnabled(true).setVisible(true);
+ }
+
+ try {
+ URL mediaUrl = new URL(m.getImageUrl());
+ this.handleBackgroundColorMenuItems(
+ () -> BitmapFactory.decodeStream(mediaUrl.openConnection().getInputStream()),
+ menu
+ );
+ } catch (Exception e) {
+ Timber.e("Cant detect media transparency");
+ }
+
+ // Initialize bookmark object
+ bookmark = new Bookmark(
+ m.getFilename(),
+ m.getAuthorOrUser(),
+ BookmarkPicturesContentProvider.uriForName(m.getFilename())
+ );
+ updateBookmarkState(menu.findItem(R.id.menu_bookmark_current_image));
+ final Integer contributionState = provider.getContributionStateAt(position);
+ if (contributionState != null) {
+ switch (contributionState) {
+ case Contribution.STATE_FAILED:
+ case Contribution.STATE_IN_PROGRESS:
+ case Contribution.STATE_QUEUED:
+ menu.findItem(R.id.menu_browser_current_image).setEnabled(false)
+ .setVisible(false);
+ menu.findItem(R.id.menu_copy_link).setEnabled(false)
+ .setVisible(false);
+ menu.findItem(R.id.menu_share_current_image).setEnabled(false)
+ .setVisible(false);
+ menu.findItem(R.id.menu_download_current_image).setEnabled(false)
+ .setVisible(false);
+ menu.findItem(R.id.menu_bookmark_current_image).setEnabled(false)
+ .setVisible(false);
+ menu.findItem(R.id.menu_set_as_wallpaper).setEnabled(false)
+ .setVisible(false);
+ break;
+ case Contribution.STATE_COMPLETED:
+ // Default set of menu items works fine. Treat same as regular media object
+ break;
+ }
+ }
+ } else {
+ menu.findItem(R.id.menu_browser_current_image).setEnabled(false)
+ .setVisible(false);
+ menu.findItem(R.id.menu_copy_link).setEnabled(false)
+ .setVisible(false);
+ menu.findItem(R.id.menu_share_current_image).setEnabled(false)
+ .setVisible(false);
+ menu.findItem(R.id.menu_download_current_image).setEnabled(false)
+ .setVisible(false);
+ menu.findItem(R.id.menu_bookmark_current_image).setEnabled(false)
+ .setVisible(false);
+ menu.findItem(R.id.menu_set_as_wallpaper).setEnabled(false)
+ .setVisible(false);
+ }
+
+ if (!sessionManager.isUserLoggedIn()) {
+ menu.findItem(R.id.menu_set_as_avatar).setVisible(false);
+ }
+
+ }
+ }
+ }
+
+ /**
+ * Decide wether or not we should display the background color menu items
+ * We display them if the image is transparent
+ * @param getBitmap
+ * @param menu
+ */
+ private void handleBackgroundColorMenuItems(Callable getBitmap, Menu menu) {
+ Observable.fromCallable(
+ getBitmap
+ ).subscribeOn(Schedulers.newThread())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(image -> {
+ if (image.hasAlpha()) {
+ menu.findItem(R.id.menu_view_set_white_background).setVisible(true).setEnabled(true);
+ menu.findItem(R.id.menu_view_set_black_background).setVisible(true).setEnabled(true);
+ }
+ });
+ }
+
+ private void updateBookmarkState(MenuItem item) {
+ boolean isBookmarked = bookmarkDao.findBookmark(bookmark);
+ if(isBookmarked) {
+ if(removedItems.contains(binding.mediaDetailsPager.getCurrentItem())) {
+ removedItems.remove(new Integer(binding.mediaDetailsPager.getCurrentItem()));
+ }
+ }
+ else {
+ if(!removedItems.contains(binding.mediaDetailsPager.getCurrentItem())) {
+ removedItems.add(binding.mediaDetailsPager.getCurrentItem());
+ }
+ }
+ int icon = isBookmarked ? R.drawable.menu_ic_round_star_filled_24px : R.drawable.menu_ic_round_star_border_24px;
+ item.setIcon(icon);
+ }
+
+ public void showImage(int i, boolean isWikipediaButtonDisplayed) {
+ this.isWikipediaButtonDisplayed = isWikipediaButtonDisplayed;
+ setViewPagerCurrentItem(i);
+ }
+
+ public void showImage(int i) {
+ setViewPagerCurrentItem(i);
+ }
+
+ /**
+ * This function waits for the item to load then sets the item to current item
+ * @param position current item that to be shown
+ */
+ private void setViewPagerCurrentItem(int position) {
+
+ final Handler handler = new Handler(Looper.getMainLooper());
+ final Runnable runnable = new Runnable() {
+ @Override
+ public void run() {
+ // Show the ProgressBar while waiting for the item to load
+ imageProgressBar.setVisibility(View.VISIBLE);
+ // Check if the adapter has enough items loaded
+ if(adapter.getCount() > position){
+ // Set the current item in the ViewPager
+ binding.mediaDetailsPager.setCurrentItem(position, false);
+ // Hide the ProgressBar once the item is loaded
+ imageProgressBar.setVisibility(View.GONE);
+ } else {
+ // If the item is not ready yet, post the Runnable again
+ handler.post(this);
+ }
+ }
+ };
+ // Start the Runnable
+ handler.post(runnable);
+ }
+
+ /**
+ * The method notify the viewpager that number of items have changed.
+ */
+ public void notifyDataSetChanged(){
+ if (null != adapter) {
+ adapter.notifyDataSetChanged();
+ }
+ }
+
+ @Override
+ public void onPageScrolled(int i, float v, int i2) {
+ if(getActivity() == null) {
+ Timber.d("Returning as activity is destroyed!");
+ return;
+ }
+
+ getActivity().invalidateOptionsMenu();
+ }
+
+ @Override
+ public void onPageSelected(int i) {
+ }
+
+ @Override
+ public void onPageScrollStateChanged(int i) {
+ }
+
+ public void onDataSetChanged() {
+ if (null != adapter) {
+ adapter.notifyDataSetChanged();
+ }
+ }
+
+ /**
+ * Called after the media is nominated for deletion
+ *
+ * @param index item position that has been nominated
+ */
+ @Override
+ public void nominatingForDeletion(int index) {
+ provider.refreshNominatedMedia(index);
+ }
+
+ public interface MediaDetailProvider {
+ Media getMediaAtPosition(int i);
+
+ int getTotalMediaCount();
+
+ Integer getContributionStateAt(int position);
+
+ // Reload media detail fragment once media is nominated
+ void refreshNominatedMedia(int index);
+ }
+
+ //FragmentStatePagerAdapter allows user to swipe across collection of images (no. of images undetermined)
+ private class MediaDetailAdapter extends FragmentStatePagerAdapter {
+
+ /**
+ * Keeps track of the current displayed fragment.
+ */
+ private Fragment mCurrentFragment;
+
+ public MediaDetailAdapter(FragmentManager fm) {
+ super(fm);
+ }
+
+ @Override
+ public Fragment getItem(int i) {
+ if (i == 0) {
+ // See bug https://code.google.com/p/android/issues/detail?id=27526
+ if(getActivity() == null) {
+ Timber.d("Skipping getItem. Returning as activity is destroyed!");
+ return null;
+ }
+ binding.mediaDetailsPager.postDelayed(() -> getActivity().invalidateOptionsMenu(), 5);
+ }
+ if (isFromFeaturedRootFragment) {
+ return MediaDetailFragment.forMedia(position+i, editable, isFeaturedImage, isWikipediaButtonDisplayed);
+ } else {
+ return MediaDetailFragment.forMedia(i, editable, isFeaturedImage, isWikipediaButtonDisplayed);
+ }
+ }
+
+ @Override
+ public int getCount() {
+ if (getActivity() == null) {
+ Timber.d("Skipping getCount. Returning as activity is destroyed!");
+ return 0;
+ }
+ return provider.getTotalMediaCount();
+ }
+
+ /**
+ * Get the currently displayed fragment.
+ * @return
+ */
+ public Fragment getCurrentFragment() {
+ return mCurrentFragment;
+ }
+
+ /**
+ * If current fragment is of type MediaDetailFragment, return it, otherwise return null.
+ * @return MediaDetailFragment
+ */
+ public MediaDetailFragment getCurrentMediaDetailFragment() {
+ if (mCurrentFragment instanceof MediaDetailFragment) {
+ return (MediaDetailFragment) mCurrentFragment;
+ }
+
+ return null;
+ }
+
+ /**
+ * Called to inform the adapter of which item is currently considered to be the "primary",
+ * that is the one show to the user as the current page.
+ * @param container
+ * @param position
+ * @param object
+ */
+ @Override
+ public void setPrimaryItem(@NonNull final ViewGroup container, final int position,
+ @NonNull final Object object) {
+ // Update the current fragment if changed
+ if(getCurrentFragment() != object) {
+ mCurrentFragment = ((Fragment)object);
+ }
+ super.setPrimaryItem(container, position, object);
+ }
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.kt b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.kt
deleted file mode 100644
index 92cca611e..000000000
--- a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.kt
+++ /dev/null
@@ -1,622 +0,0 @@
-package fr.free.nrw.commons.media
-
-import android.content.ActivityNotFoundException
-import android.content.DialogInterface
-import android.content.Intent
-import android.graphics.Bitmap
-import android.graphics.BitmapFactory
-import android.net.Uri
-import android.os.Bundle
-import android.os.Handler
-import android.os.Looper
-import android.view.LayoutInflater
-import android.view.Menu
-import android.view.MenuInflater
-import android.view.MenuItem
-import android.view.View
-import android.view.ViewGroup
-import android.widget.ProgressBar
-import android.widget.Toast
-import androidx.appcompat.app.AlertDialog
-import androidx.appcompat.app.AppCompatActivity
-import androidx.core.content.ContextCompat
-import androidx.viewpager.widget.ViewPager.OnPageChangeListener
-import com.google.android.material.snackbar.Snackbar
-import fr.free.nrw.commons.CommonsApplication
-import fr.free.nrw.commons.Media
-import fr.free.nrw.commons.R
-import fr.free.nrw.commons.auth.SessionManager
-import fr.free.nrw.commons.bookmarks.models.Bookmark
-import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesContentProvider
-import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao
-import fr.free.nrw.commons.contributions.Contribution
-import fr.free.nrw.commons.contributions.MainActivity
-import fr.free.nrw.commons.databinding.FragmentMediaDetailPagerBinding
-import fr.free.nrw.commons.di.CommonsDaggerSupportFragment
-import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient
-import fr.free.nrw.commons.profile.ProfileActivity
-import fr.free.nrw.commons.profile.ProfileActivity.Companion.startYourself
-import fr.free.nrw.commons.utils.ClipboardUtils.copy
-import fr.free.nrw.commons.utils.DownloadUtils.downloadMedia
-import fr.free.nrw.commons.utils.ImageUtils.setAvatarFromImageUrl
-import fr.free.nrw.commons.utils.ImageUtils.setWallpaperFromImageUrl
-import fr.free.nrw.commons.utils.NetworkUtils.isInternetConnectionEstablished
-import fr.free.nrw.commons.utils.ViewUtil.showShortSnackbar
-import fr.free.nrw.commons.utils.handleWebUrl
-import io.reactivex.Observable
-import io.reactivex.android.schedulers.AndroidSchedulers
-import io.reactivex.disposables.CompositeDisposable
-import io.reactivex.functions.Consumer
-import io.reactivex.schedulers.Schedulers
-import timber.log.Timber
-import java.net.URL
-import java.util.concurrent.Callable
-import javax.inject.Inject
-import androidx.core.net.toUri
-
-class MediaDetailPagerFragment : CommonsDaggerSupportFragment(), OnPageChangeListener,
- MediaDetailFragment.Callback {
- @JvmField
- @Inject
- var bookmarkDao: BookmarkPicturesDao? = null
-
- @JvmField
- @Inject
- var okHttpJsonApiClient: OkHttpJsonApiClient? = null
-
- @JvmField
- @Inject
- var sessionManager: SessionManager? = null
-
- var binding: FragmentMediaDetailPagerBinding? = null
- var editable: Boolean = false
- var isFeaturedImage: Boolean = false
- var isWikipediaButtonDisplayed: Boolean = false
- var adapter: MediaDetailAdapter? = null
- var bookmark: Bookmark? = null
- var mediaDetailProvider: MediaDetailProvider? = null
- var isFromFeaturedRootFragment: Boolean = false
- var position: Int = 0
-
- /**
- * ProgressBar used to indicate the loading status of media items.
- */
- var imageProgressBar: ProgressBar? = null
-
- var removedItems: ArrayList = ArrayList()
-
- fun clearRemoved() = removedItems.clear()
-
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View? {
- binding = FragmentMediaDetailPagerBinding.inflate(inflater, container, false)
- binding!!.mediaDetailsPager.addOnPageChangeListener(this)
- // Initialize the ProgressBar by finding it in the layout
- imageProgressBar = binding!!.root.findViewById(R.id.itemProgressBar)
- adapter = MediaDetailAdapter(this, childFragmentManager)
-
- // ActionBar is now supported in both activities - if this crashes something is quite wrong
- val actionBar = (activity as AppCompatActivity).supportActionBar
- if (actionBar != null) {
- actionBar.setDisplayHomeAsUpEnabled(true)
- } else {
- throw AssertionError("Action bar should not be null!")
- }
-
- // If fragment is associated with ProfileActivity, then hide the tabLayout
- if (activity is ProfileActivity) {
- (activity as ProfileActivity).setTabLayoutVisibility(false)
- } else if (activity is MainActivity) {
- (activity as MainActivity).hideTabs()
- }
-
- binding!!.mediaDetailsPager.adapter = adapter
-
- if (savedInstanceState != null) {
- val pageNumber = savedInstanceState.getInt("current-page")
- binding!!.mediaDetailsPager.setCurrentItem(pageNumber, false)
- requireActivity().invalidateOptionsMenu()
- }
- adapter!!.notifyDataSetChanged()
-
- return binding!!.root
- }
-
- override fun onSaveInstanceState(outState: Bundle) {
- super.onSaveInstanceState(outState)
- outState.putInt("current-page", binding!!.mediaDetailsPager.currentItem)
- outState.putBoolean("editable", editable)
- outState.putBoolean("isFeaturedImage", isFeaturedImage)
- }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- if (savedInstanceState != null) {
- editable = savedInstanceState.getBoolean("editable", false)
- isFeaturedImage = savedInstanceState.getBoolean("isFeaturedImage", false)
- }
- setHasOptionsMenu(true)
- initProvider()
- }
-
- /**
- * initialise the provider, based on from where the fragment was started, as in from an activity
- * or a fragment
- */
- private fun initProvider() {
- if (parentFragment is MediaDetailProvider) {
- mediaDetailProvider = parentFragment as MediaDetailProvider
- } else if (activity is MediaDetailProvider) {
- mediaDetailProvider = activity as MediaDetailProvider?
- } else {
- throw ClassCastException("Parent must implement MediaDetailProvider")
- }
- }
-
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- if (activity == null) {
- Timber.d("Returning as activity is destroyed!")
- return true
- }
-
- val m = mediaDetailProvider!!.getMediaAtPosition(binding!!.mediaDetailsPager.currentItem)
- val mediaDetailFragment = adapter!!.currentMediaDetailFragment
- when (item.itemId) {
- R.id.menu_bookmark_current_image -> {
- val bookmarkExists = bookmarkDao!!.updateBookmark(bookmark!!)
- val snackbar = if (bookmarkExists) Snackbar.make(
- requireView(),
- R.string.add_bookmark,
- Snackbar.LENGTH_LONG
- ) else Snackbar.make(
- requireView(), R.string.remove_bookmark, Snackbar.LENGTH_LONG
- )
- snackbar.show()
- updateBookmarkState(item)
- return true
- }
-
- R.id.menu_copy_link -> {
- val uri = m!!.pageTitle.canonicalUri
- copy("shareLink", uri, requireContext())
- Timber.d("Copied share link to clipboard: %s", uri)
- Toast.makeText(
- requireContext(), getString(R.string.menu_link_copied),
- Toast.LENGTH_SHORT
- ).show()
- return true
- }
-
- R.id.menu_share_current_image -> {
- val shareIntent = Intent(Intent.ACTION_SEND)
- shareIntent.setType("text/plain")
- shareIntent.putExtra(
- Intent.EXTRA_TEXT, """${m!!.displayTitle}
-${m.pageTitle.canonicalUri}"""
- )
- startActivity(Intent.createChooser(shareIntent, "Share image via..."))
-
- //Add media detail to backstack when the share button is clicked
- //So that when the share is cancelled or completed the media detail page is on top
- // of back stack fixing:https://github.com/commons-app/apps-android-commons/issues/2296
- val supportFragmentManager = requireActivity().supportFragmentManager
- if (supportFragmentManager.backStackEntryCount < 2) {
- supportFragmentManager
- .beginTransaction()
- .addToBackStack(MediaDetailPagerFragment::class.java.name)
- .commit()
- supportFragmentManager.executePendingTransactions()
- }
- return true
- }
-
- R.id.menu_browser_current_image -> {
- // View in browser
- handleWebUrl(requireContext(), m!!.pageTitle.mobileUri.toUri())
- return true
- }
-
- R.id.menu_download_current_image -> {
- // Download
- if (!isInternetConnectionEstablished(activity)) {
- showShortSnackbar(requireView(), R.string.no_internet)
- return false
- }
- downloadMedia(activity, m!!)
- return true
- }
-
- R.id.menu_set_as_wallpaper -> {
- // Set wallpaper
- setWallpaper(m!!)
- return true
- }
-
- R.id.menu_set_as_avatar -> {
- // Set avatar
- setAvatar(m!!)
- return true
- }
-
- R.id.menu_view_user_page -> {
- if (m?.user != null) {
- startYourself(
- requireActivity(), m.user!!,
- sessionManager!!.userName != m.user
- )
- }
- return true
- }
-
- R.id.menu_view_report -> {
- showReportDialog(m)
- mediaDetailFragment?.onImageBackgroundChanged(
- ContextCompat.getColor(
- requireContext(),
- R.color.white
- )
- )
- return true
- }
-
- R.id.menu_view_set_white_background -> {
- mediaDetailFragment?.onImageBackgroundChanged(
- ContextCompat.getColor(
- requireContext(),
- R.color.white
- )
- )
- return true
- }
-
- R.id.menu_view_set_black_background -> {
- mediaDetailFragment?.onImageBackgroundChanged(
- ContextCompat.getColor(
- requireContext(),
- R.color.black
- )
- )
- return true
- }
-
- else -> return super.onOptionsItemSelected(item)
- }
- }
-
- private fun showReportDialog(media: Media?) {
- if (media == null) {
- return
- }
- val builder = AlertDialog.Builder(requireActivity())
- val values = requireContext().resources
- .getStringArray(R.array.report_violation_options)
- builder.setTitle(R.string.report_violation)
- builder.setItems(
- R.array.report_violation_options
- ) { dialog: DialogInterface?, which: Int ->
- sendReportEmail(media, values[which])
- }
- builder.setNegativeButton(
- R.string.cancel
- ) { dialog: DialogInterface?, which: Int -> }
- builder.setCancelable(false)
- builder.show()
- }
-
- private fun sendReportEmail(media: Media, type: String) {
- val technicalInfo = getTechInfo(media, type)
-
- val feedbackIntent = Intent(Intent.ACTION_SENDTO)
- feedbackIntent.setType("message/rfc822")
- feedbackIntent.setData(Uri.parse("mailto:"))
- feedbackIntent.putExtra(
- Intent.EXTRA_EMAIL,
- arrayOf(CommonsApplication.REPORT_EMAIL)
- )
- feedbackIntent.putExtra(
- Intent.EXTRA_SUBJECT,
- CommonsApplication.REPORT_EMAIL_SUBJECT
- )
- feedbackIntent.putExtra(Intent.EXTRA_TEXT, technicalInfo)
- try {
- startActivity(feedbackIntent)
- } catch (e: ActivityNotFoundException) {
- Toast.makeText(activity, R.string.no_email_client, Toast.LENGTH_SHORT).show()
- }
- }
-
- private fun getTechInfo(media: Media, type: String): String {
- val builder = StringBuilder()
-
- builder.append("Report type: ")
- .append(type)
- .append("\n\n")
-
- builder.append("Image that you want to report: ")
- .append(media.imageUrl)
- .append("\n\n")
-
- builder.append("User that you want to report: ")
- .append(media.user)
- .append("\n\n")
-
- if (sessionManager!!.userName != null) {
- builder.append("Your username: ")
- .append(sessionManager!!.userName)
- .append("\n\n")
- }
-
- builder.append("Violation reason: ")
- .append("\n")
-
- builder.append("----------------------------------------------")
- .append("\n")
- .append("(please write reason here)")
- .append("\n")
- .append("----------------------------------------------")
- .append("\n\n")
- .append("Thank you for your report! Our team will investigate as soon as possible.")
- .append("\n")
- .append("Please note that images also have a `Nominate for deletion` button.")
-
- return builder.toString()
- }
-
- /**
- * Set the media as the device's wallpaper if the imageUrl is not null
- * Fails silently if setting the wallpaper fails
- * @param media
- */
- private fun setWallpaper(media: Media) {
- if (media.imageUrl == null || media.imageUrl!!.isEmpty()) {
- Timber.d("Media URL not present")
- return
- }
- setWallpaperFromImageUrl(requireActivity(), media.imageUrl!!.toUri())
- }
-
- /**
- * Set the media as user's leaderboard avatar
- * @param media
- */
- private fun setAvatar(media: Media) {
- if (media.imageUrl == null || media.imageUrl!!.isEmpty()) {
- Timber.d("Media URL not present")
- return
- }
- setAvatarFromImageUrl(
- requireActivity(), media.imageUrl!!,
- sessionManager!!.currentAccount!!.name,
- okHttpJsonApiClient!!, Companion.compositeDisposable
- )
- }
-
- override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
- if (!editable) { // Disable menu options for editable views
- menu.clear() // see http://stackoverflow.com/a/8495697/17865
- inflater.inflate(R.menu.fragment_image_detail, menu)
- if (binding!!.mediaDetailsPager != null) {
- val provider = mediaDetailProvider ?: return
- val position = if (isFromFeaturedRootFragment) {
- position
- } else {
- binding!!.mediaDetailsPager.currentItem
- }
-
- val m = provider.getMediaAtPosition(position)
- if (m != null) {
- // Enable default set of actions, then re-enable different set of actions only if it is a failed contrib
- menu.findItem(R.id.menu_browser_current_image).setEnabled(true).setVisible(true)
- menu.findItem(R.id.menu_copy_link).setEnabled(true).setVisible(true)
- menu.findItem(R.id.menu_share_current_image).setEnabled(true).setVisible(true)
- menu.findItem(R.id.menu_download_current_image).setEnabled(true)
- .setVisible(true)
- menu.findItem(R.id.menu_bookmark_current_image).setEnabled(true)
- .setVisible(true)
- menu.findItem(R.id.menu_set_as_wallpaper).setEnabled(true).setVisible(true)
- if (m.user != null) {
- menu.findItem(R.id.menu_view_user_page).setEnabled(true).setVisible(true)
- }
-
- try {
- val mediaUrl = URL(m.imageUrl)
- handleBackgroundColorMenuItems({
- BitmapFactory.decodeStream(
- mediaUrl.openConnection().getInputStream()
- )
- }, menu)
- } catch (e: Exception) {
- Timber.e("Cant detect media transparency")
- }
-
- // Initialize bookmark object
- bookmark = Bookmark(
- m.filename,
- m.getAuthorOrUser(),
- BookmarkPicturesContentProvider.uriForName(m.filename!!)
- )
- updateBookmarkState(menu.findItem(R.id.menu_bookmark_current_image))
- val contributionState = provider.getContributionStateAt(position)
- if (contributionState != null) {
- when (contributionState) {
- Contribution.STATE_FAILED, Contribution.STATE_IN_PROGRESS, Contribution.STATE_QUEUED -> {
- menu.findItem(R.id.menu_browser_current_image).setEnabled(false)
- .setVisible(false)
- menu.findItem(R.id.menu_copy_link).setEnabled(false)
- .setVisible(false)
- menu.findItem(R.id.menu_share_current_image).setEnabled(false)
- .setVisible(false)
- menu.findItem(R.id.menu_download_current_image).setEnabled(false)
- .setVisible(false)
- menu.findItem(R.id.menu_bookmark_current_image).setEnabled(false)
- .setVisible(false)
- menu.findItem(R.id.menu_set_as_wallpaper).setEnabled(false)
- .setVisible(false)
- }
-
- Contribution.STATE_COMPLETED -> {}
- }
- }
- } else {
- menu.findItem(R.id.menu_browser_current_image).setEnabled(false)
- .setVisible(false)
- menu.findItem(R.id.menu_copy_link).setEnabled(false)
- .setVisible(false)
- menu.findItem(R.id.menu_share_current_image).setEnabled(false)
- .setVisible(false)
- menu.findItem(R.id.menu_download_current_image).setEnabled(false)
- .setVisible(false)
- menu.findItem(R.id.menu_bookmark_current_image).setEnabled(false)
- .setVisible(false)
- menu.findItem(R.id.menu_set_as_wallpaper).setEnabled(false)
- .setVisible(false)
- }
-
- if (!sessionManager!!.isUserLoggedIn) {
- menu.findItem(R.id.menu_set_as_avatar).setVisible(false)
- }
- }
- }
- }
-
- /**
- * Decide wether or not we should display the background color menu items
- * We display them if the image is transparent
- * @param getBitmap
- * @param menu
- */
- private fun handleBackgroundColorMenuItems(getBitmap: Callable, menu: Menu) {
- Observable.fromCallable(
- getBitmap
- ).subscribeOn(Schedulers.newThread())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(Consumer { image: Bitmap ->
- if (image.hasAlpha()) {
- menu.findItem(R.id.menu_view_set_white_background).setVisible(true)
- .setEnabled(true)
- menu.findItem(R.id.menu_view_set_black_background).setVisible(true)
- .setEnabled(true)
- }
- })
- }
-
- private fun updateBookmarkState(item: MenuItem) {
- val isBookmarked = bookmarkDao!!.findBookmark(bookmark)
- if (isBookmarked) {
- if (removedItems.contains(binding!!.mediaDetailsPager.currentItem)) {
- removedItems.remove(binding!!.mediaDetailsPager.currentItem)
- }
- } else {
- if (!removedItems.contains(binding!!.mediaDetailsPager.currentItem)) {
- removedItems.add(binding!!.mediaDetailsPager.currentItem)
- }
- }
-
- item.setIcon(if (isBookmarked) {
- R.drawable.menu_ic_round_star_filled_24px
- } else {
- R.drawable.menu_ic_round_star_border_24px
- })
- }
-
- fun showImage(i: Int, isWikipediaButtonDisplayed: Boolean) {
- this.isWikipediaButtonDisplayed = isWikipediaButtonDisplayed
- setViewPagerCurrentItem(i)
- }
-
- fun showImage(i: Int) {
- setViewPagerCurrentItem(i)
- }
-
- /**
- * This function waits for the item to load then sets the item to current item
- * @param position current item that to be shown
- */
- private fun setViewPagerCurrentItem(position: Int) {
- val handler = Handler(Looper.getMainLooper())
- val runnable: Runnable = object : Runnable {
- override fun run() {
- // Show the ProgressBar while waiting for the item to load
- imageProgressBar!!.visibility = View.VISIBLE
- // Check if the adapter has enough items loaded
- if (adapter!!.count > position) {
- // Set the current item in the ViewPager
- binding!!.mediaDetailsPager.setCurrentItem(position, false)
- // Hide the ProgressBar once the item is loaded
- imageProgressBar!!.visibility = View.GONE
- } else {
- // If the item is not ready yet, post the Runnable again
- handler.post(this)
- }
- }
- }
- // Start the Runnable
- handler.post(runnable)
- }
-
- /**
- * The method notify the viewpager that number of items have changed.
- */
- fun notifyDataSetChanged() {
- if (null != adapter) {
- adapter!!.notifyDataSetChanged()
- }
- }
-
- override fun onPageScrolled(i: Int, v: Float, i2: Int) {
- if (activity == null) {
- Timber.d("Returning as activity is destroyed!")
- return
- }
-
- requireActivity().invalidateOptionsMenu()
- }
-
- override fun onPageSelected(i: Int) {
- }
-
- override fun onPageScrollStateChanged(i: Int) {
- }
-
- fun onDataSetChanged() {
- if (null != adapter) {
- adapter!!.notifyDataSetChanged()
- }
- }
-
- /**
- * Called after the media is nominated for deletion
- *
- * @param index item position that has been nominated
- */
- override fun nominatingForDeletion(index: Int) {
- mediaDetailProvider!!.refreshNominatedMedia(index)
- }
-
- companion object {
- private val compositeDisposable = CompositeDisposable()
-
- /**
- * Use this factory method to create a new instance of this fragment using the provided
- * parameters.
- *
- * This method will create a new instance of MediaDetailPagerFragment and the arguments will be
- * saved to a bundle which will be later available in the [.onCreate]
- * @param editable
- * @param isFeaturedImage
- * @return
- */
- @JvmStatic
- fun newInstance(editable: Boolean, isFeaturedImage: Boolean): MediaDetailPagerFragment {
- val mediaDetailPagerFragment = MediaDetailPagerFragment()
- val args = Bundle()
- args.putBoolean("is_editable", editable)
- args.putBoolean("is_featured_image", isFeaturedImage)
- mediaDetailPagerFragment.arguments = args
- return mediaDetailPagerFragment
- }
- }
-}
diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailProvider.kt b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailProvider.kt
deleted file mode 100644
index 591adfe75..000000000
--- a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailProvider.kt
+++ /dev/null
@@ -1,14 +0,0 @@
-package fr.free.nrw.commons.media
-
-import fr.free.nrw.commons.Media
-
-interface MediaDetailProvider {
- fun getMediaAtPosition(i: Int): Media?
-
- fun getTotalMediaCount(): Int
-
- fun getContributionStateAt(position: Int): Int?
-
- // Reload media detail fragment once media is nominated
- fun refreshNominatedMedia(index: Int)
-}
diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaInterface.kt b/app/src/main/java/fr/free/nrw/commons/media/MediaInterface.kt
index d6c87410a..ef0ef1f9c 100644
--- a/app/src/main/java/fr/free/nrw/commons/media/MediaInterface.kt
+++ b/app/src/main/java/fr/free/nrw/commons/media/MediaInterface.kt
@@ -1,6 +1,6 @@
package fr.free.nrw.commons.media
-import fr.free.nrw.commons.SUPPRESS_ERROR_LOG_HEADER
+import fr.free.nrw.commons.OkHttpConnectionFactory.UnsuccessfulResponseInterceptor.SUPPRESS_ERROR_LOG_HEADER
import fr.free.nrw.commons.wikidata.mwapi.MwQueryResponse
import io.reactivex.Single
import retrofit2.http.GET
@@ -186,25 +186,13 @@ interface MediaInterface {
): Single
companion object {
- /**
- * Retrieved thumbnail height will be about this tall, but must be at least this height.
- * A larger number means higher thumbnail resolution but more network usage.
- */
- const val THUMB_HEIGHT_PX = 450
-
const val MEDIA_PARAMS =
- "&prop=imageinfo|coordinates&iiprop=url|extmetadata|user&&iiurlheight=" +
- THUMB_HEIGHT_PX +
- "&iiextmetadatafilter=DateTime|Categories|GPSLatitude|GPSLongitude|" +
- "ImageDescription|DateTimeOriginal|Artist|LicenseShortName|LicenseUrl"
+ "&prop=imageinfo|coordinates&iiprop=url|extmetadata|user&&iiurlwidth=640&iiextmetadatafilter=DateTime|Categories|GPSLatitude|GPSLongitude|ImageDescription|DateTimeOriginal|Artist|LicenseShortName|LicenseUrl"
/**
* fetches category detail(title, hidden) for each category along with File information
*/
const val MEDIA_PARAMS_WITH_CATEGORY_DETAILS =
- "&clprop=hidden&prop=categories|imageinfo&iiprop=url|extmetadata|user&&iiurlheight=" +
- THUMB_HEIGHT_PX +
- "&iiextmetadatafilter=DateTime|GPSLatitude|GPSLongitude|ImageDescription|" +
- "DateTimeOriginal|Artist|LicenseShortName|LicenseUrl"
+ "&clprop=hidden&prop=categories|imageinfo&iiprop=url|extmetadata|user&&iiurlwidth=640&iiextmetadatafilter=DateTime|GPSLatitude|GPSLongitude|ImageDescription|DateTimeOriginal|Artist|LicenseShortName|LicenseUrl"
}
}
diff --git a/app/src/main/java/fr/free/nrw/commons/media/MwParseResponse.java b/app/src/main/java/fr/free/nrw/commons/media/MwParseResponse.java
new file mode 100644
index 000000000..28df3811a
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/media/MwParseResponse.java
@@ -0,0 +1,25 @@
+package fr.free.nrw.commons.media;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import fr.free.nrw.commons.wikidata.mwapi.MwResponse;
+
+public class MwParseResponse extends MwResponse {
+ @Nullable
+ private MwParseResult parse;
+
+ @Nullable
+ public MwParseResult parse() {
+ return parse;
+ }
+
+ public boolean success() {
+ return parse != null;
+ }
+
+ @VisibleForTesting
+ protected void setParse(@Nullable MwParseResult parse) {
+ this.parse = parse;
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/media/MwParseResponse.kt b/app/src/main/java/fr/free/nrw/commons/media/MwParseResponse.kt
deleted file mode 100644
index fc0282a9e..000000000
--- a/app/src/main/java/fr/free/nrw/commons/media/MwParseResponse.kt
+++ /dev/null
@@ -1,17 +0,0 @@
-package fr.free.nrw.commons.media
-
-import androidx.annotation.VisibleForTesting
-import fr.free.nrw.commons.wikidata.mwapi.MwResponse
-
-class MwParseResponse : MwResponse() {
- private var parse: MwParseResult? = null
-
- fun parse(): MwParseResult? = parse
-
- fun success(): Boolean = parse != null
-
- @VisibleForTesting
- protected fun setParse(parse: MwParseResult?) {
- this.parse = parse
- }
-}
diff --git a/app/src/main/java/fr/free/nrw/commons/media/MwParseResult.java b/app/src/main/java/fr/free/nrw/commons/media/MwParseResult.java
new file mode 100644
index 000000000..edb7ff447
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/media/MwParseResult.java
@@ -0,0 +1,18 @@
+package fr.free.nrw.commons.media;
+
+import com.google.gson.annotations.SerializedName;
+
+public class MwParseResult {
+ @SuppressWarnings("unused") private int pageid;
+ @SuppressWarnings("unused") private int index;
+ private MwParseText text;
+
+ public String text() {
+ return text.text;
+ }
+
+
+ public class MwParseText{
+ @SerializedName("*") private String text;
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/media/MwParseResult.kt b/app/src/main/java/fr/free/nrw/commons/media/MwParseResult.kt
deleted file mode 100644
index 7aacdea09..000000000
--- a/app/src/main/java/fr/free/nrw/commons/media/MwParseResult.kt
+++ /dev/null
@@ -1,18 +0,0 @@
-package fr.free.nrw.commons.media
-
-import com.google.gson.annotations.SerializedName
-
-class MwParseResult {
- private val pageid = 0
- private val index = 0
- private val text: MwParseText? = null
-
- fun text(): String? {
- return text?.text
- }
-
- inner class MwParseText {
- @SerializedName("*")
- internal val text: String? = null
- }
-}
diff --git a/app/src/main/java/fr/free/nrw/commons/mwapi/OkHttpJsonApiClient.kt b/app/src/main/java/fr/free/nrw/commons/mwapi/OkHttpJsonApiClient.kt
index a2f92c2e6..291c834bd 100644
--- a/app/src/main/java/fr/free/nrw/commons/mwapi/OkHttpJsonApiClient.kt
+++ b/app/src/main/java/fr/free/nrw/commons/mwapi/OkHttpJsonApiClient.kt
@@ -281,7 +281,6 @@ class OkHttpJsonApiClient @Inject constructor(
FeedbackResponse::class.java
)
} catch (e: Exception) {
- e.printStackTrace()
return@fromCallable FeedbackResponse(0, 0, 0, FeaturedImages(0, 0), 0, "")
}
}
@@ -532,38 +531,40 @@ ${"wd:" + place.wikiDataEntityId}"""
)
if (placeBindings != null) {
for ((item1, label, location, clas) in placeBindings) {
- val input = location.value
- val pattern = Pattern.compile(
- "Point\\(([-+]?[0-9]*\\.?[0-9]+) ([-+]?[0-9]*\\.?[0-9]+)\\)"
- )
- val matcher = pattern.matcher(input)
+ if (item1 != null && label != null && clas != null) {
+ val input = location.value
+ val pattern = Pattern.compile(
+ "Point\\(([-+]?[0-9]*\\.?[0-9]+) ([-+]?[0-9]*\\.?[0-9]+)\\)"
+ )
+ val matcher = pattern.matcher(input)
- if (matcher.find()) {
- val longStr = matcher.group(1)
- val latStr = matcher.group(2)
- val itemUrl = item1.value
- val itemName = label.value.replace("&", "&")
- val itemLatitude = latStr
- val itemLongitude = longStr
- val itemClass = clas.value
+ if (matcher.find()) {
+ val longStr = matcher.group(1)
+ val latStr = matcher.group(2)
+ val itemUrl = item1.value
+ val itemName = label.value.replace("&", "&")
+ val itemLatitude = latStr
+ val itemLongitude = longStr
+ val itemClass = clas.value
- val formattedItemName =
- if (!itemClass.isEmpty())
- "$itemName ($itemClass)"
- else
- itemName
+ val formattedItemName =
+ if (!itemClass.isEmpty())
+ "$itemName ($itemClass)"
+ else
+ itemName
- val kmlEntry = ("""
-
- $formattedItemName
- $itemUrl
-
- $itemLongitude,$itemLatitude
-
- """)
- kmlString = kmlString + kmlEntry
- } else {
- Timber.e("No match found")
+ val kmlEntry = ("""
+
+ $formattedItemName
+ $itemUrl
+
+ $itemLongitude,$itemLatitude
+
+ """)
+ kmlString = kmlString + kmlEntry
+ } else {
+ Timber.e("No match found")
+ }
}
}
}
@@ -588,35 +589,37 @@ ${"wd:" + place.wikiDataEntityId}"""
val placeBindings = runQuery(leftLatLng, rightLatLng)
if (placeBindings != null) {
for ((item1, label, location, clas) in placeBindings) {
- val input = location.value
- val pattern = Pattern.compile(
- "Point\\(([-+]?[0-9]*\\.?[0-9]+) ([-+]?[0-9]*\\.?[0-9]+)\\)"
- )
- val matcher = pattern.matcher(input)
+ if (item1 != null && label != null && clas != null) {
+ val input = location.value
+ val pattern = Pattern.compile(
+ "Point\\(([-+]?[0-9]*\\.?[0-9]+) ([-+]?[0-9]*\\.?[0-9]+)\\)"
+ )
+ val matcher = pattern.matcher(input)
- if (matcher.find()) {
- val longStr = matcher.group(1)
- val latStr = matcher.group(2)
- val itemUrl = item1.value
- val itemName = label.value.replace("&", "&")
- val itemLatitude = latStr
- val itemLongitude = longStr
- val itemClass = clas.value
+ if (matcher.find()) {
+ val longStr = matcher.group(1)
+ val latStr = matcher.group(2)
+ val itemUrl = item1.value
+ val itemName = label.value.replace("&", "&")
+ val itemLatitude = latStr
+ val itemLongitude = longStr
+ val itemClass = clas.value
- val formattedItemName = if (!itemClass.isEmpty())
- "$itemName ($itemClass)"
- else
- itemName
+ val formattedItemName = if (!itemClass.isEmpty())
+ "$itemName ($itemClass)"
+ else
+ itemName
- val gpxEntry =
- ("""
-
- $itemName
- $itemUrl
- """)
- gpxString = gpxString + gpxEntry
- } else {
- Timber.e("No match found")
+ val gpxEntry =
+ ("""
+
+ $itemName
+ $itemUrl
+ """)
+ gpxString = gpxString + gpxEntry
+ } else {
+ Timber.e("No match found")
+ }
}
}
}
diff --git a/app/src/main/java/fr/free/nrw/commons/navtab/MoreBottomSheetFragment.kt b/app/src/main/java/fr/free/nrw/commons/navtab/MoreBottomSheetFragment.kt
index a4f08f241..3f7a196fe 100644
--- a/app/src/main/java/fr/free/nrw/commons/navtab/MoreBottomSheetFragment.kt
+++ b/app/src/main/java/fr/free/nrw/commons/navtab/MoreBottomSheetFragment.kt
@@ -18,6 +18,7 @@ import fr.free.nrw.commons.BuildConfig
import fr.free.nrw.commons.CommonsApplication
import fr.free.nrw.commons.CommonsApplication.ActivityLogoutListener
import fr.free.nrw.commons.R
+import fr.free.nrw.commons.WelcomeActivity
import fr.free.nrw.commons.actions.PageEditClient
import fr.free.nrw.commons.databinding.FragmentMoreBottomSheetBinding
import fr.free.nrw.commons.di.ApplicationlessInjection
@@ -31,7 +32,6 @@ import fr.free.nrw.commons.logging.CommonsLogSender
import fr.free.nrw.commons.profile.ProfileActivity
import fr.free.nrw.commons.review.ReviewActivity
import fr.free.nrw.commons.settings.SettingsActivity
-import fr.free.nrw.commons.startWelcome
import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
@@ -114,13 +114,13 @@ class MoreBottomSheetFragment : BottomSheetDialogFragment() {
val level = store.getString("userAchievementsLevel", "0")
if (level == "0"){
binding?.moreProfile?.text = getString(
- R.string.profile_withoutLevel,
+ R.string.profileLevel,
getUserName(),
getString(R.string.see_your_achievements) // Second argument
)
} else {
binding?.moreProfile?.text = getString(
- R.string.profile_withLevel,
+ R.string.profileLevel,
getUserName(),
level
)
@@ -241,7 +241,7 @@ class MoreBottomSheetFragment : BottomSheetDialogFragment() {
}
fun onTutorialClicked() {
- requireContext().startWelcome()
+ WelcomeActivity.startYourself(requireActivity())
}
fun onSettingsClicked() {
diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/BottomSheetAdapter.kt b/app/src/main/java/fr/free/nrw/commons/nearby/BottomSheetAdapter.kt
index 714cd388f..a83d49f75 100644
--- a/app/src/main/java/fr/free/nrw/commons/nearby/BottomSheetAdapter.kt
+++ b/app/src/main/java/fr/free/nrw/commons/nearby/BottomSheetAdapter.kt
@@ -16,7 +16,7 @@ import fr.free.nrw.commons.nearby.model.BottomSheetItem
/**
* RecyclerView Adapter for displaying items in a bottom sheet.
*
- * @param context The context used for inflating layout resources.
+ * @property context The context used for inflating layout resources.
* @property itemList The list of BottomSheetItem objects to display.
* @constructor Creates an instance of BottomSheetAdapter.
*/
diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/CheckBoxTriStates.java b/app/src/main/java/fr/free/nrw/commons/nearby/CheckBoxTriStates.java
index 323f9756f..db2c1f5d9 100644
--- a/app/src/main/java/fr/free/nrw/commons/nearby/CheckBoxTriStates.java
+++ b/app/src/main/java/fr/free/nrw/commons/nearby/CheckBoxTriStates.java
@@ -44,7 +44,7 @@ public class CheckBoxTriStates extends AppCompatCheckBox {
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
switch (state) {
case UNKNOWN:
- setState(UNCHECKED);
+ setState(UNCHECKED);;
break;
case UNCHECKED:
setState(CHECKED);
diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyFilterSearchRecyclerViewAdapter.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyFilterSearchRecyclerViewAdapter.java
index 53e9970a6..b5f760c9f 100644
--- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyFilterSearchRecyclerViewAdapter.java
+++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyFilterSearchRecyclerViewAdapter.java
@@ -91,7 +91,6 @@ public class NearbyFilterSearchRecyclerViewAdapter
label.setSelected(!label.isSelected());
holder.placeTypeLayout.setSelected(label.isSelected());
- NearbyFilterState.setSelectedLabels(new ArrayList<>(selectedLabels));
callback.filterByMarkerType(selectedLabels, 0, false, false);
});
}
@@ -153,7 +152,6 @@ public class NearbyFilterSearchRecyclerViewAdapter
label.setSelected(false);
selectedLabels.remove(label);
}
- NearbyFilterState.setSelectedLabels(new ArrayList<>(selectedLabels));
notifyDataSetChanged();
}
@@ -165,7 +163,6 @@ public class NearbyFilterSearchRecyclerViewAdapter
selectedLabels.add(label);
}
}
- NearbyFilterState.setSelectedLabels(new ArrayList<>(selectedLabels));
notifyDataSetChanged();
}
diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyFilterState.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyFilterState.java
index d0aec96af..d3ece9bfa 100644
--- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyFilterState.java
+++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyFilterState.java
@@ -9,7 +9,7 @@ public class NearbyFilterState {
private int checkBoxTriState;
private ArrayList selectedLabels;
- private static NearbyFilterState nearbyFilterStateInstance;
+ private static NearbyFilterState nearbyFılterStateInstance;
/**
* Define initial filter values here
@@ -23,10 +23,10 @@ public class NearbyFilterState {
}
public static NearbyFilterState getInstance() {
- if (nearbyFilterStateInstance == null) {
- nearbyFilterStateInstance = new NearbyFilterState();
+ if (nearbyFılterStateInstance == null) {
+ nearbyFılterStateInstance = new NearbyFilterState();
}
- return nearbyFilterStateInstance;
+ return nearbyFılterStateInstance;
}
public static void setSelectedLabels(ArrayList selectedLabels) {
diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyPlaces.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyPlaces.java
index 3bb2f549f..caae8ee45 100644
--- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyPlaces.java
+++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyPlaces.java
@@ -1,14 +1,10 @@
package fr.free.nrw.commons.nearby;
-import static java.util.Collections.emptyList;
-
import android.location.Location;
-import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import fr.free.nrw.commons.nearby.model.NearbyQueryParams;
-import fr.free.nrw.commons.nearby.model.NearbyQueryParams.Radial;
-import fr.free.nrw.commons.nearby.model.NearbyQueryParams.Rectangular;
import java.io.IOException;
+import java.util.Collections;
import java.util.List;
import javax.inject.Inject;
@@ -50,14 +46,13 @@ public class NearbyPlaces {
* @param customQuery
* @return list of places obtained
*/
- @NonNull
List radiusExpander(final LatLng currentLatLng, final String lang,
final boolean returnClosestResult, @Nullable final String customQuery) throws Exception {
final int minResults;
final double maxRadius;
- List places = emptyList();
+ List places = Collections.emptyList();
// If returnClosestResult is true, then this means that we are trying to get closest point
// to use in cardView in Contributions fragment
@@ -118,7 +113,6 @@ public class NearbyPlaces {
* @return A list of places obtained from the Wikidata query.
* @throws Exception If an error occurs during the retrieval process.
*/
- @NonNull
public List getFromWikidataQuery(
final fr.free.nrw.commons.location.LatLng centerPoint,
final fr.free.nrw.commons.location.LatLng screenTopRight,
@@ -126,11 +120,11 @@ public class NearbyPlaces {
final boolean shouldQueryForMonuments,
@Nullable final String customQuery) throws Exception {
if (customQuery != null) {
- final List nearbyPlaces = okHttpJsonApiClient.getNearbyPlaces(
- new Rectangular(screenTopRight, screenBottomLeft), lang,
+ return okHttpJsonApiClient
+ .getNearbyPlaces(
+ new NearbyQueryParams.Rectangular(screenTopRight, screenBottomLeft), lang,
shouldQueryForMonuments,
customQuery);
- return nearbyPlaces != null ? nearbyPlaces : emptyList();
}
final int lowerLimit = 1000, upperLimit = 1500;
@@ -147,10 +141,9 @@ public class NearbyPlaces {
final int itemCount = okHttpJsonApiClient.getNearbyItemCount(
new NearbyQueryParams.Rectangular(screenTopRight, screenBottomLeft));
if (itemCount < upperLimit) {
- final List nearbyPlaces = okHttpJsonApiClient.getNearbyPlaces(
- new Rectangular(screenTopRight, screenBottomLeft), lang,
+ return okHttpJsonApiClient.getNearbyPlaces(
+ new NearbyQueryParams.Rectangular(screenTopRight, screenBottomLeft), lang,
shouldQueryForMonuments, null);
- return nearbyPlaces != null ? nearbyPlaces : emptyList();
}
}
@@ -182,10 +175,9 @@ public class NearbyPlaces {
maxRadius = targetRadius - 1;
}
}
- final List nearbyPlaces = okHttpJsonApiClient.getNearbyPlaces(
- new Radial(centerPoint, targetRadius / 100f), lang, shouldQueryForMonuments,
+ return okHttpJsonApiClient.getNearbyPlaces(
+ new NearbyQueryParams.Radial(centerPoint, targetRadius / 100f), lang, shouldQueryForMonuments,
null);
- return nearbyPlaces != null ? nearbyPlaces : emptyList();
}
/**
diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/Place.java b/app/src/main/java/fr/free/nrw/commons/nearby/Place.java
index cff2ed4de..3b3b798eb 100644
--- a/app/src/main/java/fr/free/nrw/commons/nearby/Place.java
+++ b/app/src/main/java/fr/free/nrw/commons/nearby/Place.java
@@ -153,10 +153,7 @@ public class Place implements Parcelable {
.build(),
item.getPic().getValue(),
// Checking if the place exists or not
- (item.getDestroyed().getValue() == "") && (item.getEndTime().getValue() == "")
- && (item.getDateOfOfficialClosure().getValue() == "")
- && (item.getPointInTime().getValue()==""),
- entityId);
+ (item.getDestroyed().getValue() == "") && (item.getEndTime().getValue() == ""), entityId);
}
/**
diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/Sitelinks.java b/app/src/main/java/fr/free/nrw/commons/nearby/Sitelinks.java
index 93bcba717..fc01585ce 100644
--- a/app/src/main/java/fr/free/nrw/commons/nearby/Sitelinks.java
+++ b/app/src/main/java/fr/free/nrw/commons/nearby/Sitelinks.java
@@ -105,6 +105,9 @@ public class Sitelinks implements Parcelable {
private String commonsLink;
private String wikipediaLink;
+ public Builder() {
+ }
+
public Sitelinks.Builder setWikipediaLink(String link) {
this.wikipediaLink = link;
return this;
diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/WikidataFeedback.kt b/app/src/main/java/fr/free/nrw/commons/nearby/WikidataFeedback.kt
index 1775401dd..e5196bee8 100644
--- a/app/src/main/java/fr/free/nrw/commons/nearby/WikidataFeedback.kt
+++ b/app/src/main/java/fr/free/nrw/commons/nearby/WikidataFeedback.kt
@@ -63,10 +63,7 @@ class WikidataFeedback : BaseActivity() {
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
binding.appCompatButton.setOnClickListener {
- var desc = when (binding.radioGroup.checkedRadioButtonId) {
- R.id.radioButton2 -> getString(R.string.is_at_a_different_place_wikidata, place)
- else -> findViewById(binding.radioGroup.checkedRadioButtonId).text
- }
+ var desc = findViewById(binding.radioGroup.checkedRadioButtonId).text
var det = binding.detailsEditText.text.toString()
if (binding.radioGroup.checkedRadioButtonId == R.id.radioButton3 && binding.detailsEditText.text.isNullOrEmpty()) {
Toast
@@ -106,4 +103,4 @@ class WikidataFeedback : BaseActivity() {
onBackPressed()
return true
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/fragments/CommonPlaceClickActions.kt b/app/src/main/java/fr/free/nrw/commons/nearby/fragments/CommonPlaceClickActions.kt
index 5f4d0ab13..202f2c305 100644
--- a/app/src/main/java/fr/free/nrw/commons/nearby/fragments/CommonPlaceClickActions.kt
+++ b/app/src/main/java/fr/free/nrw/commons/nearby/fragments/CommonPlaceClickActions.kt
@@ -10,13 +10,12 @@ import androidx.activity.result.ActivityResultLauncher
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.PopupMenu
import fr.free.nrw.commons.R
+import fr.free.nrw.commons.Utils
import fr.free.nrw.commons.auth.LoginActivity
import fr.free.nrw.commons.contributions.ContributionController
import fr.free.nrw.commons.kvstore.JsonKvStore
import fr.free.nrw.commons.nearby.Place
import fr.free.nrw.commons.utils.ActivityUtils
-import fr.free.nrw.commons.utils.handleGeoCoordinates
-import fr.free.nrw.commons.utils.handleWebUrl
import fr.free.nrw.commons.wikidata.WikidataConstants
import timber.log.Timber
import javax.inject.Inject
@@ -105,7 +104,7 @@ class CommonPlaceClickActions
fun onDirectionsClicked(): (Place) -> Unit =
{
- handleGeoCoordinates(activity, it.getLocation())
+ Utils.handleGeoCoordinates(activity, it.getLocation())
}
private fun storeSharedPrefs(selectedPlace: Place) {
@@ -114,7 +113,7 @@ class CommonPlaceClickActions
}
private fun openWebView(link: Uri): Boolean {
- handleWebUrl(activity, link)
+ Utils.handleWebUrl(activity, link)
return true
}
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 3e6e71511..25baf3a92 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
@@ -58,6 +58,7 @@ 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
import fr.free.nrw.commons.contributions.ContributionController
import fr.free.nrw.commons.contributions.MainActivity
@@ -74,7 +75,7 @@ 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.MediaDetailProvider
+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
@@ -103,10 +104,6 @@ import fr.free.nrw.commons.utils.NearbyFABUtils.removeAnchorFromFAB
import fr.free.nrw.commons.utils.NetworkUtils.isInternetConnectionEstablished
import fr.free.nrw.commons.utils.SystemThemeUtils
import fr.free.nrw.commons.utils.ViewUtil.showLongToast
-import fr.free.nrw.commons.utils.copyToClipboard
-import fr.free.nrw.commons.utils.handleGeoCoordinates
-import fr.free.nrw.commons.utils.handleWebUrl
-import fr.free.nrw.commons.utils.isMonumentsEnabled
import fr.free.nrw.commons.wikidata.WikidataConstants
import fr.free.nrw.commons.wikidata.WikidataEditListener
import fr.free.nrw.commons.wikidata.WikidataEditListener.WikidataP18EditListener
@@ -125,7 +122,6 @@ import org.osmdroid.views.CustomZoomButtonsController
import org.osmdroid.views.MapView
import org.osmdroid.views.overlay.MapEventsOverlay
import org.osmdroid.views.overlay.Marker
-import org.osmdroid.views.overlay.Overlay
import org.osmdroid.views.overlay.ScaleBarOverlay
import org.osmdroid.views.overlay.ScaleDiskOverlay
import org.osmdroid.views.overlay.TilesOverlay
@@ -142,6 +138,7 @@ import java.util.UUID
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import javax.inject.Named
+import javax.sql.DataSource
import kotlin.concurrent.Volatile
@@ -151,7 +148,7 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
LocationUpdateListener,
LocationPermissionCallback,
ItemClickListener,
- MediaDetailProvider {
+ MediaDetailPagerFragment.MediaDetailProvider {
var binding: FragmentNearbyParentBinding? = null
val mapEventsOverlay: MapEventsOverlay = MapEventsOverlay(object : MapEventsReceiver {
@@ -269,9 +266,6 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
private var dataList: MutableList? = null
private var bottomSheetAdapter: BottomSheetAdapter? = null
- private var userLocationOverlay: Overlay? = null
- private var userLocationErrorOverlay: Overlay? = null
-
private val galleryPickLauncherForResult =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
controller?.handleActivityResultWithCallback(
@@ -468,7 +462,7 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
}
}
_isDarkTheme = systemThemeUtils?.isDeviceInNightMode() == true
- if (isMonumentsEnabled) {
+ if (Utils.isMonumentsEnabled(Date())) {
binding?.rlContainerWlmMonthMessage?.visibility = View.VISIBLE
} else {
binding?.rlContainerWlmMonthMessage?.visibility = View.GONE
@@ -729,7 +723,7 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
val targetP = GeoPoint(target.latitude, target.longitude)
mapCenter = targetP
binding?.map?.controller?.setCenter(targetP)
- updateUserLocationOverlays(targetP, true)
+ recenterMarkerToPosition(targetP)
if (!isCameFromExploreMap()) {
moveCameraToPosition(targetP)
}
@@ -837,7 +831,7 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
loadAnimations()
setBottomSheetCallbacks()
addActionToTitle()
- if (!isMonumentsEnabled) {
+ if (!Utils.isMonumentsEnabled(Date())) {
NearbyFilterState.setWlmSelected(false)
}
}
@@ -881,12 +875,6 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
fun initNearbyFilter() {
binding!!.nearbyFilterList.root.visibility = View.GONE
hideBottomSheet()
- binding!!.nearbyFilter.searchViewLayout.searchView.apply {
- setIconifiedByDefault(false)
- isIconified = false
- setQuery("", false)
- clearFocus()
- }
binding!!.nearbyFilter.searchViewLayout.searchView.setOnQueryTextFocusChangeListener { v, hasFocus ->
setLayoutHeightAlignedToWidth(
1.25,
@@ -930,7 +918,6 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
return _isDarkTheme
}
})
- restoreStoredFilterSelection()
binding!!.nearbyFilterList.root
.layoutParams.width = getScreenWidth(
requireActivity(),
@@ -949,22 +936,6 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
})
}
- private fun restoreStoredFilterSelection() {
- val adapter = nearbyFilterSearchRecyclerViewAdapter ?: return
- val savedLabels = ArrayList(NearbyFilterState.getInstance().selectedLabels)
- adapter.selectedLabels.clear()
- val savedSet = savedLabels.toSet()
- Label.valuesAsList().forEach { label ->
- val isSelected = savedSet.contains(label)
- label.setSelected(isSelected)
- if (isSelected) {
- adapter.selectedLabels.add(label)
- }
- }
- NearbyFilterState.setSelectedLabels(ArrayList(adapter.selectedLabels))
- adapter.notifyDataSetChanged()
- }
-
override fun setCheckBoxAction() {
binding!!.nearbyFilterList.checkboxTriStates.addAction()
binding!!.nearbyFilterList.checkboxTriStates.state = CheckBoxTriStates.UNKNOWN
@@ -1002,7 +973,7 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
} else if (bottomSheetDetailsBehavior!!.state
== BottomSheetBehavior.STATE_EXPANDED
) {
- bottomSheetDetailsBehavior!!.setState(BottomSheetBehavior.STATE_COLLAPSED)
+ bottomSheetDetailsBehavior!!.state = BottomSheetBehavior.STATE_COLLAPSED
}
}
@@ -1041,10 +1012,11 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
*/
private fun addActionToTitle() {
binding!!.bottomSheetDetails.title.setOnLongClickListener { view ->
- requireContext().copyToClipboard(
- "place", binding!!.bottomSheetDetails.title.text.toString()
+ Utils.copy(
+ "place", binding!!.bottomSheetDetails.title.text.toString(),
+ context
)
- Toast.makeText(requireContext(), fr.free.nrw.commons.R.string.text_copy, Toast.LENGTH_SHORT)
+ Toast.makeText(context, fr.free.nrw.commons.R.string.text_copy, Toast.LENGTH_SHORT)
.show()
true
}
@@ -1092,7 +1064,7 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
override fun updateListFragment(placeList: List) {
adapter!!.clear()
- adapter!!.items = placeList.filter{ it.name.isNotEmpty() }
+ adapter!!.items = placeList
binding!!.bottomSheetNearby.noResultsMessage.visibility =
if (placeList.isEmpty()) View.VISIBLE else View.GONE
}
@@ -1603,7 +1575,7 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
searchLatLng,
false,
true,
- isMonumentsEnabled,
+ Utils.isMonumentsEnabled(Date()),
customQuery
)
}
@@ -1656,7 +1628,7 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
searchLatLng,
false,
true,
- isMonumentsEnabled,
+ Utils.isMonumentsEnabled(Date()),
customQuery
)
}
@@ -1784,9 +1756,9 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
override fun animateFABs() {
if (binding!!.fabPlus.isShown) {
if (isFABsExpanded) {
- collapseFABs(true)
+ collapseFABs(isFABsExpanded)
} else {
- expandFABs(false)
+ expandFABs(isFABsExpanded)
}
}
}
@@ -1890,8 +1862,6 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
lastKnownLocation = latLng
NearbyController.currentLocation = lastKnownLocation
presenter!!.updateMapAndList(locationChangeType)
-
- updateUserLocationOverlays(GeoPoint(latLng.latitude, latLng.longitude), true)
}
override fun onLocationChangedSignificantly(latLng: LatLng) {
@@ -2043,17 +2013,17 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
if (place.exists && place.pic.trim { it <= ' ' }.isEmpty()) {
shouldUpdateMarker = true
}
- } else if (displayExists) {
+ } else if (displayExists && !displayNeedsPhoto) {
// Exists and all included needs and doesn't needs photo
if (place.exists) {
shouldUpdateMarker = true
}
- } else if (displayNeedsPhoto) {
+ } else if (!displayExists && displayNeedsPhoto) {
// All and only needs photo
if (place.pic.trim { it <= ' ' }.isEmpty()) {
shouldUpdateMarker = true
}
- } else {
+ } else if (!displayExists && !displayNeedsPhoto) {
// all
shouldUpdateMarker = true
}
@@ -2486,11 +2456,9 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
Timber.d("Gallery button tapped. Place: %s", selectedPlace.toString())
storeSharedPrefs(selectedPlace!!)
activity?.let {
- // Pass singleSelection = true for Nearby flow
controller!!.initiateCustomGalleryPickWithPermission(
it,
- customSelectorLauncherForResult,
- singleSelection = true
+ customSelectorLauncherForResult
)
}
}
@@ -2673,14 +2641,43 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
*/
override fun clearAllMarkers() {
binding!!.map.overlayManager.clear()
-
- var geoPoint = mapCenter
- val lastLatLng = locationManager.getLastLocation()
- if (lastLatLng != null) {
- geoPoint = GeoPoint(lastLatLng.latitude, lastLatLng.longitude)
+ binding!!.map.invalidate()
+ val geoPoint = mapCenter
+ if (geoPoint != null) {
+ val diskOverlay =
+ ScaleDiskOverlay(
+ this.context,
+ geoPoint, 2000, UnitOfMeasure.foot
+ )
+ val circlePaint = Paint()
+ circlePaint.color = Color.rgb(128, 128, 128)
+ circlePaint.style = Paint.Style.STROKE
+ circlePaint.strokeWidth = 2f
+ diskOverlay.setCirclePaint2(circlePaint)
+ val diskPaint = Paint()
+ diskPaint.color = Color.argb(40, 128, 128, 128)
+ diskPaint.style = Paint.Style.FILL_AND_STROKE
+ diskOverlay.setCirclePaint1(diskPaint)
+ diskOverlay.setDisplaySizeMin(900)
+ diskOverlay.setDisplaySizeMax(1700)
+ binding!!.map.overlays.add(diskOverlay)
+ val startMarker = Marker(
+ binding!!.map
+ )
+ startMarker.position = geoPoint
+ startMarker.setAnchor(
+ Marker.ANCHOR_CENTER,
+ Marker.ANCHOR_BOTTOM
+ )
+ startMarker.icon =
+ getDrawable(
+ this.requireContext(),
+ fr.free.nrw.commons.R.drawable.current_location_marker
+ )
+ startMarker.title = "Your Location"
+ startMarker.textLabelFontSize = 24
+ binding!!.map.overlays.add(startMarker)
}
- updateUserLocationOverlays(geoPoint, false)
-
val scaleBarOverlay = ScaleBarOverlay(binding!!.map)
scaleBarOverlay.setScaleBarOffset(15, 25)
val barPaint = Paint()
@@ -2690,7 +2687,6 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
binding!!.map.overlays.add(scaleBarOverlay)
binding!!.map.overlays.add(mapEventsOverlay)
binding!!.map.setMultiTouchControls(true)
- binding!!.map.invalidate()
}
/**
@@ -2701,147 +2697,43 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
private fun recenterMarkerToPosition(geoPoint: GeoPoint?) {
geoPoint?.let {
binding?.map?.controller?.setCenter(it)
+ val overlays = binding?.map?.overlays ?: return@let
- updateUserLocationOverlays(it, true);
- }
- }
-
- /**
- * Updates the user current location overlays (both the location and error overlays) by
- * replacing any existing location overlays with new overlays at the given GeoPoint. If there
- * are no existing location and error overlays, then new overlays are added.
- *
- * @param geoPoint The GeoPoint representing the user's current location.
- * @param invalidate If true, the map overlays will be invalidated after the user
- * location overlays are updated/added. If false, the overlays will not be invalidated.
- */
- private fun updateUserLocationOverlays(geoPoint: GeoPoint?, invalidate: Boolean) {
- geoPoint?.let{
- updateUserLocationOverlay(geoPoint)
- updateUserLocationErrorOverlay(geoPoint)
- }
-
- if (invalidate) {
- binding!!.map.invalidate()
- }
- }
-
- /**
- * Updates the user location error overlay by either replacing it with or adding a new one.
- *
- * If the user location error overlay is null, the new overlay is added. If the
- * overlay is not null, it is replaced by the new overlay.
- *
- * @param geoPoint The GeoPoint representing the user's location
- */
- private fun updateUserLocationErrorOverlay(geoPoint: GeoPoint) {
- val overlays = binding?.map?.overlays ?: return
-
- // Multiply accuracy by 2 to get 95% confidence interval
- val accuracy = getCurrentLocationAccuracy() * 2
- val overlay = createCurrentLocationErrorOverlay(this.context, geoPoint,
- (accuracy).toInt(), UnitOfMeasure.meter)
-
- val index = overlays.indexOf(userLocationErrorOverlay)
-
- if (userLocationErrorOverlay == null || index == -1) {
- overlays.add(overlay)
- } else {
- overlays[index] = overlay
+ // Remove markers and disks using index-based removal
+ var i = 0
+ while (i < overlays.size) {
+ when (overlays[i]) {
+ is Marker, is ScaleDiskOverlay -> overlays.removeAt(i)
+ else -> i++
+ }
}
- userLocationErrorOverlay = overlay
- }
-
- /**
- * Updates the user location overlay by either replacing it with or adding a new one.
- *
- * If the user location overlay is null, the new overlay is added. If the
- * overlay is not null, it is replaced by the new overlay.
- *
- * @param geoPoint The GeoPoint representing the user's location
- */
- private fun updateUserLocationOverlay(geoPoint: GeoPoint) {
- val overlays = binding?.map?.overlays ?: return
-
- val overlay = createCurrentLocationOverlay(geoPoint)
-
- val index = overlays.indexOf(userLocationOverlay)
-
- if (userLocationOverlay == null || index == -1) {
- overlays.add(overlay)
- } else {
- overlays[index] = overlay
+ // Add disk overlay
+ ScaleDiskOverlay(context, it, 2000, UnitOfMeasure.foot).apply {
+ setCirclePaint2(Paint().apply {
+ color = Color.rgb(128, 128, 128)
+ style = Paint.Style.STROKE
+ strokeWidth = 2f
+ })
+ setCirclePaint1(Paint().apply {
+ color = Color.argb(40, 128, 128, 128)
+ style = Paint.Style.FILL_AND_STROKE
+ })
+ setDisplaySizeMin(900)
+ setDisplaySizeMax(1700)
+ overlays.add(this)
}
- userLocationOverlay = overlay
- }
-
- /**
- * @return The accuracy of the current location with a confidence at the 68th percentile.
- * Units are in meters. Returning 0 may indicate failure.
- */
- private fun getCurrentLocationAccuracy(): Float {
- var accuracy = 0f
- val lastLocation = locationManager.getLastLocation()
- if (lastLocation != null) {
- accuracy = lastLocation.accuracy
+ // Add marker
+ Marker(binding?.map).apply {
+ position = it
+ setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM)
+ icon = getDrawable(context, R.drawable.current_location_marker)
+ title = "Your Location"
+ textLabelFontSize = 24
+ overlays.add(this)
+ }
}
-
- return accuracy
- }
-
- /**
- * Creates the current location overlay
- *
- * @param geoPoint The GeoPoint where the current location overlay will be placed.
- *
- * @return The current location overlay as a Marker
- */
- private fun createCurrentLocationOverlay(geoPoint: GeoPoint): Marker {
- val currentLocationOverlay = Marker(
- binding!!.map
- )
- currentLocationOverlay.position = geoPoint
- currentLocationOverlay.icon =
- getDrawable(
- this.requireContext(),
- fr.free.nrw.commons.R.drawable.current_location_marker
- )
- currentLocationOverlay.title = "Your Location"
- currentLocationOverlay.textLabelFontSize = 24
- currentLocationOverlay.setAnchor(0.5f, 0.5f)
-
- return currentLocationOverlay
- }
-
- /**
- * Creates the location error overlay to show the user how accurate the current location
- * overlay is. The edge of the disk is the 95% confidence interval.
- *
- * @param context The Android context
- * @param point The user's location as a GeoPoint
- * @param value The radius of the disk
- * @param unitOfMeasure The unit of measurement of the value/disk radius.
- *
- * @return The location error overlay as a ScaleDiskOverlay.
- */
- private fun createCurrentLocationErrorOverlay(context: Context?, point: GeoPoint, value: Int,
- unitOfMeasure: UnitOfMeasure): ScaleDiskOverlay {
- val scaleDisk = ScaleDiskOverlay(context, point, value, unitOfMeasure)
-
- scaleDisk.setCirclePaint2(Paint().apply {
- color = Color.rgb(128, 128, 128)
- style = Paint.Style.STROKE
- strokeWidth = 2f
- })
-
- scaleDisk.setCirclePaint1(Paint().apply {
- color = Color.argb(40, 128, 128, 128)
- style = Paint.Style.FILL_AND_STROKE
- })
-
- return scaleDisk
}
private fun moveCameraToPosition(geoPoint: GeoPoint) {
@@ -2877,14 +2769,14 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
R.drawable.ic_directions_black_24dp -> {
selectedPlace?.let {
- handleGeoCoordinates(requireContext(), it.getLocation())
+ Utils.handleGeoCoordinates(this.context, it.getLocation())
binding?.map?.zoomLevelDouble ?: 0.0
}
}
R.drawable.ic_wikidata_logo_24dp -> {
selectedPlace?.siteLinks?.wikidataLink?.let {
- handleWebUrl(requireContext(), it)
+ Utils.handleWebUrl(this.context, it)
}
}
@@ -2902,13 +2794,13 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
R.drawable.ic_wikipedia_logo_24dp -> {
selectedPlace?.siteLinks?.wikipediaLink?.let {
- handleWebUrl(requireContext(), it)
+ Utils.handleWebUrl(this.context, it)
}
}
R.drawable.ic_commons_icon_vector -> {
selectedPlace?.siteLinks?.commonsLink?.let {
- handleWebUrl(requireContext(), it)
+ Utils.handleWebUrl(this.context, it)
}
}
@@ -3010,4 +2902,4 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
return input.contains("(") || input.contains(")")
}
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/model/NearbyResultItem.kt b/app/src/main/java/fr/free/nrw/commons/nearby/model/NearbyResultItem.kt
index 1d5d7bd80..f28cc833e 100644
--- a/app/src/main/java/fr/free/nrw/commons/nearby/model/NearbyResultItem.kt
+++ b/app/src/main/java/fr/free/nrw/commons/nearby/model/NearbyResultItem.kt
@@ -7,8 +7,7 @@ class NearbyResultItem(
private val wikipediaArticle: ResultTuple?,
private val commonsArticle: ResultTuple?,
private val location: ResultTuple?,
- @field:SerializedName("label") private val label: ResultTuple?,
- @field:SerializedName("itemLabel") private val itemLabel: ResultTuple?,
+ private val label: ResultTuple?,
@field:SerializedName("streetAddress") private val address: ResultTuple?,
private val icon: ResultTuple?,
@field:SerializedName("class") private val className: ResultTuple?,
@@ -19,8 +18,6 @@ class NearbyResultItem(
@field:SerializedName("description") private val description: ResultTuple?,
@field:SerializedName("endTime") private val endTime: ResultTuple?,
@field:SerializedName("monument") private val monument: ResultTuple?,
- @field:SerializedName("dateOfOfficialClosure") private val dateOfOfficialClosure: ResultTuple?,
- @field:SerializedName("pointInTime") private val pointInTime: ResultTuple?,
) {
fun getItem(): ResultTuple = item ?: ResultTuple()
@@ -30,15 +27,7 @@ class NearbyResultItem(
fun getLocation(): ResultTuple = location ?: ResultTuple()
- /**
- * Returns label for display (pins, popup), using fallback to itemLabel if needed.
- */
- fun getLabel(): ResultTuple = label ?: itemLabel ?: ResultTuple()
-
- /**
- * Returns only the original label field, for Wikidata edits.
- */
- fun getOriginalLabel(): ResultTuple = label ?: ResultTuple()
+ fun getLabel(): ResultTuple = label ?: ResultTuple()
fun getIcon(): ResultTuple = icon ?: ResultTuple()
@@ -52,8 +41,6 @@ class NearbyResultItem(
fun getDestroyed(): ResultTuple = destroyed ?: ResultTuple()
- fun getDateOfOfficialClosure(): ResultTuple = dateOfOfficialClosure ?: ResultTuple()
-
fun getDescription(): ResultTuple = description ?: ResultTuple()
fun getEndTime(): ResultTuple = endTime ?: ResultTuple()
@@ -61,7 +48,4 @@ class NearbyResultItem(
fun getAddress(): String = address?.value ?: ""
fun getMonument(): ResultTuple? = monument
-
- fun getPointInTime(): ResultTuple = pointInTime ?: ResultTuple()
-
}
diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/presenter/NearbyParentFragmentPresenter.kt b/app/src/main/java/fr/free/nrw/commons/nearby/presenter/NearbyParentFragmentPresenter.kt
index 6ec1064be..b4639b14a 100644
--- a/app/src/main/java/fr/free/nrw/commons/nearby/presenter/NearbyParentFragmentPresenter.kt
+++ b/app/src/main/java/fr/free/nrw/commons/nearby/presenter/NearbyParentFragmentPresenter.kt
@@ -351,7 +351,6 @@ class NearbyParentFragmentPresenter
pic = repoPlace.pic ?: ""
exists = repoPlace.exists ?: true
longDescription = repoPlace.longDescription ?: ""
- language = repoPlace.language
}
} else {
indicesToUpdate.add(i)
diff --git a/app/src/main/java/fr/free/nrw/commons/notification/NotificationActivity.kt b/app/src/main/java/fr/free/nrw/commons/notification/NotificationActivity.kt
index 4a43bf470..1547f89ad 100644
--- a/app/src/main/java/fr/free/nrw/commons/notification/NotificationActivity.kt
+++ b/app/src/main/java/fr/free/nrw/commons/notification/NotificationActivity.kt
@@ -8,23 +8,20 @@ import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.view.View
-import androidx.core.view.ViewGroupCompat
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.snackbar.Snackbar
import fr.free.nrw.commons.CommonsApplication
import fr.free.nrw.commons.R
+import fr.free.nrw.commons.Utils
import fr.free.nrw.commons.auth.SessionManager
import fr.free.nrw.commons.auth.csrf.InvalidLoginTokenException
import fr.free.nrw.commons.databinding.ActivityNotificationBinding
import fr.free.nrw.commons.notification.models.Notification
import fr.free.nrw.commons.notification.models.NotificationType
import fr.free.nrw.commons.theme.BaseActivity
-import fr.free.nrw.commons.utils.applyEdgeToEdgeTopInsets
import fr.free.nrw.commons.utils.NetworkUtils
import fr.free.nrw.commons.utils.ViewUtil
-import fr.free.nrw.commons.utils.applyEdgeToEdgeBottomPaddingInsets
-import fr.free.nrw.commons.utils.handleWebUrl
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
@@ -59,9 +56,6 @@ class NotificationActivity : BaseActivity() {
super.onCreate(savedInstanceState)
isRead = intent.getStringExtra("title") == "read"
binding = ActivityNotificationBinding.inflate(layoutInflater)
- ViewGroupCompat.installCompatInsetsDispatch(binding.root)
- applyEdgeToEdgeTopInsets(binding.toolbar.toolbar)
- binding.listView.applyEdgeToEdgeBottomPaddingInsets()
setContentView(binding.root)
mNotificationWorkerFragment = supportFragmentManager.findFragmentByTag(
tagNotificationWorkerFragment
@@ -203,7 +197,7 @@ class NotificationActivity : BaseActivity() {
private fun handleUrl(url: String?) {
if (url.isNullOrEmpty()) return
- handleWebUrl(this, Uri.parse(url))
+ Utils.handleWebUrl(this, Uri.parse(url))
}
private fun setItems(notificationList: List?) {
diff --git a/app/src/main/java/fr/free/nrw/commons/profile/ProfileActivity.kt b/app/src/main/java/fr/free/nrw/commons/profile/ProfileActivity.kt
index 8567d37ae..164842c9a 100644
--- a/app/src/main/java/fr/free/nrw/commons/profile/ProfileActivity.kt
+++ b/app/src/main/java/fr/free/nrw/commons/profile/ProfileActivity.kt
@@ -3,17 +3,16 @@ package fr.free.nrw.commons.profile
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
+import android.net.Uri
import android.os.Bundle
import android.util.Log
-import android.view.Menu
-import android.view.MenuItem
-import android.view.View
+import android.view.*
import android.widget.ImageView
import android.widget.TextView
import androidx.core.content.FileProvider
-import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment
import fr.free.nrw.commons.R
+import fr.free.nrw.commons.Utils
import fr.free.nrw.commons.ViewPagerAdapter
import fr.free.nrw.commons.auth.SessionManager
import fr.free.nrw.commons.contributions.ContributionsFragment
@@ -21,13 +20,11 @@ import fr.free.nrw.commons.databinding.ActivityProfileBinding
import fr.free.nrw.commons.profile.achievements.AchievementsFragment
import fr.free.nrw.commons.profile.leaderboard.LeaderboardFragment
import fr.free.nrw.commons.theme.BaseActivity
-import fr.free.nrw.commons.utils.applyEdgeToEdgeAllInsets
import fr.free.nrw.commons.utils.DialogUtil
import java.io.File
import java.io.FileOutputStream
-import java.util.Locale
+import java.util.*
import javax.inject.Inject
-import timber.log.Timber
/**
* This activity will set two tabs, achievements and
@@ -48,7 +45,7 @@ class ProfileActivity : BaseActivity() {
private var contributionsFragment: ContributionsFragment? = null
fun setScroll(canScroll: Boolean) {
- binding.viewPager.canScroll = canScroll
+ binding.viewPager.setCanScroll(canScroll)
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
@@ -63,7 +60,6 @@ class ProfileActivity : BaseActivity() {
super.onCreate(savedInstanceState)
binding = ActivityProfileBinding.inflate(layoutInflater)
- applyEdgeToEdgeAllInsets(binding.root)
setContentView(binding.root)
setSupportActionBar(binding.toolbarBinding.toolbar)
@@ -75,7 +71,7 @@ class ProfileActivity : BaseActivity() {
title = userName
shouldShowContributions = intent.getBooleanExtra(KEY_SHOULD_SHOW_CONTRIBUTIONS, false)
- viewPagerAdapter = ViewPagerAdapter(this, supportFragmentManager)
+ viewPagerAdapter = ViewPagerAdapter(supportFragmentManager)
binding.viewPager.adapter = viewPagerAdapter
binding.tabLayout.setupWithViewPager(binding.viewPager)
setTabs()
@@ -87,23 +83,39 @@ class ProfileActivity : BaseActivity() {
}
private fun setTabs() {
+ val fragmentList = mutableListOf()
+ val titleList = mutableListOf