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/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..8a4d530c4 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
@@ -74,9 +74,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 +102,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 +116,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 +125,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 +314,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 +544,6 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C
}
)
binding.progressBarEdit.visibility = View.GONE
- binding.descriptionEdit.visibility = View.VISIBLE
}
override fun onConfigurationChanged(newConfig: Configuration) {
@@ -809,27 +811,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
}
}
@@ -922,7 +907,7 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C
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 +915,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 +1012,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 +1646,7 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C
getString(R.string.cancel),
{
val reason: String = input.text.toString()
- onDeleteClickedDialogText(reason)
+ onDeleteClickeddialogtext(reason)
},
{},
input
@@ -1715,48 +1700,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 +1736,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 +2107,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 +2136,11 @@ fun FileUsagesContainer(
MediaDetailViewModel.FileUsagesContainerState.Loading -> {
LinearProgressIndicator()
}
+
is MediaDetailViewModel.FileUsagesContainerState.Success -> {
+
val data = commonsContainerState.data
+
if (data.isNullOrEmpty()) {
ListItem(headlineContent = {
Text(
@@ -2174,7 +2160,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 +2168,11 @@ fun FileUsagesContainer(
textDecoration = TextDecoration.Underline
)
)
- }
- )
+ })
}
}
}
+
is MediaDetailViewModel.FileUsagesContainerState.Error -> {
ListItem(headlineContent = {
Text(
@@ -2196,10 +2182,12 @@ fun FileUsagesContainer(
)
})
}
+
MediaDetailViewModel.FileUsagesContainerState.Initial -> {}
}
}
+
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
@@ -2210,7 +2198,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 +2215,11 @@ fun FileUsagesContainer(
MediaDetailViewModel.FileUsagesContainerState.Loading -> {
LinearProgressIndicator()
}
+
is MediaDetailViewModel.FileUsagesContainerState.Success -> {
+
val data = globalContainerState.data
+
if (data.isNullOrEmpty()) {
ListItem(headlineContent = {
Text(
@@ -2244,20 +2238,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 +2257,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..643374e54 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
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..4fa7979d2 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, "")
}
}
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/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/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/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..5b52c0ce5 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,10 +58,12 @@ 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
import fr.free.nrw.commons.contributions.MainActivity.ActiveFragment
+import fr.free.nrw.commons.customselector.ui.selector.ImageLoader
import fr.free.nrw.commons.databinding.FragmentNearbyParentBinding
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment
import fr.free.nrw.commons.filepicker.FilePicker
@@ -74,7 +76,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 +105,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 +123,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 +139,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 +149,7 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
LocationUpdateListener,
LocationPermissionCallback,
ItemClickListener,
- MediaDetailProvider {
+ MediaDetailPagerFragment.MediaDetailProvider {
var binding: FragmentNearbyParentBinding? = null
val mapEventsOverlay: MapEventsOverlay = MapEventsOverlay(object : MapEventsReceiver {
@@ -269,9 +267,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 +463,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 +724,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 +832,7 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
loadAnimations()
setBottomSheetCallbacks()
addActionToTitle()
- if (!isMonumentsEnabled) {
+ if (!Utils.isMonumentsEnabled(Date())) {
NearbyFilterState.setWlmSelected(false)
}
}
@@ -881,12 +876,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 +919,6 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
return _isDarkTheme
}
})
- restoreStoredFilterSelection()
binding!!.nearbyFilterList.root
.layoutParams.width = getScreenWidth(
requireActivity(),
@@ -949,22 +937,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
@@ -1041,10 +1013,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
}
@@ -1603,7 +1576,7 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
searchLatLng,
false,
true,
- isMonumentsEnabled,
+ Utils.isMonumentsEnabled(Date()),
customQuery
)
}
@@ -1656,7 +1629,7 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
searchLatLng,
false,
true,
- isMonumentsEnabled,
+ Utils.isMonumentsEnabled(Date()),
customQuery
)
}
@@ -1890,8 +1863,6 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
lastKnownLocation = latLng
NearbyController.currentLocation = lastKnownLocation
presenter!!.updateMapAndList(locationChangeType)
-
- updateUserLocationOverlays(GeoPoint(latLng.latitude, latLng.longitude), true)
}
override fun onLocationChangedSignificantly(latLng: LatLng) {
@@ -2673,14 +2644,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 +2690,6 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
binding!!.map.overlays.add(scaleBarOverlay)
binding!!.map.overlays.add(mapEventsOverlay)
binding!!.map.setMultiTouchControls(true)
- binding!!.map.invalidate()
}
/**
@@ -2701,147 +2700,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 +2772,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 +2797,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 +2905,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..c39d8901d 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?,
@@ -30,15 +29,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()
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()
+
+ // Add Achievements tab
achievementsFragment = AchievementsFragment().apply {
- arguments = bundleOf(KEY_USERNAME to userName)
+ arguments = Bundle().apply {
+ putString(KEY_USERNAME, userName)
+ }
}
+ fragmentList.add(achievementsFragment)
+ titleList.add(resources.getString(R.string.achievements_tab_title).uppercase())
+ // Add Leaderboard tab
leaderboardFragment = LeaderboardFragment().apply {
- arguments = bundleOf(KEY_USERNAME to userName)
+ arguments = Bundle().apply {
+ putString(KEY_USERNAME, userName)
+ }
}
+ fragmentList.add(leaderboardFragment)
+ titleList.add(resources.getString(R.string.leaderboard_tab_title).uppercase(Locale.ROOT))
+ // Add Contributions tab
contributionsFragment = ContributionsFragment().apply {
- arguments = bundleOf(KEY_USERNAME to userName)
+ arguments = Bundle().apply {
+ putString(KEY_USERNAME, userName)
+ }
+ }
+ contributionsFragment?.let {
+ fragmentList.add(it)
+ titleList.add(getString(R.string.contributions_fragment).uppercase(Locale.ROOT))
}
- viewPagerAdapter.setTabs(
- R.string.achievements_tab_title to achievementsFragment,
- R.string.leaderboard_tab_title to leaderboardFragment,
- R.string.contributions_fragment to contributionsFragment!!
- )
+ viewPagerAdapter.setTabData(fragmentList, titleList)
viewPagerAdapter.notifyDataSetChanged()
}
@@ -121,9 +133,9 @@ class ProfileActivity : BaseActivity() {
return when (item.itemId) {
R.id.share_app_icon -> {
val rootView = window.decorView.findViewById(android.R.id.content)
- val screenShot = getScreenShot(rootView)
+ val screenShot = Utils.getScreenShot(rootView)
if (screenShot == null) {
- Timber.e("ScreenShot is null")
+ Log.e("ERROR", "ScreenShot is null")
return false
}
showAlert(screenShot)
@@ -200,24 +212,6 @@ class ProfileActivity : BaseActivity() {
binding.tabLayout.visibility = if (isVisible) View.VISIBLE else View.GONE
}
- /**
- * To take screenshot of the screen and return it in Bitmap format
- *
- * @param view
- * @return
- */
- fun getScreenShot(view: View): Bitmap? {
- val screenView = view.rootView
- screenView.isDrawingCacheEnabled = true
- val drawingCache = screenView.drawingCache
- if (drawingCache != null) {
- val bitmap = Bitmap.createBitmap(drawingCache)
- screenView.isDrawingCacheEnabled = false
- return bitmap
- }
- return null
- }
-
companion object {
const val KEY_USERNAME = "username"
const val KEY_SHOULD_SHOW_CONTRIBUTIONS = "shouldShowContributions"
diff --git a/app/src/main/java/fr/free/nrw/commons/profile/achievements/AchievementsFragment.kt b/app/src/main/java/fr/free/nrw/commons/profile/achievements/AchievementsFragment.kt
index 8f23674ca..f967b8619 100644
--- a/app/src/main/java/fr/free/nrw/commons/profile/achievements/AchievementsFragment.kt
+++ b/app/src/main/java/fr/free/nrw/commons/profile/achievements/AchievementsFragment.kt
@@ -15,6 +15,7 @@ import com.google.android.material.badge.BadgeDrawable
import com.google.android.material.badge.BadgeUtils
import com.google.android.material.badge.ExperimentalBadgeUtils
import fr.free.nrw.commons.R
+import fr.free.nrw.commons.Utils
import fr.free.nrw.commons.auth.SessionManager
import fr.free.nrw.commons.databinding.FragmentAchievementsBinding
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment
@@ -26,7 +27,6 @@ import fr.free.nrw.commons.utils.ConfigUtils.isBetaFlavour
import fr.free.nrw.commons.utils.DialogUtil.showAlertDialog
import fr.free.nrw.commons.utils.ViewUtil.showDismissibleSnackBar
import fr.free.nrw.commons.utils.ViewUtil.showLongToast
-import fr.free.nrw.commons.utils.handleWebUrl
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import org.apache.commons.lang3.StringUtils
@@ -524,7 +524,7 @@ class AchievementsFragment : CommonsDaggerSupportFragment(){
getString(R.string.ok),
getString(R.string.read_help_link),
{},
- { handleWebUrl(requireContext(), Uri.parse(helpLinkUrl)) },
+ { Utils.handleWebUrl(requireContext(), Uri.parse(helpLinkUrl)) },
null
)
}
diff --git a/app/src/main/java/fr/free/nrw/commons/profile/leaderboard/LeaderboardFragment.kt b/app/src/main/java/fr/free/nrw/commons/profile/leaderboard/LeaderboardFragment.kt
index 5dcdf283b..e77c24c8d 100644
--- a/app/src/main/java/fr/free/nrw/commons/profile/leaderboard/LeaderboardFragment.kt
+++ b/app/src/main/java/fr/free/nrw/commons/profile/leaderboard/LeaderboardFragment.kt
@@ -311,7 +311,7 @@ class LeaderboardFragment : CommonsDaggerSupportFragment() {
}
private class SelectionListener(private val handler: () -> Unit): AdapterView.OnItemSelectedListener {
- override fun onItemSelected(adapterView: AdapterView<*>?, view: View?, i: Int, l: Long) =
+ override fun onItemSelected(adapterView: AdapterView<*>?, view: View, i: Int, l: Long) =
handler()
override fun onNothingSelected(p0: AdapterView<*>?) = Unit
diff --git a/app/src/main/java/fr/free/nrw/commons/quiz/QuizActivity.kt b/app/src/main/java/fr/free/nrw/commons/quiz/QuizActivity.kt
index 11fd1e6a6..e65b819e5 100644
--- a/app/src/main/java/fr/free/nrw/commons/quiz/QuizActivity.kt
+++ b/app/src/main/java/fr/free/nrw/commons/quiz/QuizActivity.kt
@@ -3,11 +3,9 @@ package fr.free.nrw.commons.quiz
import android.annotation.SuppressLint
import android.content.Intent
import android.os.Bundle
-import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
-import androidx.core.view.WindowCompat
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat
import com.facebook.drawee.drawable.ProgressBarDrawable
@@ -17,7 +15,6 @@ import fr.free.nrw.commons.databinding.ActivityQuizBinding
import java.util.ArrayList
import fr.free.nrw.commons.R
-import fr.free.nrw.commons.utils.applyEdgeToEdgeAllInsets
class QuizActivity : AppCompatActivity() {
@@ -40,11 +37,7 @@ class QuizActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- enableEdgeToEdge()
binding = ActivityQuizBinding.inflate(layoutInflater)
- applyEdgeToEdgeAllInsets(binding.root)
- WindowCompat.getInsetsController(window, window.decorView)
- .isAppearanceLightStatusBars = true
setContentView(binding.root)
quizController.initialize(this)
diff --git a/app/src/main/java/fr/free/nrw/commons/quiz/QuizChecker.kt b/app/src/main/java/fr/free/nrw/commons/quiz/QuizChecker.kt
index 5362f1542..0183056a6 100644
--- a/app/src/main/java/fr/free/nrw/commons/quiz/QuizChecker.kt
+++ b/app/src/main/java/fr/free/nrw/commons/quiz/QuizChecker.kt
@@ -151,7 +151,7 @@ class QuizChecker @Inject constructor(
activity.getString(R.string.quiz),
activity.getString(R.string.quiz_alert_message, revertPercentageForMessage),
activity.getString(R.string.about_translate_proceed),
- activity.getString(R.string.cancel),
+ activity.getString(android.R.string.cancel),
{ startQuizActivity(activity) },
null
)
diff --git a/app/src/main/java/fr/free/nrw/commons/quiz/QuizResultActivity.kt b/app/src/main/java/fr/free/nrw/commons/quiz/QuizResultActivity.kt
index 15884146d..81372b4a6 100644
--- a/app/src/main/java/fr/free/nrw/commons/quiz/QuizResultActivity.kt
+++ b/app/src/main/java/fr/free/nrw/commons/quiz/QuizResultActivity.kt
@@ -12,11 +12,9 @@ import android.view.MenuItem
import android.view.View
import android.widget.ImageView
import android.widget.TextView
-import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
-import androidx.core.view.WindowCompat
import fr.free.nrw.commons.databinding.ActivityQuizResultBinding
import java.io.File
@@ -24,7 +22,6 @@ import java.io.FileOutputStream
import fr.free.nrw.commons.R
import fr.free.nrw.commons.contributions.MainActivity
-import fr.free.nrw.commons.utils.applyEdgeToEdgeAllInsets
/**
@@ -38,11 +35,7 @@ class QuizResultActivity : AppCompatActivity() {
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- enableEdgeToEdge()
binding = ActivityQuizResultBinding.inflate(layoutInflater)
- applyEdgeToEdgeAllInsets(binding!!.root)
- WindowCompat.getInsetsController(window, window.decorView)
- .isAppearanceLightStatusBars = true
setContentView(binding?.root)
setSupportActionBar(binding?.toolbar?.toolbar)
@@ -193,7 +186,7 @@ class QuizResultActivity : AppCompatActivity() {
alertadd.setPositiveButton(R.string.about_translate_proceed) { dialog, _ ->
shareScreen(screenshot)
}
- alertadd.setNegativeButton(R.string.cancel) { dialog, _ ->
+ alertadd.setNegativeButton(android.R.string.cancel) { dialog, _ ->
dialog.cancel()
}
alertadd.show()
diff --git a/app/src/main/java/fr/free/nrw/commons/recentlanguages/RecentLanguagesContentProvider.kt b/app/src/main/java/fr/free/nrw/commons/recentlanguages/RecentLanguagesContentProvider.kt
index 80128ba73..facc4384f 100644
--- a/app/src/main/java/fr/free/nrw/commons/recentlanguages/RecentLanguagesContentProvider.kt
+++ b/app/src/main/java/fr/free/nrw/commons/recentlanguages/RecentLanguagesContentProvider.kt
@@ -3,13 +3,17 @@ package fr.free.nrw.commons.recentlanguages
import android.content.ContentValues
import android.database.Cursor
+import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteQueryBuilder
import android.net.Uri
+import android.text.TextUtils
import fr.free.nrw.commons.BuildConfig
+import fr.free.nrw.commons.data.DBOpenHelper
import fr.free.nrw.commons.di.CommonsDaggerContentProvider
import fr.free.nrw.commons.recentlanguages.RecentLanguagesDao.Table.COLUMN_NAME
import fr.free.nrw.commons.recentlanguages.RecentLanguagesDao.Table.TABLE_NAME
-import androidx.core.net.toUri
+import javax.inject.Inject
+import timber.log.Timber
/**
@@ -19,17 +23,27 @@ class RecentLanguagesContentProvider : CommonsDaggerContentProvider() {
companion object {
private const val BASE_PATH = "recent_languages"
- val BASE_URI: Uri = "content://${BuildConfig.RECENT_LANGUAGE_AUTHORITY}/$BASE_PATH".toUri()
+ val BASE_URI: Uri =
+ Uri.parse(
+ "content://${BuildConfig.RECENT_LANGUAGE_AUTHORITY}/$BASE_PATH"
+ )
/**
* Append language code to the base URI
* @param languageCode Code of a language
*/
@JvmStatic
- fun uriForCode(languageCode: String): Uri = "$BASE_URI/$languageCode".toUri()
+ fun uriForCode(languageCode: String): Uri {
+ return Uri.parse("$BASE_URI/$languageCode")
+ }
}
- override fun getType(uri: Uri): String? = null
+ @Inject
+ lateinit var dbOpenHelper: DBOpenHelper
+
+ override fun getType(uri: Uri): String? {
+ return null
+ }
/**
* Queries the SQLite database for the recently used languages
@@ -46,12 +60,11 @@ class RecentLanguagesContentProvider : CommonsDaggerContentProvider() {
selectionArgs: Array?,
sortOrder: String?
): Cursor? {
- val queryBuilder = SQLiteQueryBuilder().apply {
- tables = TABLE_NAME
- }
-
+ val queryBuilder = SQLiteQueryBuilder()
+ queryBuilder.tables = TABLE_NAME
+ val db = dbOpenHelper.readableDatabase
val cursor = queryBuilder.query(
- requireDb(),
+ db,
projection,
selection,
selectionArgs,
@@ -76,11 +89,12 @@ class RecentLanguagesContentProvider : CommonsDaggerContentProvider() {
selection: String?,
selectionArgs: Array?
): Int {
+ val sqlDB = dbOpenHelper.writableDatabase
val rowsUpdated: Int
if (selection.isNullOrEmpty()) {
val id = uri.lastPathSegment?.toInt()
?: throw IllegalArgumentException("Invalid URI: $uri")
- rowsUpdated = requireDb().update(
+ rowsUpdated = sqlDB.update(
TABLE_NAME,
contentValues,
"$COLUMN_NAME = ?",
@@ -100,13 +114,14 @@ class RecentLanguagesContentProvider : CommonsDaggerContentProvider() {
* @param contentValues : new values to be entered to the database
*/
override fun insert(uri: Uri, contentValues: ContentValues?): Uri? {
- val id = requireDb().insert(
+ val sqlDB = dbOpenHelper.writableDatabase
+ val id = sqlDB.insert(
TABLE_NAME,
null,
contentValues
)
context?.contentResolver?.notifyChange(uri, null)
- return "$BASE_URI/$id".toUri()
+ return Uri.parse("$BASE_URI/$id")
}
/**
@@ -114,7 +129,9 @@ class RecentLanguagesContentProvider : CommonsDaggerContentProvider() {
* @param uri : contains the URI for recently used languages
*/
override fun delete(uri: Uri, s: String?, strings: Array?): Int {
- val rows = requireDb().delete(
+ val db = dbOpenHelper.readableDatabase
+ Timber.d("Deleting recently used language %s", uri.lastPathSegment)
+ val rows = db.delete(
TABLE_NAME,
"language_code = ?",
arrayOf(uri.lastPathSegment)
diff --git a/app/src/main/java/fr/free/nrw/commons/review/ReviewActivity.kt b/app/src/main/java/fr/free/nrw/commons/review/ReviewActivity.kt
index dccb77af1..20f289f8f 100644
--- a/app/src/main/java/fr/free/nrw/commons/review/ReviewActivity.kt
+++ b/app/src/main/java/fr/free/nrw/commons/review/ReviewActivity.kt
@@ -16,7 +16,6 @@ import fr.free.nrw.commons.databinding.ActivityReviewBinding
import fr.free.nrw.commons.delete.DeleteHelper
import fr.free.nrw.commons.media.MediaDetailFragment
import fr.free.nrw.commons.theme.BaseActivity
-import fr.free.nrw.commons.utils.applyEdgeToEdgeAllInsets
import fr.free.nrw.commons.utils.DialogUtil
import fr.free.nrw.commons.utils.ViewUtil
import io.reactivex.android.schedulers.AndroidSchedulers
@@ -74,7 +73,6 @@ class ReviewActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityReviewBinding.inflate(layoutInflater)
- applyEdgeToEdgeAllInsets(binding.root)
setContentView(binding.root)
setSupportActionBar(binding.toolbarBinding?.toolbar)
@@ -109,8 +107,10 @@ class ReviewActivity : BaseActivity() {
setUpMediaDetailFragment()
}
- binding.skipImageInfo?.setOnTouchListener { _, event ->
- if (event.action == MotionEvent.ACTION_UP) {
+ binding.skipImage.setOnTouchListener { _, event ->
+ if (event.action == MotionEvent.ACTION_UP &&
+ event.rawX >= (binding.skipImage.right - binding.skipImage.compoundDrawables[2].bounds.width())
+ ) {
showSkipImageInfo()
true
} else {
@@ -238,7 +238,7 @@ class ReviewActivity : BaseActivity() {
this,
getString(R.string.skip_image).uppercase(Locale.ROOT),
getString(R.string.skip_image_explanation),
- getString(R.string.ok),
+ getString(android.R.string.ok),
null,
null,
null
@@ -250,7 +250,7 @@ class ReviewActivity : BaseActivity() {
this,
getString(R.string.title_activity_review),
getString(R.string.review_image_explanation),
- getString(R.string.ok),
+ getString(android.R.string.ok),
null,
null,
null
diff --git a/app/src/main/java/fr/free/nrw/commons/settings/SettingsActivity.kt b/app/src/main/java/fr/free/nrw/commons/settings/SettingsActivity.kt
index 233e688f4..91c88d7b0 100644
--- a/app/src/main/java/fr/free/nrw/commons/settings/SettingsActivity.kt
+++ b/app/src/main/java/fr/free/nrw/commons/settings/SettingsActivity.kt
@@ -4,7 +4,6 @@ import android.os.Bundle
import android.view.MenuItem
import fr.free.nrw.commons.databinding.ActivitySettingsBinding
import fr.free.nrw.commons.theme.BaseActivity
-import fr.free.nrw.commons.utils.applyEdgeToEdgeAllInsets
/**
@@ -22,7 +21,6 @@ class SettingsActivity : BaseActivity() {
super.onCreate(savedInstanceState)
binding = ActivitySettingsBinding.inflate(layoutInflater)
val view = binding.root
- applyEdgeToEdgeAllInsets(view)
setContentView(view)
setSupportActionBar(binding.toolbarBinding.toolbar)
diff --git a/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.kt b/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.kt
index c38ed1ecb..092f057e9 100644
--- a/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.kt
+++ b/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.kt
@@ -1,7 +1,6 @@
package fr.free.nrw.commons.settings
import android.Manifest.permission
-import android.annotation.SuppressLint
import android.app.Activity
import android.app.Dialog
import android.content.Context.MODE_PRIVATE
@@ -34,7 +33,9 @@ import com.karumi.dexter.MultiplePermissionsReport
import com.karumi.dexter.PermissionToken
import com.karumi.dexter.listener.PermissionRequest
import com.karumi.dexter.listener.multi.MultiplePermissionsListener
+import fr.free.nrw.commons.BuildConfig.MOBILE_META_URL
import fr.free.nrw.commons.R
+import fr.free.nrw.commons.Utils
import fr.free.nrw.commons.activity.SingleWebViewActivity
import fr.free.nrw.commons.campaigns.CampaignView
import fr.free.nrw.commons.contributions.ContributionController
@@ -52,7 +53,6 @@ import fr.free.nrw.commons.utils.DialogUtil
import fr.free.nrw.commons.utils.PermissionUtils
import fr.free.nrw.commons.utils.StringUtil
import fr.free.nrw.commons.utils.ViewUtil
-import fr.free.nrw.commons.utils.handleWebUrl
import java.util.Locale
import javax.inject.Inject
import javax.inject.Named
@@ -239,10 +239,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
val betaTesterPreference: Preference? = findPreference("becomeBetaTester")
betaTesterPreference?.setOnPreferenceClickListener {
- handleWebUrl(
- requireActivity(),
- Uri.parse(getString(R.string.beta_opt_in_link))
- )
+ Utils.handleWebUrl(requireActivity(), Uri.parse(getString(R.string.beta_opt_in_link)))
true
}
@@ -299,16 +296,11 @@ class SettingsFragment : PreferenceFragmentCompat() {
getString(R.string.ok),
getString(R.string.read_help_link),
{ },
- { handleWebUrl(requireContext(), Uri.parse(GET_CONTENT_PICKER_HELP_URL)) },
+ { Utils.handleWebUrl(requireContext(), Uri.parse(GET_CONTENT_PICKER_HELP_URL)) },
null
)
}
- // Remove the space for icons in the settings menu.
- // This uses an internal API that shouldn't be used in app code,
- // but it appears to be the most robust way to do this at the moment,
- // disable the warning.
- @SuppressLint("RestrictedApi")
override fun onCreateAdapter(preferenceScreen: PreferenceScreen): Adapter
{
return object : PreferenceGroupAdapter(preferenceScreen) {
diff --git a/app/src/main/java/fr/free/nrw/commons/theme/BaseActivity.kt b/app/src/main/java/fr/free/nrw/commons/theme/BaseActivity.kt
index d317a7d35..d2d936460 100644
--- a/app/src/main/java/fr/free/nrw/commons/theme/BaseActivity.kt
+++ b/app/src/main/java/fr/free/nrw/commons/theme/BaseActivity.kt
@@ -4,7 +4,6 @@ import android.content.res.Configuration
import android.os.Bundle
import android.util.DisplayMetrics
import android.view.WindowManager
-import androidx.activity.enableEdgeToEdge
import javax.inject.Inject
import javax.inject.Named
import fr.free.nrw.commons.R
@@ -37,7 +36,6 @@ abstract class BaseActivity : CommonsDaggerAppCompatActivity() {
1f
)
adjustFontScale(resources.configuration, fontScale)
- enableEdgeToEdge()
}
override fun onResume() {
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/PageContentsCreator.kt b/app/src/main/java/fr/free/nrw/commons/upload/PageContentsCreator.kt
index 2de17f849..0c4ded8b2 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/PageContentsCreator.kt
+++ b/app/src/main/java/fr/free/nrw/commons/upload/PageContentsCreator.kt
@@ -1,11 +1,11 @@
package fr.free.nrw.commons.upload
import android.content.Context
+import fr.free.nrw.commons.Utils
import fr.free.nrw.commons.contributions.Contribution
import fr.free.nrw.commons.filepicker.UploadableFile.DateTimeWithSource
import fr.free.nrw.commons.settings.Prefs.Licenses
import fr.free.nrw.commons.utils.ConfigUtils.getVersionNameWithSha
-import fr.free.nrw.commons.utils.getWikiLovesMonumentsYear
import org.apache.commons.lang3.StringUtils
import java.text.SimpleDateFormat
import java.util.Calendar
@@ -49,7 +49,7 @@ class PageContentsCreator @Inject constructor(private val context: Context) {
String.format(
Locale.ENGLISH,
"{{Wiki Loves Monuments %d|1= %s}}\n",
- getWikiLovesMonumentsYear(Calendar.getInstance()),
+ Utils.getWikiLovesMonumentsYear(Calendar.getInstance()),
contribution.countryCode
)
)
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.kt b/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.kt
index 66e0257f6..38e7dace8 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.kt
+++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.kt
@@ -13,7 +13,6 @@ import android.os.Build.VERSION_CODES
import android.os.Bundle
import android.provider.Settings
import android.view.View
-import android.view.inputmethod.InputMethodManager
import android.widget.CheckBox
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.AlertDialog
@@ -39,7 +38,6 @@ import fr.free.nrw.commons.mwapi.UserClient
import fr.free.nrw.commons.nearby.Place
import fr.free.nrw.commons.settings.Prefs
import fr.free.nrw.commons.theme.BaseActivity
-import fr.free.nrw.commons.utils.applyEdgeToEdgeAllInsets
import fr.free.nrw.commons.upload.ThumbnailsAdapter.OnThumbnailDeletedListener
import fr.free.nrw.commons.upload.categories.UploadCategoriesFragment
import fr.free.nrw.commons.upload.depicts.DepictsFragment
@@ -179,7 +177,6 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C
presenter?.setupBasicKvStoreFactory { BasicKvStore(this@UploadActivity, it) }
_binding = ActivityUploadBinding.inflate(layoutInflater)
- applyEdgeToEdgeAllInsets(_binding!!.root, false)
setContentView(binding.root)
// Overrides the back button to make sure the user is prepared to lose their progress
@@ -446,7 +443,7 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C
this,
getString(R.string.storage_permissions_denied),
getString(R.string.unable_to_share_upload_item),
- getString(R.string.ok)
+ getString(android.R.string.ok)
) { finish() }
} else {
showAlertDialog(
@@ -455,7 +452,7 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C
getString(
R.string.write_storage_permission_rationale_for_image_share
),
- getString(R.string.ok)
+ getString(android.R.string.ok)
) { checkStoragePermissions() }
}
}
@@ -508,17 +505,24 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C
fragments = mutableListOf()
}
+
for (uploadableFile in uploadableFiles) {
val uploadMediaDetailFragment = UploadMediaDetailFragment()
- // set fragment properties but defer initialization
- uploadMediaDetailFragment.uploadableFile = uploadableFile
- uploadMediaDetailFragment.place = place
- uploadMediaDetailFragment.inAppPictureLocation = if (!uploadIsOfAPlace) {
+ if (!uploadIsOfAPlace) {
handleLocation()
- currLocation
+ uploadMediaDetailFragment.setImageToBeUploaded(
+ uploadableFile,
+ place,
+ currLocation
+ )
+ locationManager!!.unregisterLocationManager()
} else {
- currLocation
+ uploadMediaDetailFragment.setImageToBeUploaded(
+ uploadableFile,
+ place,
+ currLocation
+ )
}
val uploadMediaDetailFragmentCallback: UploadMediaDetailFragmentCallback =
@@ -573,19 +577,13 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C
if (isFragmentsSaved) {
val fragment = fragments!![0] as UploadMediaDetailFragment?
fragment!!.fragmentCallback = uploadMediaDetailFragmentCallback
- fragment.initializeFragment()
} else {
uploadMediaDetailFragment.fragmentCallback = uploadMediaDetailFragmentCallback
fragments!!.add(uploadMediaDetailFragment)
}
}
- // unregister location manager after loop if needed
- if (!uploadIsOfAPlace) {
- locationManager!!.unregisterLocationManager()
- }
-
- // If fragments are not created, create them and add them to the fragments ArrayList
+ //If fragments are not created, create them and add them to the fragments ArrayList
if (!isFragmentsSaved) {
uploadCategoriesFragment = UploadCategoriesFragment()
if (place != null) {
@@ -805,19 +803,6 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C
override fun onNextButtonClicked(index: Int) {
if (index < fragments!!.size - 1) {
- // Hide the keyboard before navigating to Media License screen
- val isUploadCategoriesFragment = fragments!!.getOrNull(index)?.let {
- it is UploadCategoriesFragment
- } ?: false
- if (isUploadCategoriesFragment) {
- val inputMethodManager = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
- currentFocus?.let { focusedView ->
- inputMethodManager.hideSoftInputFromWindow(
- focusedView.windowToken,
- InputMethodManager.HIDE_NOT_ALWAYS
- )
- }
- }
binding.vpUpload.setCurrentItem(index + 1, false)
fragments!![index + 1].onBecameVisible()
(binding.rvThumbnails.layoutManager as LinearLayoutManager)
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadItem.kt b/app/src/main/java/fr/free/nrw/commons/upload/UploadItem.kt
index 6d2321def..f357cd112 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/UploadItem.kt
+++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadItem.kt
@@ -1,10 +1,10 @@
package fr.free.nrw.commons.upload
import android.net.Uri
+import fr.free.nrw.commons.Utils
import fr.free.nrw.commons.filepicker.MimeTypeMapWrapper.Companion.getExtensionFromMimeType
import fr.free.nrw.commons.nearby.Place
import fr.free.nrw.commons.utils.ImageUtils
-import fr.free.nrw.commons.utils.fixExtension
class UploadItem(
var mediaUri: Uri?,
@@ -32,7 +32,7 @@ class UploadItem(
* languages have been entered, the first language is used.
*/
val filename: String
- get() = fixExtension(
+ get() = Utils.fixExtension(
uploadMediaDetails[0].captionText,
getExtensionFromMimeType(mimeType)
)
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadProgressActivity.kt b/app/src/main/java/fr/free/nrw/commons/upload/UploadProgressActivity.kt
index 665f106e2..aeaefa302 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/UploadProgressActivity.kt
+++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadProgressActivity.kt
@@ -10,7 +10,6 @@ import fr.free.nrw.commons.ViewPagerAdapter
import fr.free.nrw.commons.contributions.ContributionDao
import fr.free.nrw.commons.databinding.ActivityUploadProgressBinding
import fr.free.nrw.commons.theme.BaseActivity
-import fr.free.nrw.commons.utils.applyEdgeToEdgeAllInsets
import javax.inject.Inject
/**
@@ -29,6 +28,8 @@ class UploadProgressActivity : BaseActivity() {
@Inject
lateinit var contributionDao: ContributionDao
+ val fragmentList: MutableList = ArrayList()
+ val titleList: MutableList = ArrayList()
var isPaused = true
var isPendingIconsVisible = true
var isErrorIconsVisisble = false
@@ -36,9 +37,8 @@ class UploadProgressActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityUploadProgressBinding.inflate(layoutInflater)
- applyEdgeToEdgeAllInsets(binding.root)
setContentView(binding.root)
- viewPagerAdapter = ViewPagerAdapter(this, supportFragmentManager)
+ viewPagerAdapter = ViewPagerAdapter(supportFragmentManager)
binding.uploadProgressViewPager.setAdapter(viewPagerAdapter)
binding.uploadProgressViewPager.setId(R.id.upload_progress_view_pager)
binding.uploadProgressTabLayout.setupWithViewPager(binding.uploadProgressViewPager)
@@ -58,7 +58,11 @@ class UploadProgressActivity : BaseActivity() {
override fun onPageSelected(position: Int) {
updateMenuItems(position)
- binding.uploadProgressViewPager.canScroll = (position != 2)
+ if (position == 2) {
+ binding.uploadProgressViewPager.setCanScroll(false)
+ } else {
+ binding.uploadProgressViewPager.setCanScroll(true)
+ }
}
override fun onPageScrollStateChanged(state: Int) {
@@ -77,10 +81,11 @@ class UploadProgressActivity : BaseActivity() {
pendingUploadsFragment = PendingUploadsFragment()
failedUploadsFragment = FailedUploadsFragment()
- viewPagerAdapter!!.setTabs(
- R.string.pending to pendingUploadsFragment!!,
- R.string.failed to failedUploadsFragment!!
- )
+ fragmentList.add(pendingUploadsFragment!!)
+ titleList.add(getString(R.string.pending))
+ fragmentList.add(failedUploadsFragment!!)
+ titleList.add(getString(R.string.failed))
+ viewPagerAdapter!!.setTabData(fragmentList, titleList)
viewPagerAdapter!!.notifyDataSetChanged()
}
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/categories/CategoriesContract.kt b/app/src/main/java/fr/free/nrw/commons/upload/categories/CategoriesContract.kt
index 29e5ba90b..183c7cd93 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/categories/CategoriesContract.kt
+++ b/app/src/main/java/fr/free/nrw/commons/upload/categories/CategoriesContract.kt
@@ -17,11 +17,6 @@ interface CategoriesContract {
fun showError(stringResourceId: Int)
- /**
- * Show a cancelable AlertDialog with a given message.
- */
- fun showErrorDialog(message: String)
-
fun setCategories(categories: List?)
fun goToNextScreen()
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/categories/CategoriesPresenter.kt b/app/src/main/java/fr/free/nrw/commons/upload/categories/CategoriesPresenter.kt
index a1a96f2ac..dbeeae6ff 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/categories/CategoriesPresenter.kt
+++ b/app/src/main/java/fr/free/nrw/commons/upload/categories/CategoriesPresenter.kt
@@ -12,7 +12,6 @@ import fr.free.nrw.commons.di.CommonsApplicationModule.Companion.IO_THREAD
import fr.free.nrw.commons.di.CommonsApplicationModule.Companion.MAIN_THREAD
import fr.free.nrw.commons.repository.UploadRepository
import fr.free.nrw.commons.upload.depicts.proxy
-import fr.free.nrw.commons.wikidata.mwapi.MwIOException
import io.reactivex.Observable
import io.reactivex.Scheduler
import io.reactivex.android.schedulers.AndroidSchedulers
@@ -76,12 +75,7 @@ class CategoriesPresenter
},
{ t: Throwable? ->
view.showProgress(false)
- view.showError(R.string.error_loading_categories)
- val mwException = t as? MwIOException
- view.showErrorDialog(
- if (mwException == null) ""
- else "\n${mwException.error.title} / ${mwException.error.details}"
- )
+ view.showError(R.string.no_categories_found)
Timber.e(t)
},
),
@@ -200,12 +194,7 @@ class CategoriesPresenter
},
{ t: Throwable? ->
view.showProgress(false)
- view.showError(R.string.error_loading_categories)
- val mwException = t as? MwIOException
- view.showErrorDialog(
- if (mwException == null) ""
- else "\n${mwException.error.title} / ${mwException.error.details}"
- )
+ view.showError(R.string.no_categories_found)
Timber.e(t)
},
),
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/categories/UploadCategoriesFragment.kt b/app/src/main/java/fr/free/nrw/commons/upload/categories/UploadCategoriesFragment.kt
index ef4521431..262013045 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/categories/UploadCategoriesFragment.kt
+++ b/app/src/main/java/fr/free/nrw/commons/upload/categories/UploadCategoriesFragment.kt
@@ -10,7 +10,6 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
-import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import com.jakewharton.rxbinding2.view.RxView
@@ -27,12 +26,12 @@ import fr.free.nrw.commons.media.MediaDetailFragment
import fr.free.nrw.commons.upload.UploadActivity
import fr.free.nrw.commons.upload.UploadBaseFragment
import fr.free.nrw.commons.utils.DialogUtil.showAlertDialog
-import fr.free.nrw.commons.utils.handleKeyboardInsets
import fr.free.nrw.commons.wikidata.WikidataConstants.SELECTED_NEARBY_PLACE_CATEGORY
import io.reactivex.Notification
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import timber.log.Timber
+import java.util.Objects
import java.util.concurrent.TimeUnit
import javax.inject.Inject
@@ -70,7 +69,6 @@ class UploadCategoriesFragment : UploadBaseFragment(), CategoriesContract.View {
savedInstanceState: Bundle?
): View? {
binding = UploadCategoriesFragmentBinding.inflate(inflater, container, false)
- binding!!.llContainerButtons.handleKeyboardInsets()
return binding!!.root
}
@@ -117,7 +115,7 @@ class UploadCategoriesFragment : UploadBaseFragment(), CategoriesContract.View {
requireActivity(),
getString(R.string.categories_activity_title),
getString(R.string.categories_tooltip),
- getString(R.string.ok),
+ getString(android.R.string.ok),
null
)
}
@@ -199,15 +197,6 @@ class UploadCategoriesFragment : UploadBaseFragment(), CategoriesContract.View {
binding?.tilContainerSearch?.error = getString(stringResourceId)
}
- override fun showErrorDialog(message: String) {
- AlertDialog
- .Builder(requireContext())
- .setMessage(getString(R.string.error_loading_categories) + "\n" + message)
- .setCancelable(false)
- .setNegativeButton(R.string.ok){_,_ -> }
- .show()
- }
-
override fun setCategories(categories: List?) {
if (adapter == null) {
Timber.e("Adapter is null in setCategories")
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/depicts/DepictsFragment.kt b/app/src/main/java/fr/free/nrw/commons/upload/depicts/DepictsFragment.kt
index 5dcc2bf86..39bcabb46 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/depicts/DepictsFragment.kt
+++ b/app/src/main/java/fr/free/nrw/commons/upload/depicts/DepictsFragment.kt
@@ -27,7 +27,6 @@ import fr.free.nrw.commons.upload.UploadActivity
import fr.free.nrw.commons.upload.UploadBaseFragment
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
import fr.free.nrw.commons.utils.DialogUtil.showAlertDialog
-import fr.free.nrw.commons.utils.handleKeyboardInsets
import fr.free.nrw.commons.wikidata.WikidataConstants.SELECTED_NEARBY_PLACE
import io.reactivex.Notification
import io.reactivex.android.schedulers.AndroidSchedulers
@@ -70,7 +69,6 @@ class DepictsFragment : UploadBaseFragment(), DepictsContract.View {
savedInstanceState: Bundle?
): View {
_binding = UploadDepictsFragmentBinding.inflate(inflater, container, false)
- _binding!!.navigationButtonsContainer.handleKeyboardInsets()
return binding.root
}
@@ -116,7 +114,7 @@ class DepictsFragment : UploadBaseFragment(), DepictsContract.View {
requireActivity(),
getString(R.string.depicts_step_title),
getString(R.string.depicts_tooltip),
- getString(R.string.ok),
+ getString(android.R.string.ok),
null
)
}
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/license/MediaLicenseFragment.kt b/app/src/main/java/fr/free/nrw/commons/upload/license/MediaLicenseFragment.kt
index a789ef362..0415d3270 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/license/MediaLicenseFragment.kt
+++ b/app/src/main/java/fr/free/nrw/commons/upload/license/MediaLicenseFragment.kt
@@ -16,13 +16,11 @@ import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.TextView
import fr.free.nrw.commons.R
+import fr.free.nrw.commons.Utils
import fr.free.nrw.commons.databinding.FragmentMediaLicenseBinding
import fr.free.nrw.commons.upload.UploadActivity
import fr.free.nrw.commons.upload.UploadBaseFragment
import fr.free.nrw.commons.utils.DialogUtil.showAlertDialog
-import fr.free.nrw.commons.utils.handleWebUrl
-import fr.free.nrw.commons.utils.toLicenseName
-import fr.free.nrw.commons.utils.toLicenseUrl
import timber.log.Timber
import javax.inject.Inject
@@ -70,7 +68,7 @@ class MediaLicenseFragment : UploadBaseFragment(), MediaLicenseContract.View {
requireActivity(),
getString(R.string.license_step_title),
getString(R.string.license_tooltip),
- getString(R.string.ok),
+ getString(android.R.string.ok),
null
)
}
@@ -128,20 +126,20 @@ class MediaLicenseFragment : UploadBaseFragment(), MediaLicenseContract.View {
}
override fun setSelectedLicense(license: String?) {
- var position = license?.let { licenses!!.indexOf(getString(it.toLicenseName())) } ?: -1
+ var position = licenses!!.indexOf(getString(Utils.licenseNameFor(license)))
// Check if position is valid
if (position < 0) {
Timber.d("Invalid position: %d. Using default licenses", position)
position = licenses!!.size - 1
+ } else {
+ Timber.d("Position: %d %s", position, getString(Utils.licenseNameFor(license)))
}
binding.spinnerLicenseList.setSelection(position)
}
override fun updateLicenseSummary(selectedLicense: String?, numberOfItems: Int) {
- if (selectedLicense == null) return
-
- val licenseHyperLink = "" +
- getString(selectedLicense.toLicenseName()) + " "
+ val licenseHyperLink = "" +
+ getString(Utils.licenseNameFor(selectedLicense)) + " "
setTextViewHTML(
binding.tvShareLicenseSummary, resources
@@ -186,7 +184,7 @@ class MediaLicenseFragment : UploadBaseFragment(), MediaLicenseContract.View {
}
private fun launchBrowser(hyperLink: String) =
- handleWebUrl(requireContext(), Uri.parse(hyperLink))
+ Utils.handleWebUrl(context, Uri.parse(hyperLink))
override fun onDestroyView() {
presenter.onDetachView()
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/license/MediaLicensePresenter.kt b/app/src/main/java/fr/free/nrw/commons/upload/license/MediaLicensePresenter.kt
index df75019b2..25d1a2324 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/license/MediaLicensePresenter.kt
+++ b/app/src/main/java/fr/free/nrw/commons/upload/license/MediaLicensePresenter.kt
@@ -1,9 +1,9 @@
package fr.free.nrw.commons.upload.license
+import fr.free.nrw.commons.Utils
import fr.free.nrw.commons.kvstore.JsonKvStore
import fr.free.nrw.commons.repository.UploadRepository
import fr.free.nrw.commons.settings.Prefs
-import fr.free.nrw.commons.utils.toLicenseName
import timber.log.Timber
import java.lang.reflect.Method
import java.lang.reflect.Proxy
@@ -34,14 +34,12 @@ class MediaLicensePresenter @Inject constructor(
val licenses = repository.getLicenses()
view.setLicenses(licenses)
- //CC_BY_SA_4 is the default one used by the commons web app
- var selectedLicense: String = defaultKVStore.getString(
+ var selectedLicense = defaultKVStore.getString(
Prefs.DEFAULT_LICENSE,
Prefs.Licenses.CC_BY_SA_4
- ) ?: Prefs.Licenses.CC_BY_SA_4
-
+ ) //CC_BY_SA_4 is the default one used by the commons web app
try { //I have to make sure that the stored default license was not one of the deprecated one's
- selectedLicense.toLicenseName()
+ Utils.licenseNameFor(selectedLicense)
} catch (exception: IllegalStateException) {
Timber.e(exception)
selectedLicense = Prefs.Licenses.CC_BY_SA_4
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.kt b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.kt
index b3b067948..4a4c13ba7 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.kt
+++ b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.kt
@@ -50,7 +50,6 @@ import fr.free.nrw.commons.utils.ImageUtils.IMAGE_OK
import fr.free.nrw.commons.utils.ImageUtils.getErrorMessageForResult
import fr.free.nrw.commons.utils.NetworkUtils.isInternetConnectionEstablished
import fr.free.nrw.commons.utils.ViewUtil.showLongToast
-import fr.free.nrw.commons.utils.handleKeyboardInsets
import timber.log.Timber
import java.io.File
import java.util.ArrayList
@@ -119,8 +118,8 @@ class UploadMediaDetailFragment : UploadBaseFragment(), UploadMediaDetailsContra
private var basicKvStore: BasicKvStore? = null
private val keyForShowingAlertDialog = "isNoNetworkAlertDialogShowing"
- internal var uploadableFile: UploadableFile? = null
- internal var place: Place? = null
+ private var uploadableFile: UploadableFile? = null
+ private var place: Place? = null
private lateinit var uploadMediaDetailAdapter: UploadMediaDetailAdapter
var indexOfFragment = 0
var isExpanded = true
@@ -142,24 +141,18 @@ class UploadMediaDetailFragment : UploadBaseFragment(), UploadMediaDetailsContra
}
}
+ fun setImageToBeUploaded(
+ uploadableFile: UploadableFile?, place: Place?, inAppPictureLocation: LatLng?
+ ) {
+ this.uploadableFile = uploadableFile
+ this.place = place
+ this.inAppPictureLocation = inAppPictureLocation
+ }
+
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View {
_binding = FragmentUploadMediaDetailFragmentBinding.inflate(inflater, container, false)
- _binding!!.mediaDetailCardView.handleKeyboardInsets()
- // intialise the adapter early to prevent uninitialized access
- uploadMediaDetailAdapter = UploadMediaDetailAdapter(
- this,
- defaultKvStore.getString(Prefs.DESCRIPTION_LANGUAGE, "")!!,
- recentLanguagesDao, voiceInputResultLauncher
- )
- uploadMediaDetailAdapter.callback =
- UploadMediaDetailAdapter.Callback { titleStringID: Int, messageStringId: Int ->
- showInfoAlert(titleStringID, messageStringId)
- }
- uploadMediaDetailAdapter.eventListener = this
- binding.rvDescriptions.layoutManager = LinearLayoutManager(context)
- binding.rvDescriptions.adapter = uploadMediaDetailAdapter
return binding.root
}
@@ -168,48 +161,20 @@ class UploadMediaDetailFragment : UploadBaseFragment(), UploadMediaDetailsContra
basicKvStore = BasicKvStore(requireActivity(), "CurrentUploadImageQualities")
- // restore adapter items from savedInstanceState if available
- if (savedInstanceState != null) {
- val savedItems = savedInstanceState.getParcelableArrayList(UPLOAD_MEDIA_DETAILS)
- Timber.d("Restoring state: savedItems size = %s", savedItems?.size ?: "null")
- if (savedItems != null && savedItems.isNotEmpty()) {
- uploadMediaDetailAdapter.items = savedItems
- // only call setUploadMediaDetails if indexOfFragment is valid
- if (fragmentCallback != null) {
- indexOfFragment = fragmentCallback!!.getIndexInViewFlipper(this)
- if (indexOfFragment >= 0) {
- presenter.setUploadMediaDetails(uploadMediaDetailAdapter.items, indexOfFragment)
- Timber.d("Restored and set upload media details for index %d", indexOfFragment)
- } else {
- Timber.w("Invalid indexOfFragment %d, skipping setUploadMediaDetails", indexOfFragment)
- }
- } else {
- Timber.w("fragmentCallback is null, skipping setUploadMediaDetails")
- }
- } else {
- // initialize with a default UploadMediaDetail if saved state is empty or null
- uploadMediaDetailAdapter.items = mutableListOf(UploadMediaDetail())
- Timber.d("Initialized default UploadMediaDetail due to empty or null savedItems")
- }
- } else {
- // intitialise with a default UploadMediaDetail for fresh fragment
- if (uploadMediaDetailAdapter.items.isEmpty()) {
- uploadMediaDetailAdapter.items = mutableListOf(UploadMediaDetail())
- Timber.d("Initialized default UploadMediaDetail for new fragment")
- }
- }
-
if (fragmentCallback != null) {
indexOfFragment = fragmentCallback!!.getIndexInViewFlipper(this)
- Timber.d("Fragment callback present, indexOfFragment = %d", indexOfFragment)
initializeFragment()
- } else {
- Timber.w("Fragment callback is null, skipping initializeFragment")
+ }
+
+ if (savedInstanceState != null) {
+ if (uploadMediaDetailAdapter.items.isEmpty() && fragmentCallback != null) {
+ uploadMediaDetailAdapter.items = savedInstanceState.getParcelableArrayList(UPLOAD_MEDIA_DETAILS)!!
+ presenter.setUploadMediaDetails(uploadMediaDetailAdapter.items, indexOfFragment)
+ }
}
try {
- if (indexOfFragment >= 0 && !presenter.getImageQuality(indexOfFragment, inAppPictureLocation, requireActivity())) {
- Timber.d("Image quality check failed, redirecting to MainActivity")
+ if (!presenter.getImageQuality(indexOfFragment, inAppPictureLocation, requireActivity())) {
startActivityWithFlags(
requireActivity(),
MainActivity::class.java,
@@ -217,12 +182,11 @@ class UploadMediaDetailFragment : UploadBaseFragment(), UploadMediaDetailsContra
Intent.FLAG_ACTIVITY_SINGLE_TOP
)
}
- } catch (e: Exception) {
- Timber.e(e, "Error during image quality check")
+ } catch (_: Exception) {
}
}
- internal fun initializeFragment() {
+ private fun initializeFragment() {
if (_binding == null) {
return
}
@@ -240,6 +204,7 @@ class UploadMediaDetailFragment : UploadBaseFragment(), UploadMediaDetailsContra
presenter.setupBasicKvStoreFactory { BasicKvStore(requireActivity(), it) }
presenter.receiveImage(uploadableFile, place, inAppPictureLocation)
+ initRecyclerView()
with (binding){
if (indexOfFragment == 0) {
@@ -298,12 +263,30 @@ class UploadMediaDetailFragment : UploadBaseFragment(), UploadMediaDetailsContra
}
}
+ /**
+ * init the description recycler veiw and caption recyclerview
+ */
+ private fun initRecyclerView() {
+ uploadMediaDetailAdapter = UploadMediaDetailAdapter(
+ this,
+ defaultKvStore.getString(Prefs.DESCRIPTION_LANGUAGE, "")!!,
+ recentLanguagesDao, voiceInputResultLauncher
+ )
+ uploadMediaDetailAdapter.callback =
+ UploadMediaDetailAdapter.Callback { titleStringID: Int, messageStringId: Int ->
+ showInfoAlert(titleStringID, messageStringId)
+ }
+ uploadMediaDetailAdapter.eventListener = this
+ binding.rvDescriptions.layoutManager = LinearLayoutManager(context)
+ binding.rvDescriptions.adapter = uploadMediaDetailAdapter
+ }
+
private fun showInfoAlert(titleStringID: Int, messageStringId: Int) {
showAlertDialog(
requireActivity(),
getString(titleStringID),
getString(messageStringId),
- getString(R.string.ok),
+ getString(android.R.string.ok),
null
)
}
@@ -605,14 +588,16 @@ class UploadMediaDetailFragment : UploadBaseFragment(), UploadMediaDetailsContra
var defaultLongitude = -122.431297
var defaultZoom = 16.0
+ val locationPickerIntent: Intent
+
/* Retrieve image location from EXIF if present or
check if user has provided location while using the in-app camera.
Use location of last UploadItem if none of them is available */
- val locationPickerIntent: Intent
if (uploadItem.gpsCoords != null && uploadItem.gpsCoords!!
.decLatitude != 0.0 && uploadItem.gpsCoords!!.decLongitude != 0.0
) {
- defaultLatitude = uploadItem.gpsCoords!!.decLatitude
+ defaultLatitude = uploadItem.gpsCoords!!
+ .decLatitude
defaultLongitude = uploadItem.gpsCoords!!.decLongitude
defaultZoom = uploadItem.gpsCoords!!.zoomLevel
@@ -628,7 +613,8 @@ class UploadMediaDetailFragment : UploadBaseFragment(), UploadMediaDetailsContra
defaultLongitude = locationLatLng[1].toDouble()
}
if (defaultKvStore.getString(LAST_ZOOM) != null) {
- defaultZoom = defaultKvStore.getString(LAST_ZOOM)!!.toDouble()
+ defaultZoom = defaultKvStore.getString(LAST_ZOOM)!!
+ .toDouble()
}
locationPickerIntent = LocationPicker.IntentBuilder()
@@ -835,7 +821,6 @@ class UploadMediaDetailFragment : UploadBaseFragment(), UploadMediaDetailsContra
{
showProgress(false)
uploadItem.imageQuality = IMAGE_OK
- uploadItem.hasInvalidLocation = false // Reset invalid location flag when user confirms upload
},
{
presenterCallback!!.deletePictureAtIndex(index)
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailsContract.kt b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailsContract.kt
index d6d774208..c368b96ac 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailsContract.kt
+++ b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailsContract.kt
@@ -54,7 +54,7 @@ interface UploadMediaDetailsContract {
fun showBadImagePopup(errorCode: Int, index: Int, uploadItem: UploadItem)
}
- interface UserActionListener : BasePresenter {
+ interface UserActionListener : BasePresenter {
fun setupBasicKvStoreFactory(factory: (String) -> BasicKvStore)
fun receiveImage(
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaPresenter.kt b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaPresenter.kt
index 6e26e02a6..77999cf2f 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaPresenter.kt
+++ b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaPresenter.kt
@@ -69,18 +69,7 @@ class UploadMediaPresenter @Inject constructor(
uploadMediaDetails: List,
uploadItemIndex: Int
) {
- val uploadItems = repository.getUploads()
- if (uploadItemIndex >= 0 && uploadItemIndex < uploadItems.size) {
- if (uploadMediaDetails.isNotEmpty()) {
- uploadItems[uploadItemIndex].uploadMediaDetails = uploadMediaDetails.toMutableList()
- Timber.d("Set uploadMediaDetails for index %d, size %d", uploadItemIndex, uploadMediaDetails.size)
- } else {
- uploadItems[uploadItemIndex].uploadMediaDetails = mutableListOf(UploadMediaDetail())
- Timber.w("Received empty uploadMediaDetails for index %d, initialized default", uploadItemIndex)
- }
- } else {
- Timber.e("Invalid index %d for uploadItems size %d, skipping setUploadMediaDetails", uploadItemIndex, uploadItems.size)
- }
+ repository.getUploads()[uploadItemIndex].uploadMediaDetails = uploadMediaDetails.toMutableList()
}
override fun setupBasicKvStoreFactory(factory: (String) -> BasicKvStore) {
@@ -118,10 +107,7 @@ class UploadMediaPresenter @Inject constructor(
view.showProgress(false)
val gpsCoords = uploadItem.gpsCoords
val hasImageCoordinates = gpsCoords != null && gpsCoords.imageCoordsExists
-
- // Only check for nearby places if image has coordinates AND no place was pre-selected
- // This prevents the popup from appearing when uploading from Nearby feature
- if (hasImageCoordinates && place == null && uploadItem.place == null) {
+ if (hasImageCoordinates && place == null) {
checkNearbyPlaces(uploadItem)
}
}, { throwable: Throwable? ->
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/worker/UploadWorker.kt b/app/src/main/java/fr/free/nrw/commons/upload/worker/UploadWorker.kt
index 21db20f1b..6d28085b2 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/worker/UploadWorker.kt
+++ b/app/src/main/java/fr/free/nrw/commons/upload/worker/UploadWorker.kt
@@ -393,12 +393,6 @@ class UploadWorker(
makeWikiDataEdit(uploadResult, contribution)
}
showSuccessNotification(contribution)
- if (appContext.contentResolver.persistedUriPermissions.any {
- it.uri == contribution.contentUri }) {
- appContext.contentResolver.releasePersistableUriPermission(
- contribution.contentUri!!, Intent.FLAG_GRANT_READ_URI_PERMISSION
- )
- }
} else {
Timber.e("Stash Upload failed")
showFailedNotification(contribution)
@@ -478,10 +472,7 @@ class UploadWorker(
if (wikiDataPlace != null) {
if (!contribution.hasInvalidLocation()) {
var revisionID: Long? = null
- val p18WasSkipped = !wikiDataPlace.imageValue.isNullOrBlank()
try {
- if (!p18WasSkipped) {
- // Only set P18 if the place does not already have a picture
revisionID =
wikidataEditService.createClaim(
wikiDataPlace,
@@ -498,11 +489,9 @@ class UploadWorker(
.subscribeOn(Schedulers.io())
.blockingAwait()
Timber.d("Updated WikiItem place ${place.name} with image ${place.pic}")
- }
}
+ showSuccessNotification(contribution)
}
- // Always show success notification, whether P18 was set or skipped
- showSuccessNotification(contribution)
} catch (exception: Exception) {
Timber.e(exception)
}
@@ -511,7 +500,6 @@ class UploadWorker(
wikidataEditService.handleImageClaimResult(
contribution.wikidataPlace!!,
revisionID,
- p18WasSkipped = p18WasSkipped
)
}
} else {
diff --git a/app/src/main/java/fr/free/nrw/commons/utils/ClipboardUtils.kt b/app/src/main/java/fr/free/nrw/commons/utils/ClipboardUtils.kt
deleted file mode 100644
index 64d3636f0..000000000
--- a/app/src/main/java/fr/free/nrw/commons/utils/ClipboardUtils.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-package fr.free.nrw.commons.utils
-
-import android.content.ClipData
-import android.content.ClipboardManager
-import android.content.Context
-import android.content.Context.CLIPBOARD_SERVICE
-
-object ClipboardUtils {
- // Convenience for Java usages - remove when they are converted.
- @JvmStatic
- fun copy(label: String?, text: String?, context: Context) {
- context.copyToClipboard(label, text)
- }
-}
-
-fun Context.copyToClipboard(label: String?, text: String?) {
- with(getSystemService(CLIPBOARD_SERVICE) as ClipboardManager) {
- setPrimaryClip(ClipData.newPlainText(label, text))
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/utils/ConfigUtils.kt b/app/src/main/java/fr/free/nrw/commons/utils/ConfigUtils.kt
index 95fa62a20..332c8d023 100644
--- a/app/src/main/java/fr/free/nrw/commons/utils/ConfigUtils.kt
+++ b/app/src/main/java/fr/free/nrw/commons/utils/ConfigUtils.kt
@@ -12,9 +12,9 @@ object ConfigUtils {
val isBetaFlavour: Boolean = BuildConfig.FLAVOR == "beta"
@JvmStatic
- private fun Context.getVersionName(): String? =
+ private fun Context.getVersionName(): String =
try {
- packageManager.getPackageInfo(packageName, 0).versionName ?: BuildConfig.VERSION_NAME
+ packageManager.getPackageInfo(packageName, 0).versionName
} catch (e: PackageManager.NameNotFoundException) {
BuildConfig.VERSION_NAME
}
diff --git a/app/src/main/java/fr/free/nrw/commons/utils/DatabaseUtils.kt b/app/src/main/java/fr/free/nrw/commons/utils/DatabaseUtils.kt
deleted file mode 100644
index 737f34614..000000000
--- a/app/src/main/java/fr/free/nrw/commons/utils/DatabaseUtils.kt
+++ /dev/null
@@ -1,51 +0,0 @@
-package fr.free.nrw.commons.utils
-
-import android.annotation.SuppressLint
-import android.database.Cursor
-
-fun Cursor.getStringArray(name: String): List =
- stringToArray(getString(name))
-
-/**
- * Gets the String at the current row and specified column.
- *
- * @param name The name of the column to get the String from.
- * @return The String if the column exists. Else, null is returned.
- */
-@SuppressLint("Range")
-fun Cursor.getString(name: String): String? {
- val index = getColumnIndex(name)
- if (index == -1) {
- return null
- }
- return getString(index)
-}
-
-@SuppressLint("Range")
-fun Cursor.getInt(name: String): Int =
- getInt(getColumnIndex(name))
-
-@SuppressLint("Range")
-fun Cursor.getLong(name: String): Long =
- getLong(getColumnIndex(name))
-
-/**
- * Converts string to List
- * @param listString comma separated single string from of list items
- * @return List of string
- */
-fun stringToArray(listString: String?): List {
- if (listString.isNullOrEmpty()) return emptyList();
- val elements = listString.split(",".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
- return listOf(*elements)
-}
-
-/**
- * Converts string to List
- * @param list list of items
- * @return string comma separated single string of items
- */
-fun arrayToString(list: List?): String? {
- return list?.joinToString(",")
-}
-
diff --git a/app/src/main/java/fr/free/nrw/commons/utils/EdgeToEdgeUtils.kt b/app/src/main/java/fr/free/nrw/commons/utils/EdgeToEdgeUtils.kt
deleted file mode 100644
index 04007485d..000000000
--- a/app/src/main/java/fr/free/nrw/commons/utils/EdgeToEdgeUtils.kt
+++ /dev/null
@@ -1,212 +0,0 @@
-package fr.free.nrw.commons.utils
-
-import android.view.View
-import android.view.ViewGroup.MarginLayoutParams
-import androidx.core.view.ViewCompat
-import androidx.core.view.WindowInsetsAnimationCompat
-import androidx.core.view.WindowInsetsCompat
-import androidx.core.view.marginBottom
-import androidx.core.view.marginLeft
-import androidx.core.view.marginRight
-import androidx.core.view.marginTop
-import androidx.core.view.updateLayoutParams
-import androidx.core.view.updatePadding
-import fr.free.nrw.commons.R
-
-/**
- * Applies edge-to-edge system bar insets to a [View]’s margins using a custom adjustment block.
- *
- * Stores the initial margins to ensure inset calculations are additive, and applies the provided
- * [block] with an [InsetsAccumulator] containing initial and system bar inset values.
- *
- * @param typeMask The type of window insets to apply. Defaults to [WindowInsetsCompat.Type.systemBars].
- * @param shouldConsumeInsets If `true`, the insets are consumed and not propagated to child views.
- * @param block Lambda applied to update [MarginLayoutParams] using the accumulated insets.
- */
-fun View.applyEdgeToEdgeInsets(
- typeMask: Int = WindowInsetsCompat.Type.systemBars(),
- shouldConsumeInsets: Boolean = true,
- block: MarginLayoutParams.(InsetsAccumulator) -> Unit
-) {
- ViewCompat.setOnApplyWindowInsetsListener(this) { view, windowInsets ->
- val insets = windowInsets.getInsets(typeMask)
-
- val initialTop = if (view.getTag(R.id.initial_margin_top) != null) {
- view.getTag(R.id.initial_margin_top) as Int
- } else {
- view.setTag(R.id.initial_margin_top, view.marginTop)
- view.marginTop
- }
-
- val initialBottom = if (view.getTag(R.id.initial_margin_bottom) != null) {
- view.getTag(R.id.initial_margin_bottom) as Int
- } else {
- view.setTag(R.id.initial_margin_bottom, view.marginBottom)
- view.marginBottom
- }
-
- val initialLeft = if (view.getTag(R.id.initial_margin_left) != null) {
- view.getTag(R.id.initial_margin_left) as Int
- } else {
- view.setTag(R.id.initial_margin_left, view.marginLeft)
- view.marginLeft
- }
-
- val initialRight = if (view.getTag(R.id.initial_margin_right) != null) {
- view.getTag(R.id.initial_margin_right) as Int
- } else {
- view.setTag(R.id.initial_margin_right, view.marginRight)
- view.marginRight
- }
-
- val accumulator = InsetsAccumulator(
- initialTop,
- insets.top,
- initialBottom,
- insets.bottom,
- initialLeft,
- insets.left,
- initialRight,
- insets.right
- )
-
- view.updateLayoutParams {
- apply { block(accumulator) }
- }
-
- if(shouldConsumeInsets) WindowInsetsCompat.CONSUMED else windowInsets
- }
-}
-
-/**
- * Applies edge-to-edge system bar insets to the top padding of the view.
- *
- * @param typeMask The type of window insets to apply. Defaults to [WindowInsetsCompat.Type.systemBars].
- */
-fun View.applyEdgeToEdgeTopPaddingInsets(
- typeMask: Int = WindowInsetsCompat.Type.systemBars(),
-) {
- ViewCompat.setOnApplyWindowInsetsListener(this) { view, windowInsets ->
- val insets = windowInsets.getInsets(typeMask)
-
- view.updatePadding(
- left = insets.left,
- right = insets.right,
- top = insets.top
- )
-
- WindowInsetsCompat.CONSUMED
- }
-}
-
-/**
- * Applies edge-to-edge system bar insets to the bottom padding of the view.
- *
- * @param typeMask The type of window insets to apply. Defaults to [WindowInsetsCompat.Type.systemBars].
- */
-fun View.applyEdgeToEdgeBottomPaddingInsets(
- typeMask: Int = WindowInsetsCompat.Type.systemBars(),
-) {
- ViewCompat.setOnApplyWindowInsetsListener(this) { view, windowInsets ->
- val insets = windowInsets.getInsets(typeMask)
-
- view.updatePadding(
- left = insets.left,
- right = insets.right,
- bottom = insets.bottom
- )
-
- WindowInsetsCompat.CONSUMED
- }
-}
-
-/**
- * Applies system bar insets to all margins (top, bottom, left, right) of the view.
- *
- * @param view The target view.
- * @param shouldConsumeInsets If `true`, the insets are consumed and not propagated to child views.
- */
-fun applyEdgeToEdgeAllInsets(
- view: View,
- shouldConsumeInsets: Boolean = true
-) = view.applyEdgeToEdgeInsets(shouldConsumeInsets = shouldConsumeInsets) { insets ->
- leftMargin = insets.left
- rightMargin = insets.right
- topMargin = insets.top
- bottomMargin = insets.bottom
-}
-
-/**
- * Applies system bar insets to the top and horizontal margins of the view.
- *
- * @param view The target view.
- */
-fun applyEdgeToEdgeTopInsets(view: View) = view.applyEdgeToEdgeInsets { insets ->
- leftMargin = insets.left
- rightMargin = insets.right
- topMargin = insets.top
-}
-
-/**
- * Applies system bar insets to the bottom and horizontal margins of the view.
- *
- * @param view The target view.
- */
-fun applyEdgeToEdgeBottomInsets(view: View) = view.applyEdgeToEdgeInsets { insets ->
- leftMargin = insets.left
- rightMargin = insets.right
- bottomMargin = insets.bottom
-}
-
-/**
- * Adjusts a [View]'s bottom margin dynamically to account for the on-screen keyboard (IME),
- * ensuring the view remains visible above the keyboard during transitions.
- *
- * Preserves the initial margin, adjusts during IME visibility changes,
- * and accounts for navigation bar insets to avoid double offsets.
- */
-fun View.handleKeyboardInsets() {
- ViewCompat.setOnApplyWindowInsetsListener(this) { view, insets ->
- val existingBottomMargin = if (view.getTag(R.id.initial_margin_bottom) != null) {
- view.getTag(R.id.initial_margin_bottom) as Int
- } else {
- view.setTag(R.id.initial_margin_bottom, view.marginBottom)
- view.marginBottom
- }
-
- val lp = layoutParams as MarginLayoutParams
-
- val navBarInsets = insets.getInsets(WindowInsetsCompat.Type.navigationBars())
- val imeInsets = insets.getInsets(WindowInsetsCompat.Type.ime())
- val imeVisible = insets.isVisible(WindowInsetsCompat.Type.ime())
- val imeBottomMargin = imeInsets.bottom - navBarInsets.bottom
-
- lp.bottomMargin = if (imeVisible && imeBottomMargin >= existingBottomMargin)
- imeBottomMargin + existingBottomMargin
- else existingBottomMargin
-
- layoutParams = lp
-
- WindowInsetsCompat.CONSUMED
- }
-}
-
-/**
- * Holds both initial margin values and system bar insets, providing summed values
- * for each side (top, bottom, left, right) to apply in layout updates.
- */
-data class InsetsAccumulator(
- private val initialTop: Int,
- private val insetTop: Int,
- private val initialBottom: Int,
- private val insetBottom: Int,
- private val initialLeft: Int,
- private val insetLeft: Int,
- private val initialRight: Int,
- private val insetRight: Int
-) {
- val top = initialTop + insetTop
- val bottom = initialBottom + insetBottom
- val left = initialLeft + insetLeft
- val right = initialRight + insetRight
-}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/utils/FixExtension.kt b/app/src/main/java/fr/free/nrw/commons/utils/FixExtension.kt
deleted file mode 100644
index b9e3988a3..000000000
--- a/app/src/main/java/fr/free/nrw/commons/utils/FixExtension.kt
+++ /dev/null
@@ -1,38 +0,0 @@
-package fr.free.nrw.commons.utils
-
-import java.util.Locale
-import java.util.regex.Pattern
-
-private val jpegPattern = Pattern.compile("\\.jpeg$", Pattern.CASE_INSENSITIVE)
-
-/**
- * Adds extension to filename. Converts to .jpg if system provides .jpeg, adds .jpg if no extension detected
- * @param theTitle File name
- * @param ext Correct extension
- * @return File with correct extension
- */
-fun fixExtension(theTitle: String, ext: String?): String {
- var result = theTitle
- var extension = ext
-
- // People are used to ".jpg" more than ".jpeg" which the system gives us.
- if (extension != null && extension.lowercase() == "jpeg") {
- extension = "jpg"
- }
-
- result = jpegPattern.matcher(result).replaceFirst(".jpg")
- if (extension != null &&
- !result.lowercase(Locale.getDefault()).endsWith("." + extension.lowercase())
- ) {
- result += ".$extension"
- }
-
- // If extension is still null, make it jpg. (Hotfix for https://github.com/commons-app/apps-android-commons/issues/228)
- // If title has an extension in it, if won't be true
- if (extension == null && result.lastIndexOf(".") <= 0) {
- extension = "jpg"
- result += ".$extension"
- }
-
- return result
-}
diff --git a/app/src/main/java/fr/free/nrw/commons/utils/GeoCoordinates.kt b/app/src/main/java/fr/free/nrw/commons/utils/GeoCoordinates.kt
deleted file mode 100644
index 513e36f10..000000000
--- a/app/src/main/java/fr/free/nrw/commons/utils/GeoCoordinates.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-package fr.free.nrw.commons.utils
-
-import android.content.Context
-import android.content.Intent
-import fr.free.nrw.commons.R
-import fr.free.nrw.commons.location.LatLng
-import fr.free.nrw.commons.utils.ViewUtil.showShortToast
-
-/**
- * Util function to handle geo coordinates with specified zoom level. It no longer depends on
- * google maps and any app capable of handling the map intent can handle it
- *
- * @param context The context for launching intent
- * @param latLng The latitude and longitude of the location
- * @param zoomLevel The zoom level
- */
-fun handleGeoCoordinates(
- context: Context, latLng: LatLng,
- zoomLevel: Double = 16.0
-) {
- val mapIntent = Intent(Intent.ACTION_VIEW, latLng.getGmmIntentUri(zoomLevel))
- if (mapIntent.resolveActivity(context.packageManager) != null) {
- context.startActivity(mapIntent)
- } else {
- showShortToast(context, context.getString(R.string.map_application_missing))
- }
-}
diff --git a/app/src/main/java/fr/free/nrw/commons/utils/ImageUtils.kt b/app/src/main/java/fr/free/nrw/commons/utils/ImageUtils.kt
index fa538bb21..ebff3d054 100644
--- a/app/src/main/java/fr/free/nrw/commons/utils/ImageUtils.kt
+++ b/app/src/main/java/fr/free/nrw/commons/utils/ImageUtils.kt
@@ -24,7 +24,6 @@ import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers
import timber.log.Timber
-import androidx.core.graphics.createBitmap
/**
* Created by blueSir9 on 3/10/17.
@@ -308,19 +307,16 @@ object ImageUtils {
* * @return
*/
@JvmStatic
- fun addRedBorder(bitmap: Bitmap, borderSize: Int, context: Context): Bitmap? {
- return bitmap.config?.let { config ->
- val bmpWithBorder =
- createBitmap(
- width = bitmap.width + borderSize * 2,
- height = bitmap.height + borderSize * 2,
- config = config
- )
- val canvas = Canvas(bmpWithBorder)
- canvas.drawColor(ContextCompat.getColor(context, R.color.deleteRed))
- canvas.drawBitmap(bitmap, borderSize.toFloat(), borderSize.toFloat(), null)
- return bmpWithBorder
- }
+ fun addRedBorder(bitmap: Bitmap, borderSize: Int, context: Context): Bitmap {
+ val bmpWithBorder = Bitmap.createBitmap(
+ bitmap.width + borderSize * 2,
+ bitmap.height + borderSize * 2,
+ bitmap.config
+ )
+ val canvas = Canvas(bmpWithBorder)
+ canvas.drawColor(ContextCompat.getColor(context, R.color.deleteRed))
+ canvas.drawBitmap(bitmap, borderSize.toFloat(), borderSize.toFloat(), null)
+ return bmpWithBorder
}
/**
diff --git a/app/src/main/java/fr/free/nrw/commons/utils/Licenses.kt b/app/src/main/java/fr/free/nrw/commons/utils/Licenses.kt
deleted file mode 100644
index 065a14718..000000000
--- a/app/src/main/java/fr/free/nrw/commons/utils/Licenses.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-package fr.free.nrw.commons.utils
-
-import fr.free.nrw.commons.R
-import fr.free.nrw.commons.settings.Prefs
-
-/**
- * Generates licence name with given ID
- * @return Name of license
- */
-fun String.toLicenseName(): Int = when (this) {
- Prefs.Licenses.CC_BY_3 -> R.string.license_name_cc_by
- Prefs.Licenses.CC_BY_4 -> R.string.license_name_cc_by_four
- Prefs.Licenses.CC_BY_SA_3 -> R.string.license_name_cc_by_sa
- Prefs.Licenses.CC_BY_SA_4 -> R.string.license_name_cc_by_sa_four
- Prefs.Licenses.CC0 -> R.string.license_name_cc0
- else -> throw IllegalStateException("Unrecognized license value: $this")
-}
-
-/**
- * Generates license url with given ID
- * @return Url of license
- */
-fun String.toLicenseUrl(): String = when (this) {
- Prefs.Licenses.CC_BY_3 -> "https://creativecommons.org/licenses/by/3.0/"
- Prefs.Licenses.CC_BY_4 -> "https://creativecommons.org/licenses/by/4.0/"
- Prefs.Licenses.CC_BY_SA_3 -> "https://creativecommons.org/licenses/by-sa/3.0/"
- Prefs.Licenses.CC_BY_SA_4 -> "https://creativecommons.org/licenses/by-sa/4.0/"
- Prefs.Licenses.CC0 -> "https://creativecommons.org/publicdomain/zero/1.0/"
- else -> throw IllegalStateException("Unrecognized license value: $this")
-}
-
diff --git a/app/src/main/java/fr/free/nrw/commons/utils/LocationUtils.kt b/app/src/main/java/fr/free/nrw/commons/utils/LocationUtils.kt
index cef137f43..1fbd87581 100644
--- a/app/src/main/java/fr/free/nrw/commons/utils/LocationUtils.kt
+++ b/app/src/main/java/fr/free/nrw/commons/utils/LocationUtils.kt
@@ -37,7 +37,7 @@ object LocationUtils {
latLng = LatLng(latLngArray[1].trim().toDouble(),
latLngArray[0].trim().toDouble(), 1f)
} catch (e: Exception) {
- Timber.e(e, "Error while parsing user entered lat long")
+ Timber.e("Error while parsing user entered lat long: %s", e)
}
return latLng
diff --git a/app/src/main/java/fr/free/nrw/commons/utils/Monuments.kt b/app/src/main/java/fr/free/nrw/commons/utils/Monuments.kt
deleted file mode 100644
index d5f5736f5..000000000
--- a/app/src/main/java/fr/free/nrw/commons/utils/Monuments.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-package fr.free.nrw.commons.utils
-
-import java.util.Calendar
-import java.util.Date
-
-/**
- * Get the start date of wlm monument
- * For this release we are hardcoding it to be 1st September
- * @return
- */
-const val wLMStartDate: String = "1 Sep"
-
-/***
- * Get the end date of wlm monument
- * For this release we are hardcoding it to be 31st October
- * @return
- */
-const val wLMEndDate: String = "30 Sep"
-
-/**
- * For now we are enabling the monuments only when the date lies between 1 Sept & 31 OCt
- */
-val isMonumentsEnabled: Boolean
- get() = Date().month == 8
-
-/***
- * Function to get the current WLM year
- * It increments at the start of September in line with the other WLM functions
- * (No consideration of locales for now)
- * @param calendar
- * @return
- */
-fun getWikiLovesMonumentsYear(calendar: Calendar): Int {
- var year = calendar[Calendar.YEAR]
- if (calendar[Calendar.MONTH] < Calendar.SEPTEMBER) {
- year -= 1
- }
- return year
-}
diff --git a/app/src/main/java/fr/free/nrw/commons/utils/PermissionUtils.kt b/app/src/main/java/fr/free/nrw/commons/utils/PermissionUtils.kt
index 5bcc9d1b2..df3b33bf6 100644
--- a/app/src/main/java/fr/free/nrw/commons/utils/PermissionUtils.kt
+++ b/app/src/main/java/fr/free/nrw/commons/utils/PermissionUtils.kt
@@ -209,8 +209,8 @@ object PermissionUtils {
activity,
activity.getString(rationaleTitle),
activity.getString(rationaleMessage),
- activity.getString(R.string.ok),
- activity.getString(R.string.cancel),
+ activity.getString(android.R.string.ok),
+ activity.getString(android.R.string.cancel),
{
if (activity is UploadActivity) {
activity.isShowPermissionsDialog = true
diff --git a/app/src/main/java/fr/free/nrw/commons/utils/UnderlineUtils.kt b/app/src/main/java/fr/free/nrw/commons/utils/UnderlineUtils.kt
deleted file mode 100644
index 75760d4ab..000000000
--- a/app/src/main/java/fr/free/nrw/commons/utils/UnderlineUtils.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-package fr.free.nrw.commons.utils
-
-import android.widget.TextView
-import androidx.core.text.buildSpannedString
-import androidx.core.text.underline
-
-object UnderlineUtils {
- // Convenience method for Java usages - remove when those classes are converted
- @JvmStatic
- fun setUnderlinedText(textView: TextView, stringResourceName: Int) {
- textView.setUnderlinedText(stringResourceName)
- }
-}
-
-fun TextView.setUnderlinedText(stringResourceName: Int) {
- text = buildSpannedString {
- underline { append(context.getString(stringResourceName)) }
- }
-}
diff --git a/app/src/main/java/fr/free/nrw/commons/utils/UrlUtils.kt b/app/src/main/java/fr/free/nrw/commons/utils/UrlUtils.kt
deleted file mode 100644
index 4843cf0aa..000000000
--- a/app/src/main/java/fr/free/nrw/commons/utils/UrlUtils.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-package fr.free.nrw.commons.utils
-
-import android.content.Context
-import android.content.Intent
-import android.net.Uri
-import androidx.browser.customtabs.CustomTabColorSchemeParams
-import androidx.browser.customtabs.CustomTabsIntent
-import androidx.core.content.ContextCompat
-import fr.free.nrw.commons.R
-import timber.log.Timber
-
-/**
- * Opens Custom Tab Activity with in-app browser for the specified URL.
- * Launches intent for web URL
- */
-fun handleWebUrl(context: Context, url: Uri) {
- Timber.d("Launching web url %s", url.toString())
-
- val color = CustomTabColorSchemeParams.Builder()
- .setToolbarColor(ContextCompat.getColor(context, R.color.primaryColor))
- .setSecondaryToolbarColor(ContextCompat.getColor(context, R.color.primaryDarkColor))
- .build()
-
- val customTabsIntent = CustomTabsIntent.Builder()
- .setDefaultColorSchemeParams(color)
- .setExitAnimations(
- context, android.R.anim.slide_in_left, android.R.anim.slide_out_right
- ).build()
-
- // Clear previous browser tasks, so that back/exit buttons work as intended.
- customTabsIntent.intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
- customTabsIntent.launchUrl(context, url)
-}
diff --git a/app/src/main/java/fr/free/nrw/commons/wikidata/WikidataEditService.kt b/app/src/main/java/fr/free/nrw/commons/wikidata/WikidataEditService.kt
index f4bf23073..0b49c03e9 100644
--- a/app/src/main/java/fr/free/nrw/commons/wikidata/WikidataEditService.kt
+++ b/app/src/main/java/fr/free/nrw/commons/wikidata/WikidataEditService.kt
@@ -196,16 +196,13 @@ class WikidataEditService @Inject constructor(
return wikidataClient.setClaim(claim, COMMONS_APP_TAG).blockingSingle()
}
- fun handleImageClaimResult(wikidataItem: WikidataItem, revisionId: Long?, p18WasSkipped: Boolean = false) {
+ fun handleImageClaimResult(wikidataItem: WikidataItem, revisionId: Long?) {
if (revisionId != null) {
wikidataEditListener?.onSuccessfulWikidataEdit()
showSuccessToast(wikidataItem.name)
- } else if (!p18WasSkipped) {
+ } else {
Timber.d("Unable to make wiki data edit for entity %s", wikidataItem)
showLongToast(context, context.getString(R.string.wikidata_edit_failure))
- } else {
- Timber.d("Wikidata edit skipped for entity %s because P18 already exists", wikidataItem)
- // No error shown to user, as this is not a failure
}
}
diff --git a/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwErrorResponse.kt b/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwErrorResponse.kt
deleted file mode 100644
index 40f5afe68..000000000
--- a/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwErrorResponse.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package fr.free.nrw.commons.wikidata.mwapi
-
-import fr.free.nrw.commons.wikidata.model.BaseModel
-
-class MwErrorResponse : BaseModel() {
- val error: MwLegacyServiceError? = null
-}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwIOException.kt b/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwIOException.kt
deleted file mode 100644
index 62ca87166..000000000
--- a/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwIOException.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package fr.free.nrw.commons.wikidata.mwapi
-
-import java.io.IOException
-
-class MwIOException(string: String, val error: MwLegacyServiceError) : IOException(string)
diff --git a/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwLegacyServiceError.kt b/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwLegacyServiceError.kt
deleted file mode 100644
index 16ee29709..000000000
--- a/app/src/main/java/fr/free/nrw/commons/wikidata/mwapi/MwLegacyServiceError.kt
+++ /dev/null
@@ -1,14 +0,0 @@
-package fr.free.nrw.commons.wikidata.mwapi
-
-import fr.free.nrw.commons.wikidata.model.BaseModel
-
-class MwLegacyServiceError : BaseModel() {
- val code: String? = null
- private val info: String? = null
-
- val title: String
- get() = code ?: ""
-
- val details: String
- get() = info ?: ""
-}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_arrow_16dp.xml b/app/src/main/res/drawable/ic_arrow_16dp.xml
new file mode 100644
index 000000000..d99c0b5e4
--- /dev/null
+++ b/app/src/main/res/drawable/ic_arrow_16dp.xml
@@ -0,0 +1,4 @@
+
+
+
diff --git a/app/src/main/res/drawable/thumbnail_not_selected.xml b/app/src/main/res/drawable/thumbnail_not_selected.xml
index 50404cf47..8ead4b377 100644
--- a/app/src/main/res/drawable/thumbnail_not_selected.xml
+++ b/app/src/main/res/drawable/thumbnail_not_selected.xml
@@ -1,5 +1,5 @@
-
+
diff --git a/app/src/main/res/layout-land/activity_login.xml b/app/src/main/res/layout-land/activity_login.xml
index fa36b56ff..b9adfd033 100644
--- a/app/src/main/res/layout-land/activity_login.xml
+++ b/app/src/main/res/layout-land/activity_login.xml
@@ -125,7 +125,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/password"
- android:imeOptions="actionNext"
+ android:imeOptions="flagNoExtractUi"
android:inputType="textPassword" />
@@ -148,9 +148,9 @@
android:id="@+id/login_two_factor"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:imeOptions="actionDone"
+ android:hint="@string/_2fa_code"
+ android:imeOptions="flagNoExtractUi"
android:inputType="number"
- android:maxLines="1"
android:visibility="gone"
tools:visibility="visible" />
diff --git a/app/src/main/res/layout-xlarge/activity_login.xml b/app/src/main/res/layout-xlarge/activity_login.xml
index 74f89228e..c255aa45f 100644
--- a/app/src/main/res/layout-xlarge/activity_login.xml
+++ b/app/src/main/res/layout-xlarge/activity_login.xml
@@ -128,7 +128,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/password"
- android:imeOptions="actionNext"
+ android:imeOptions="flagNoExtractUi"
android:inputType="textPassword" />
@@ -151,9 +151,9 @@
android:id="@+id/login_two_factor"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:imeOptions="actionDone"
+ android:hint="@string/_2fa_code"
+ android:imeOptions="flagNoExtractUi"
android:inputType="number"
- android:maxLines="1"
android:visibility="gone"
tools:visibility="visible" />
diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml
index 1cdfce8ae..0da9f5d9f 100644
--- a/app/src/main/res/layout/activity_login.xml
+++ b/app/src/main/res/layout/activity_login.xml
@@ -131,7 +131,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/password"
- android:imeOptions="actionNext"
+ android:imeOptions="flagNoExtractUi"
android:inputType="textPassword" />
@@ -155,9 +155,7 @@
android:id="@+id/login_two_factor"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:imeOptions="actionDone"
- android:inputType="number"
- android:maxLines="1"
+ android:imeOptions="flagNoExtractUi"
android:visibility="gone"
tools:visibility="visible" />
diff --git a/app/src/main/res/layout/activity_notification.xml b/app/src/main/res/layout/activity_notification.xml
index 800c8aa0b..a8b60dea3 100644
--- a/app/src/main/res/layout/activity_notification.xml
+++ b/app/src/main/res/layout/activity_notification.xml
@@ -35,7 +35,6 @@
android:scrollbars="vertical"
android:fadeScrollbars="false"
android:scrollbarThumbVertical="@color/primaryColor"
- android:clipToPadding="false"
android:scrollbarSize="@dimen/dimen_6"/>
diff --git a/app/src/main/res/layout/activity_quiz.xml b/app/src/main/res/layout/activity_quiz.xml
index 1b238b8b3..f5de2c635 100644
--- a/app/src/main/res/layout/activity_quiz.xml
+++ b/app/src/main/res/layout/activity_quiz.xml
@@ -71,7 +71,7 @@
android:layout_alignParentRight="true"
android:layout_marginLeft="@dimen/activity_margin_horizontal"
android:layout_marginRight="@dimen/activity_margin_horizontal"
- android:text="@string/quiz_i_am_not_sure"
+ android:text="I am not sure"
android:backgroundTint="#D6DCE0"
android:layout_marginBottom="@dimen/activity_margin_vertical" />
diff --git a/app/src/main/res/layout/activity_quiz_result.xml b/app/src/main/res/layout/activity_quiz_result.xml
index 877b02ea4..65db12902 100644
--- a/app/src/main/res/layout/activity_quiz_result.xml
+++ b/app/src/main/res/layout/activity_quiz_result.xml
@@ -98,7 +98,7 @@
android:paddingHorizontal="@dimen/activity_margin_horizontal"
android:layout_height="wrap_content"
android:backgroundTint="#D6DCE0"
- android:text="@string/quiz_continue" />
+ android:text="Continue" />
diff --git a/app/src/main/res/layout/activity_review.xml b/app/src/main/res/layout/activity_review.xml
index 2075fb888..871d25c7d 100644
--- a/app/src/main/res/layout/activity_review.xml
+++ b/app/src/main/res/layout/activity_review.xml
@@ -24,35 +24,22 @@
android:id="@+id/toolbarBinding"
layout="@layout/toolbar" />
-
-
-
-
-
-
-
+ android:layout_gravity="center_horizontal"
+ android:drawableEnd="@drawable/ic_info_outline_24dp"
+ android:drawablePadding="@dimen/medium_height"
+ android:drawableTint="@color/button_blue_dark"
+ android:paddingLeft="@dimen/medium_height"
+ android:paddingRight="@dimen/medium_height"
+ android:text="@string/skip_image"
+ android:textAllCaps="true"
+ android:textColor="@color/button_blue_dark"
+ android:textStyle="bold" />
+ android:text="Don't show this message again" />