diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml
index a4682fd3c..dcbba0597 100644
--- a/.github/ISSUE_TEMPLATE/bug-report.yml
+++ b/.github/ISSUE_TEMPLATE/bug-report.yml
@@ -70,7 +70,7 @@ body:
required: false
- type: textarea
attributes:
- label: Screen-shots
+ label: Screenshots
description: Add screenshots related to the issue (if available). Can be created by pressing the Volume Down and Power Button at the same time on Android 4.0 and higher.
validations:
required: false
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fc22a2b99..575aa6a32 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,20 @@
# Wikimedia Commons for Android
+## v6.0.2
+
+### What's changed
+* Addressed a bug that prevented the keyboard from appearing in various text fields, such as on the upload wizard
+* Links in the "File usages" list are now clickable and will take you to the correct page.
+* Titles for file usages are now clearer and easier to understand
+* Bug fixes and stability improvements
+
+## v6.0.1
+
+### What's changed
+* The app now supports Android 15 with an improved user interface
+* Enhanced Nearby with robust and more reliable labels
+* Bug fixes and stability improvements
+
## v5.6.1
### What's changed
diff --git a/README.md b/README.md
index 0b31ff5be..37f1a7872 100644
--- a/README.md
+++ b/README.md
@@ -29,11 +29,12 @@ Thank you all for your work!
| [
misaochan](https://github.com/misaochan) | [
translatewiki](https://github.com/translatewiki) | [
neslihanturan](https://github.com/neslihanturan) | [
yuvipanda](https://github.com/yuvipanda) | [
nicolas-raoul](https://github.com/nicolas-raoul) |
| :---: | :---: | :---: | :---: | :---: |
-| [
domdomegg](https://github.com/domdomegg) | [
maskaravivek](https://github.com/maskaravivek) | [
psh](https://github.com/psh) | [
madhurgupta10](https://github.com/madhurgupta10) | [
ashishkumar468](https://github.com/ashishkumar468) |
-| [
bvibber](https://github.com/bvibber) | [
whym](https://github.com/whym) | [
akaita](https://github.com/akaita) | [
veyndan](https://github.com/veyndan) | [
ujjwalagrawal17](https://github.com/ujjwalagrawal17) |
-| [
macgills](https://github.com/macgills) | [
dbrant](https://github.com/dbrant) | [
vanshikaarora](https://github.com/vanshikaarora) | [
sivaraam](https://github.com/sivaraam) | [
Ayan-10](https://github.com/Ayan-10) |
-| [
shashankiitbhu](https://github.com/shashankiitbhu) | [
Pratham2305](https://github.com/Pratham2305) | [
sandarumk](https://github.com/sandarumk) | [
tanvidadu](https://github.com/tanvidadu) | [
cypherop](https://github.com/cypherop) |
-| [
Prince-kushwaha](https://github.com/Prince-kushwaha) | [
tobias47n9e](https://github.com/tobias47n9e) | [
4D17Y4](https://github.com/4D17Y4) | [
hismaeel](https://github.com/hismaeel) | [
tshradheya](https://github.com/tshradheya) |
+| [
psh](https://github.com/psh) | [
domdomegg](https://github.com/domdomegg) | [
maskaravivek](https://github.com/maskaravivek) | [
madhurgupta10](https://github.com/madhurgupta10) | [
ashishkumar468](https://github.com/ashishkumar468) |
+| [
bvibber](https://github.com/bvibber) | [
whym](https://github.com/whym) | [
akaita](https://github.com/akaita) | [
sivaraam](https://github.com/sivaraam) | [
veyndan](https://github.com/veyndan) |
+| [
ujjwalagrawal17](https://github.com/ujjwalagrawal17) | [
macgills](https://github.com/macgills) | [
amire80](https://github.com/amire80) | [
dbrant](https://github.com/dbrant) | [
vanshikaarora](https://github.com/vanshikaarora) |
+| [
RitikaPahwa4444](https://github.com/RitikaPahwa4444) | [
Ayan-10](https://github.com/Ayan-10) | [
rohit9625](https://github.com/rohit9625) | [
shashankiitbhu](https://github.com/shashankiitbhu) | [
Pratham2305](https://github.com/Pratham2305) |
+| [
parneet-guraya](https://github.com/parneet-guraya) | [
sandarumk](https://github.com/sandarumk) | [
tanvidadu](https://github.com/tanvidadu) | [
cypherop](https://github.com/cypherop) | [
Prince-kushwaha](https://github.com/Prince-kushwaha) |
+
.. and [many more](https://github.com/commons-app/apps-android-commons/graphs/contributors).
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index ddbbb4984..41788128c 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -24,8 +24,8 @@ android {
applicationId = "fr.free.nrw.commons"
minSdk = 21
targetSdk = 35
- versionCode = 1057
- versionName = "6.0.1"
+ versionCode = 1059
+ versionName = "6.1.0"
setProperty("archivesBaseName", "app-commons-v$versionName-" + getBranchName())
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
@@ -226,6 +226,7 @@ dependencies {
implementation(libs.rxbinding)
implementation(libs.rxbinding.appcompat)
implementation(libs.facebook.fresco)
+ implementation(libs.facebook.fresco.middleware)
implementation(libs.apache.commons.lang3)
// UI
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index d56a874b5..17917666d 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -57,8 +57,7 @@
tools:replace="android:appComponentFactory">
+ android:exported="false" />
@@ -85,6 +84,7 @@
android:parentActivityName=".customselector.ui.selector.CustomSelectorActivity" />
@@ -103,7 +103,7 @@
android:exported="true"
android:hardwareAccelerated="false"
android:icon="@mipmap/ic_launcher"
- android:windowSoftInputMode="adjustResize">
+ android:windowSoftInputMode="adjustPan">
diff --git a/app/src/main/java/fr/free/nrw/commons/OkHttpConnectionFactory.kt b/app/src/main/java/fr/free/nrw/commons/OkHttpConnectionFactory.kt
index 1c28d5fe4..c54c3aefb 100644
--- a/app/src/main/java/fr/free/nrw/commons/OkHttpConnectionFactory.kt
+++ b/app/src/main/java/fr/free/nrw/commons/OkHttpConnectionFactory.kt
@@ -1,7 +1,11 @@
package fr.free.nrw.commons
import androidx.annotation.VisibleForTesting
+import fr.free.nrw.commons.wikidata.GsonUtil
import fr.free.nrw.commons.wikidata.cookies.CommonsCookieJar
+import fr.free.nrw.commons.wikidata.mwapi.MwErrorResponse
+import fr.free.nrw.commons.wikidata.mwapi.MwIOException
+import fr.free.nrw.commons.wikidata.mwapi.MwLegacyServiceError
import okhttp3.Cache
import okhttp3.Interceptor
import okhttp3.OkHttpClient
@@ -86,16 +90,25 @@ private class UnsuccessfulResponseInterceptor : Interceptor {
rsp.peekBody(ERRORS_PREFIX.length.toLong()).use { responseBody ->
if (ERRORS_PREFIX == responseBody.string()) {
rsp.body.use { body ->
- throw IOException(body!!.string())
+ val bodyString = body!!.string()
+
+ throw MwIOException(
+ "MediaWiki API returned error: $bodyString",
+ GsonUtil.defaultGson.fromJson(
+ bodyString,
+ MwErrorResponse::class.java
+ ).error!!,
+ )
}
}
}
- } catch (e: IOException) {
+ } catch (e: MwIOException) {
// Log the error as debug (and therefore, "expected") or at error level
if (suppressErrors) {
Timber.d(e, "Suppressed (known / expected) error")
} else {
Timber.e(e)
+ throw e
}
}
return rsp
diff --git a/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.kt b/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.kt
index 688f508ae..0c9901b56 100644
--- a/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.kt
+++ b/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.kt
@@ -89,7 +89,7 @@ class LoginActivity : AccountAuthenticatorActivity() {
binding = ActivityLoginBinding.inflate(layoutInflater)
applyEdgeToEdgeAllInsets(binding!!.root)
- binding?.aboutPrivacyPolicy?.handleKeyboardInsets()
+ binding!!.root.handleKeyboardInsets()
with(binding!!) {
setContentView(root)
diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsDao.kt b/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsDao.kt
index d64ab16b3..e21e1ac8f 100644
--- a/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsDao.kt
+++ b/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsDao.kt
@@ -144,8 +144,18 @@ class BookmarkItemsDao @Inject constructor(
*/
@SuppressLint("Range")
fun fromCursor(cursor: Cursor) = with(cursor) {
+ var name = getString(COLUMN_NAME)
+ if (name == null) {
+ name = ""
+ }
+
+ var id = getString(COLUMN_ID)
+ if (id == null) {
+ id = ""
+ }
+
DepictedItem(
- getString(COLUMN_NAME),
+ name,
getString(COLUMN_DESCRIPTION),
getString(COLUMN_IMAGE),
getStringArray(COLUMN_INSTANCE_LIST),
@@ -155,7 +165,7 @@ class BookmarkItemsDao @Inject constructor(
getStringArray(COLUMN_CATEGORIES_THUMBNAIL_LIST)
),
getString(COLUMN_IS_SELECTED).toBoolean(),
- getString(COLUMN_ID)
+ id
)
}
@@ -163,19 +173,13 @@ class BookmarkItemsDao @Inject constructor(
categoryNameList: List,
categoryDescriptionList: List,
categoryThumbnailList: List
- ): List {
- return buildList {
- for (i in categoryNameList.indices) {
- add(
- CategoryItem(
- categoryNameList[i],
- categoryDescriptionList[i],
- categoryThumbnailList[i],
- false
- )
- )
- }
- }
+ ): List = categoryNameList.mapIndexed { index, name ->
+ CategoryItem(
+ name = name,
+ description = categoryDescriptionList.getOrNull(index),
+ thumbnail = categoryThumbnailList.getOrNull(index),
+ isSelected = false
+ )
}
/**
diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesDao.kt b/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesDao.kt
index e30b3160d..00c8e3228 100644
--- a/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesDao.kt
+++ b/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesDao.kt
@@ -128,7 +128,10 @@ class BookmarkPicturesDao @Inject constructor(
}
fun fromCursor(cursor: Cursor): Bookmark {
- val fileName = cursor.getString(COLUMN_MEDIA_NAME)
+ var fileName = cursor.getString(COLUMN_MEDIA_NAME)
+ if (fileName == null) {
+ fileName = ""
+ }
return Bookmark(
fileName, cursor.getString(COLUMN_CREATOR), uriForName(fileName)
)
diff --git a/app/src/main/java/fr/free/nrw/commons/campaigns/CampaignConfig.kt b/app/src/main/java/fr/free/nrw/commons/campaigns/CampaignConfig.kt
index 6bf0bc0ed..9f94e8592 100644
--- a/app/src/main/java/fr/free/nrw/commons/campaigns/CampaignConfig.kt
+++ b/app/src/main/java/fr/free/nrw/commons/campaigns/CampaignConfig.kt
@@ -7,8 +7,8 @@ import com.google.gson.annotations.SerializedName
*/
class CampaignConfig {
@SerializedName("showOnlyLiveCampaigns")
- private val showOnlyLiveCampaigns = false
+ var showOnlyLiveCampaigns = false
@SerializedName("sortBy")
- private val sortBy: String? = null
-}
+ var sortBy: String? = null
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/campaigns/CampaignResponseDTO.kt b/app/src/main/java/fr/free/nrw/commons/campaigns/CampaignResponseDTO.kt
index 767732eb7..1656109e7 100644
--- a/app/src/main/java/fr/free/nrw/commons/campaigns/CampaignResponseDTO.kt
+++ b/app/src/main/java/fr/free/nrw/commons/campaigns/CampaignResponseDTO.kt
@@ -8,8 +8,8 @@ import fr.free.nrw.commons.campaigns.models.Campaign
*/
class CampaignResponseDTO {
@SerializedName("config")
- val campaignConfig: CampaignConfig? = null
+ var campaignConfig: CampaignConfig? = null
@SerializedName("campaigns")
- val campaigns: List? = null
-}
+ var campaigns: List? = null
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.kt b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.kt
index 29267452b..b9532a12e 100644
--- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.kt
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.kt
@@ -180,8 +180,8 @@ class ContributionController @Inject constructor(@param:Named("default_preferenc
showAlertDialog(
activity, activity.getString(R.string.location_permission_title),
activity.getString(R.string.in_app_camera_location_permission_rationale),
- activity.getString(android.R.string.ok),
- activity.getString(android.R.string.cancel),
+ activity.getString(R.string.ok),
+ activity.getString(R.string.cancel),
{
createDialogsAndHandleLocationPermissions(
activity,
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.kt b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.kt
index b86cd6dc9..6d0822604 100644
--- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.kt
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.kt
@@ -5,7 +5,6 @@ import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.content.res.Configuration
-import android.net.Uri
import android.os.Bundle
import android.os.Parcelable
import android.view.LayoutInflater
@@ -20,6 +19,8 @@ import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
import androidx.annotation.VisibleForTesting
+import androidx.core.net.toUri
+import androidx.core.os.BundleCompat
import androidx.paging.PagedList
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
@@ -38,12 +39,10 @@ import fr.free.nrw.commons.filepicker.FilePicker
import fr.free.nrw.commons.media.MediaClient
import fr.free.nrw.commons.profile.ProfileActivity
import fr.free.nrw.commons.utils.DialogUtil.showAlertDialog
-import fr.free.nrw.commons.utils.SystemThemeUtils
import fr.free.nrw.commons.utils.ViewUtil.showShortToast
import fr.free.nrw.commons.utils.copyToClipboard
import fr.free.nrw.commons.utils.handleWebUrl
import fr.free.nrw.commons.wikidata.model.WikiSite
-import org.apache.commons.lang3.StringUtils
import javax.inject.Inject
import javax.inject.Named
@@ -53,10 +52,6 @@ import javax.inject.Named
*/
class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsListContract.View,
ContributionsListAdapter.Callback, WikipediaInstructionsDialogFragment.Callback {
- @JvmField
- @Inject
- var systemThemeUtils: SystemThemeUtils? = null
-
@JvmField
@Inject
var controller: ContributionController? = null
@@ -83,13 +78,14 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL
var sessionManager: SessionManager? = null
private var binding: FragmentContributionsListBinding? = null
- private var fab_close: Animation? = null
- private var fab_open: Animation? = null
- private var rotate_forward: Animation? = null
- private var rotate_backward: Animation? = null
+ private var fabClose: Animation? = null
+ private var fabOpen: Animation? = null
+ private var rotateForward: Animation? = null
+ private var rotateBackward: Animation? = null
private var isFabOpen = false
- private lateinit var inAppCameraLocationPermissionLauncher: ActivityResultLauncher>
+ private lateinit var inAppCameraLocationPermissionLauncher:
+ ActivityResultLauncher>
@VisibleForTesting
var rvContributionsList: RecyclerView? = null
@@ -100,8 +96,8 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL
@VisibleForTesting
var callback: Callback? = null
- private val SPAN_COUNT_LANDSCAPE = 3
- private val SPAN_COUNT_PORTRAIT = 1
+ private val spanCountLandscape = 3
+ private val spanCountPortrait = 1
private var contributionsSize = 0
private var userName: String? = null
@@ -150,7 +146,7 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL
userName = requireArguments().getString(ProfileActivity.KEY_USERNAME)
}
- if (StringUtils.isEmpty(userName)) {
+ if (userName.isNullOrEmpty()) {
userName = sessionManager!!.userName
}
inAppCameraLocationPermissionLauncher =
@@ -161,7 +157,8 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL
controller?.locationPermissionCallback?.onLocationPermissionGranted()
} else {
activity?.let { currentActivity ->
- if (currentActivity.shouldShowRequestPermissionRationale(permission.ACCESS_FINE_LOCATION)) {
+ if (currentActivity.shouldShowRequestPermissionRationale(
+ permission.ACCESS_FINE_LOCATION)) {
controller?.handleShowRationaleFlowCameraLocation(
currentActivity,
inAppCameraLocationPermissionLauncher, // Pass launcher
@@ -169,7 +166,8 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL
)
} else {
controller?.locationPermissionCallback?.onLocationPermissionDenied(
- currentActivity.getString(R.string.in_app_camera_location_permission_denied)
+ currentActivity.getString(
+ R.string.in_app_camera_location_permission_denied)
)
}
}
@@ -189,7 +187,7 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL
contributionsListPresenter!!.onAttachView(this)
binding!!.fabCustomGallery.setOnClickListener { v: View? -> launchCustomSelector() }
binding!!.fabCustomGallery.setOnLongClickListener { view: View? ->
- showShortToast(context, fr.free.nrw.commons.R.string.custom_selector_title)
+ showShortToast(context, R.string.custom_selector_title)
true
}
@@ -199,7 +197,7 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL
} else {
binding!!.tvContributionsOfUser.visibility = View.VISIBLE
binding!!.tvContributionsOfUser.text =
- getString(fr.free.nrw.commons.R.string.contributions_of_user, userName)
+ getString(R.string.contributions_of_user, userName)
binding!!.fabLayout.visibility = View.GONE
}
@@ -237,7 +235,10 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL
}
private fun initAdapter() {
- adapter = ContributionsListAdapter(this, mediaClient!!, mediaDataExtractor!!, compositeDisposable)
+ adapter = ContributionsListAdapter(this,
+ mediaClient!!,
+ mediaDataExtractor!!,
+ compositeDisposable)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@@ -312,7 +313,7 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL
override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean {
if (e.action == MotionEvent.ACTION_DOWN) {
if (isFabOpen) {
- animateFAB(isFabOpen)
+ animateFAB(true)
}
}
return false
@@ -344,14 +345,20 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL
}
private fun getSpanCount(orientation: Int): Int {
- return if (orientation == Configuration.ORIENTATION_LANDSCAPE) SPAN_COUNT_LANDSCAPE else SPAN_COUNT_PORTRAIT
+ return if (orientation == Configuration.ORIENTATION_LANDSCAPE)
+ spanCountLandscape
+ else
+ spanCountPortrait
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
// check orientation
binding!!.fabLayout.orientation =
- if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) LinearLayout.HORIZONTAL else LinearLayout.VERTICAL
+ if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE)
+ LinearLayout.HORIZONTAL
+ else
+ LinearLayout.VERTICAL
rvContributionsList
?.setLayoutManager(
GridLayoutManager(context, getSpanCount(newConfig.orientation))
@@ -359,10 +366,10 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL
}
private fun initializeAnimations() {
- fab_open = AnimationUtils.loadAnimation(activity, fr.free.nrw.commons.R.anim.fab_open)
- fab_close = AnimationUtils.loadAnimation(activity, fr.free.nrw.commons.R.anim.fab_close)
- rotate_forward = AnimationUtils.loadAnimation(activity, fr.free.nrw.commons.R.anim.rotate_forward)
- rotate_backward = AnimationUtils.loadAnimation(activity, fr.free.nrw.commons.R.anim.rotate_backward)
+ fabOpen = AnimationUtils.loadAnimation(activity, R.anim.fab_open)
+ fabClose = AnimationUtils.loadAnimation(activity, R.anim.fab_close)
+ rotateForward = AnimationUtils.loadAnimation(activity, R.anim.rotate_forward)
+ rotateBackward = AnimationUtils.loadAnimation(activity, R.anim.rotate_backward)
}
private fun setListeners() {
@@ -378,7 +385,7 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL
binding!!.fabCamera.setOnLongClickListener { view: View? ->
showShortToast(
context,
- fr.free.nrw.commons.R.string.add_contribution_from_camera
+ R.string.add_contribution_from_camera
)
true
}
@@ -387,7 +394,7 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL
animateFAB(isFabOpen)
}
binding!!.fabGallery.setOnLongClickListener { view: View? ->
- showShortToast(context, fr.free.nrw.commons.R.string.menu_from_gallery)
+ showShortToast(context, R.string.menu_from_gallery)
true
}
}
@@ -395,7 +402,7 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL
/**
* Launch Custom Selector.
*/
- protected fun launchCustomSelector() {
+ private fun launchCustomSelector() {
controller!!.initiateCustomGalleryPickWithPermission(
requireActivity(),
customSelectorLauncherForResult
@@ -411,18 +418,18 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL
this.isFabOpen = !isFabOpen
if (binding!!.fabPlus.isShown) {
if (isFabOpen) {
- binding!!.fabPlus.startAnimation(rotate_backward)
- binding!!.fabCamera.startAnimation(fab_close)
- binding!!.fabGallery.startAnimation(fab_close)
- binding!!.fabCustomGallery.startAnimation(fab_close)
+ binding!!.fabPlus.startAnimation(rotateBackward)
+ binding!!.fabCamera.startAnimation(fabClose)
+ binding!!.fabGallery.startAnimation(fabClose)
+ binding!!.fabCustomGallery.startAnimation(fabClose)
binding!!.fabCamera.hide()
binding!!.fabGallery.hide()
binding!!.fabCustomGallery.hide()
} else {
- binding!!.fabPlus.startAnimation(rotate_forward)
- binding!!.fabCamera.startAnimation(fab_open)
- binding!!.fabGallery.startAnimation(fab_open)
- binding!!.fabCustomGallery.startAnimation(fab_open)
+ binding!!.fabPlus.startAnimation(rotateForward)
+ binding!!.fabCamera.startAnimation(fabOpen)
+ binding!!.fabGallery.startAnimation(fabOpen)
+ binding!!.fabCustomGallery.startAnimation(fabOpen)
binding!!.fabCamera.show()
binding!!.fabGallery.show()
binding!!.fabCustomGallery.show()
@@ -434,9 +441,9 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL
/**
* Shows welcome message if user has no contributions yet i.e. new user.
*/
- override fun showWelcomeTip(shouldShow: Boolean) {
+ override fun showWelcomeTip(numberOfUploads: Boolean) {
binding!!.noContributionsYet.visibility =
- if (shouldShow) View.VISIBLE else View.GONE
+ if (numberOfUploads) View.VISIBLE else View.GONE
}
/**
@@ -456,22 +463,22 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
- val layoutManager = rvContributionsList
- ?.getLayoutManager() as GridLayoutManager?
+ val layoutManager = rvContributionsList?.layoutManager as GridLayoutManager?
outState.putParcelable(RV_STATE, layoutManager!!.onSaveInstanceState())
}
override fun onViewStateRestored(savedInstanceState: Bundle?) {
super.onViewStateRestored(savedInstanceState)
if (null != savedInstanceState) {
- val savedRecyclerLayoutState = savedInstanceState.getParcelable(RV_STATE)
+ val savedRecyclerLayoutState =
+ BundleCompat.getParcelable(savedInstanceState, RV_STATE, Parcelable::class.java)
rvContributionsList!!.layoutManager!!.onRestoreInstanceState(savedRecyclerLayoutState)
}
}
- override fun openMediaDetail(position: Int, isWikipediaButtonDisplayed: Boolean) {
+ override fun openMediaDetail(contribution: Int, isWikipediaPageExists: Boolean) {
if (null != callback) { //Just being safe, ideally they won't be called when detached
- callback!!.showDetail(position, isWikipediaButtonDisplayed)
+ callback!!.showDetail(contribution, isWikipediaPageExists)
}
}
@@ -483,8 +490,8 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL
override fun addImageToWikipedia(contribution: Contribution?) {
showAlertDialog(
requireActivity(),
- getString(fr.free.nrw.commons.R.string.add_picture_to_wikipedia_article_title),
- getString(fr.free.nrw.commons.R.string.add_picture_to_wikipedia_article_desc),
+ getString(R.string.add_picture_to_wikipedia_article_title),
+ getString(R.string.add_picture_to_wikipedia_article_desc),
{
if (contribution != null) {
showAddImageToWikipediaInstructions(contribution)
@@ -498,16 +505,18 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL
* @param contribution
*/
private fun showAddImageToWikipediaInstructions(contribution: Contribution) {
- val fragmentManager = fragmentManager
+ val fragmentManager = this.parentFragmentManager
val fragment = newInstance(contribution)
fragment.callback =
- WikipediaInstructionsDialogFragment.Callback { contribution: Contribution?, copyWikicode: Boolean ->
- this.onConfirmClicked(
+ WikipediaInstructionsDialogFragment.Callback {
+ contribution: Contribution?,
+ copyWikicode: Boolean ->
+ onConfirmClicked(
contribution,
copyWikicode
)
}
- fragment.show(fragmentManager!!, "WikimediaFragment")
+ fragment.show(fragmentManager, "WikimediaFragment")
}
@@ -534,7 +543,7 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL
val url =
languageWikipediaSite!!.mobileUrl() + "/wiki/" + (contribution!!.wikidataPlace
?.getWikipediaPageTitle())
- handleWebUrl(requireContext(), Uri.parse(url))
+ handleWebUrl(requireContext(), url.toUri())
}
fun getContributionStateAt(position: Int): Int {
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.kt b/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.kt
index 5c2c44ab5..d481017b2 100644
--- a/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.kt
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.kt
@@ -153,21 +153,7 @@ after opening the app.
}
}
setUpPager()
- /**
- * Ask the user for media location access just after login
- * so that location in the EXIF metadata of the images shared by the user
- * is retained on devices running Android 10 or above
- */
-// if (VERSION.SDK_INT >= VERSION_CODES.Q) {
-// ActivityCompat.requestPermissions(this,
-// new String[]{Manifest.permission.ACCESS_MEDIA_LOCATION}, 0);
-// PermissionUtils.checkPermissionsAndPerformAction(
-// this,
-// () -> {},
-// R.string.media_location_permission_denied,
-// R.string.add_location_manually,
-// permission.ACCESS_MEDIA_LOCATION);
-// }
+
checkAndResumeStuckUploads()
}
}
@@ -338,7 +324,7 @@ after opening the app.
)
.subscribeOn(Schedulers.io())
.blockingGet()
- Timber.d("Resuming " + stuckUploads.size + " uploads...")
+ Timber.d("Resuming %d uploads...", stuckUploads.size)
if (!stuckUploads.isEmpty()) {
for (contribution in stuckUploads) {
contribution.state = Contribution.STATE_QUEUED
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/SetWallpaperWorker.kt b/app/src/main/java/fr/free/nrw/commons/contributions/SetWallpaperWorker.kt
index 06c31fede..8e899fcba 100644
--- a/app/src/main/java/fr/free/nrw/commons/contributions/SetWallpaperWorker.kt
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/SetWallpaperWorker.kt
@@ -45,10 +45,10 @@ class SetWallpaperWorker(context: Context, params: WorkerParameters) :
}
}
- override fun onFailureImpl(dataSource: DataSource>?) {
+ override fun onFailureImpl(dataSource: DataSource?>) {
Timber.d("Error getting bitmap from image url %s", imageUrl.toString())
showNotification(context, "Setting Wallpaper Failed", "Failed to download image.")
- dataSource?.close()
+ dataSource.close()
}
}, CallerThreadExecutor.getInstance())
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/model/Folder.kt b/app/src/main/java/fr/free/nrw/commons/customselector/model/Folder.kt
index ec08f6f73..4bf295f4c 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/model/Folder.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/model/Folder.kt
@@ -39,4 +39,11 @@ data class Folder(
return true
}
+
+ override fun hashCode(): Int {
+ var result = bucketId.hashCode()
+ result = 31 * result + name.hashCode()
+ result = 31 * result + images.hashCode()
+ return result
+ }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/model/Image.kt b/app/src/main/java/fr/free/nrw/commons/customselector/model/Image.kt
index a2965fb5d..a172f28e2 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/model/Image.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/model/Image.kt
@@ -1,6 +1,7 @@
package fr.free.nrw.commons.customselector.model
import android.net.Uri
+import android.os.Build
import android.os.Parcel
import android.os.Parcelable
@@ -48,7 +49,12 @@ data class Image(
this(
parcel.readLong(),
parcel.readString()!!,
- parcel.readParcelable(Uri::class.java.classLoader)!!,
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ parcel.readParcelable(Uri::class.java.classLoader, Uri::class.java)!!
+ } else {
+ @Suppress("DEPRECATION")
+ parcel.readParcelable(Uri::class.java.classLoader)!!
+ },
parcel.readString()!!,
parcel.readLong(),
parcel.readString()!!,
@@ -121,4 +127,16 @@ data class Image(
override fun newArray(size: Int): Array = arrayOfNulls(size)
}
+
+ override fun hashCode(): Int {
+ var result = id.hashCode()
+ result = 31 * result + bucketId.hashCode()
+ result = 31 * result + name.hashCode()
+ result = 31 * result + uri.hashCode()
+ result = 31 * result + path.hashCode()
+ result = 31 * result + bucketName.hashCode()
+ result = 31 * result + sha1.hashCode()
+ result = 31 * result + date.hashCode()
+ return result
+ }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/ImageAdapter.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/ImageAdapter.kt
index 62a440ff4..c3ef4a784 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/ImageAdapter.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/ImageAdapter.kt
@@ -168,8 +168,7 @@ class ImageAdapter(
// Getting selected index when switch is off
} else if (actionableImagesMap.size > position) {
- ImageHelper
- .getIndex(selectedImages, ArrayList(actionableImagesMap.values)[position])
+ ImageHelper.getIndex(selectedImages, ArrayList(actionableImagesMap.values)[position])
// For any other case return -1
} else {
@@ -348,8 +347,14 @@ class ImageAdapter(
numberOfSelectedImagesMarkedAsNotForUpload--
}
notifyItemChanged(position, ImageUnselected())
+ // Notify listener of deselection to update UI
+ imageSelectListener.onSelectedImagesChanged(selectedImages, numberOfSelectedImagesMarkedAsNotForUpload)
} else {
- val image = images[position]
+ // Prevent adding the same image multiple times
+ val image = if (showAlreadyActionedImages) images[position] else ArrayList(actionableImagesMap.values)[position]
+ if (selectedImages.contains(image)) {
+ return // Image already selected, ignore additional clicks
+ }
scope.launch(ioDispatcher) {
val imageSHA1 = imageLoader.getSHA1(image, defaultDispatcher)
withContext(Dispatchers.Main) {
@@ -373,7 +378,6 @@ class ImageAdapter(
}
selectedImages.add(image)
notifyItemChanged(position, ImageSelectedOrUpdated())
-
imageSelectListener.onSelectedImagesChanged(selectedImages, numberOfSelectedImagesMarkedAsNotForUpload)
}
}
@@ -632,4 +636,4 @@ class ImageAdapter(
fun setSingleSelection(single: Boolean) {
singleSelection = single
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageFragment.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageFragment.kt
index 4f37106cc..a5182fe62 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageFragment.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageFragment.kt
@@ -9,7 +9,6 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ProgressBar
-import android.widget.Switch
import androidx.appcompat.app.AlertDialog
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.isVisible
@@ -20,6 +19,7 @@ import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
+import com.google.android.material.switchmaterial.SwitchMaterial
import fr.free.nrw.commons.contributions.Contribution
import fr.free.nrw.commons.contributions.ContributionDao
import fr.free.nrw.commons.customselector.database.NotForUploadStatusDao
@@ -47,6 +47,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
+import timber.log.Timber
import java.util.TreeMap
import javax.inject.Inject
import kotlin.collections.ArrayList
@@ -81,7 +82,7 @@ class ImageFragment :
*/
private var selectorRV: RecyclerView? = null
private var loader: ProgressBar? = null
- private var switch: Switch? = null
+ private var switch: SwitchMaterial? = null
lateinit var filteredImages: ArrayList
/**
@@ -211,8 +212,12 @@ class ImageFragment :
savedInstanceState: Bundle?,
): View? {
_binding = FragmentCustomSelectorBinding.inflate(inflater, container, false)
- imageAdapter =
- ImageAdapter(requireActivity(), activity as ImageSelectListener, imageLoader!!)
+
+ // ensures imageAdapter is initialized
+ if (!::imageAdapter.isInitialized) {
+ imageAdapter = ImageAdapter(requireActivity(), activity as ImageSelectListener, imageLoader!!)
+ Timber.d("Initialized imageAdapter in onCreateView")
+ }
// Set single selection mode if needed
val singleSelection = (activity as? CustomSelectorActivity)?.intent?.getBooleanExtra(CustomSelectorActivity.EXTRA_SINGLE_SELECTION, false) == true
imageAdapter.setSingleSelection(singleSelection)
@@ -370,7 +375,12 @@ class ImageFragment :
* notifyDataSetChanged, rebuild the holder views to account for deleted images.
*/
override fun onResume() {
- imageAdapter.notifyDataSetChanged()
+ if (::imageAdapter.isInitialized) {
+ imageAdapter.notifyDataSetChanged()
+ Timber.d("Notified imageAdapter in onResume")
+ } else {
+ Timber.w("imageAdapter not initialized in onResume")
+ }
super.onResume()
}
@@ -380,14 +390,19 @@ class ImageFragment :
* Save the Image Fragment state.
*/
override fun onDestroy() {
- imageAdapter.cleanUp()
+ if (::imageAdapter.isInitialized) {
+ imageAdapter.cleanUp()
+ Timber.d("Cleaned up imageAdapter in onDestroy")
+ } else {
+ Timber.w("imageAdapter not initialized in onDestroy, skipping cleanup")
+ }
val position =
- (selectorRV?.layoutManager as GridLayoutManager)
- .findFirstVisibleItemPosition()
+ (selectorRV?.layoutManager as? GridLayoutManager)
+ ?.findFirstVisibleItemPosition() ?: -1
- // Check for empty RecyclerView.
- if (position != -1 && filteredImages.size > 0) {
+ // check for valid position and non-empty image list
+ if (position != -1 && filteredImages.isNotEmpty() && ::imageAdapter.isInitialized) {
context?.let { context ->
context
.getSharedPreferences(
@@ -396,34 +411,57 @@ class ImageFragment :
)?.let { prefs ->
prefs.edit()?.let { editor ->
editor.putLong("ItemId", imageAdapter.getImageIdAt(position))?.apply()
+ Timber.d("Saved last visible item ID: %d", imageAdapter.getImageIdAt(position))
}
}
}
+ } else {
+ Timber.d("Skipped saving item ID: position=%d, filteredImages.size=%d, imageAdapter initialized=%b",
+ position, filteredImages.size, ::imageAdapter.isInitialized)
}
super.onDestroy()
}
override fun onDestroyView() {
_binding = null
+ selectorRV = null
+ loader = null
+ switch = null
+ progressLayout = null
super.onDestroyView()
}
override fun refresh() {
- imageAdapter.refresh(filteredImages, allImages, getUploadingContributions())
+ if (::imageAdapter.isInitialized) {
+ imageAdapter.refresh(filteredImages, allImages, getUploadingContributions())
+ Timber.d("Refreshed imageAdapter")
+ } else {
+ Timber.w("imageAdapter not initialized in refresh")
+ }
}
/**
* Removes the image from the actionable image map
*/
fun removeImage(image: Image) {
- imageAdapter.removeImageFromActionableImageMap(image)
+ if (::imageAdapter.isInitialized) {
+ imageAdapter.removeImageFromActionableImageMap(image)
+ Timber.d("Removed image from actionable image map")
+ } else {
+ Timber.w("imageAdapter not initialized in removeImage")
+ }
}
/**
* Clears the selected images
*/
fun clearSelectedImages() {
- imageAdapter.clearSelectedImages()
+ if (::imageAdapter.isInitialized) {
+ imageAdapter.clearSelectedImages()
+ Timber.d("Cleared selected images")
+ } else {
+ Timber.w("imageAdapter not initialized in clearSelectedImages")
+ }
}
/**
@@ -434,6 +472,15 @@ class ImageFragment :
selectedImages: ArrayList,
shouldRefresh: Boolean,
) {
+ if (::imageAdapter.isInitialized) {
+ imageAdapter.setSelectedImages(selectedImages)
+ if (shouldRefresh) {
+ imageAdapter.refresh(filteredImages, allImages, getUploadingContributions())
+ }
+ Timber.d("Passed %d selected images to imageAdapter, shouldRefresh=%b", selectedImages.size, shouldRefresh)
+ } else {
+ Timber.w("imageAdapter not initialized in passSelectedImages")
+ }
}
/**
@@ -443,6 +490,7 @@ class ImageFragment :
if (!progressDialog.isShowing) {
progressDialogLayout.progressDialogText.text = text
progressDialog.show()
+ Timber.d("Showing mark/unmark progress dialog: %s", text)
}
}
@@ -452,6 +500,7 @@ class ImageFragment :
fun dismissMarkUnmarkProgressDialog() {
if (progressDialog.isShowing) {
progressDialog.dismiss()
+ Timber.d("Dismissed mark/unmark progress dialog")
}
}
@@ -461,4 +510,4 @@ class ImageFragment :
listOf(Contribution.STATE_IN_PROGRESS, Contribution.STATE_FAILED, Contribution.STATE_QUEUED, Contribution.STATE_PAUSED),
)?.subscribeOn(Schedulers.io())
?.blockingGet() ?: emptyList()
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/description/DescriptionEditActivity.kt b/app/src/main/java/fr/free/nrw/commons/description/DescriptionEditActivity.kt
index 89d43845b..b1f1b7f9b 100644
--- a/app/src/main/java/fr/free/nrw/commons/description/DescriptionEditActivity.kt
+++ b/app/src/main/java/fr/free/nrw/commons/description/DescriptionEditActivity.kt
@@ -150,7 +150,7 @@ class DescriptionEditActivity :
this,
getString(titleStringID),
getString(messageStringId),
- getString(android.R.string.ok),
+ getString(R.string.ok),
null
)
}
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/ExploreFragment.kt b/app/src/main/java/fr/free/nrw/commons/explore/ExploreFragment.kt
index ea96b50a3..bc8f9cfaa 100644
--- a/app/src/main/java/fr/free/nrw/commons/explore/ExploreFragment.kt
+++ b/app/src/main/java/fr/free/nrw/commons/explore/ExploreFragment.kt
@@ -64,6 +64,9 @@ class ExploreFragment : CommonsDaggerSupportFragment() {
override fun onPageScrollStateChanged(state: Int) = Unit
override fun onPageSelected(position: Int) {
binding!!.viewPager.canScroll = position != 2
+ if (position == 2) {
+ mapRootFragment?.requestLocationIfNeeded()
+ }
}
})
setTabs()
@@ -171,14 +174,12 @@ class ExploreFragment : CommonsDaggerSupportFragment() {
// if on Map tab, show all menu options, else only show search
binding!!.viewPager.addOnPageChangeListener(object : OnPageChangeListener {
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) = Unit
-
+ override fun onPageScrollStateChanged(state: Int) = Unit
override fun onPageSelected(position: Int) {
- others.setVisible((position == 2))
- }
-
- override fun onPageScrollStateChanged(state: Int) {
- if (state == ViewPager.SCROLL_STATE_IDLE && binding!!.viewPager.currentItem == 2) {
- onPageSelected(2)
+ binding!!.viewPager.canScroll = position != 2
+ others.setVisible(position == 2)
+ if (position == 2) {
+ mapRootFragment?.requestLocationIfNeeded()
}
}
})
@@ -194,7 +195,6 @@ class ExploreFragment : CommonsDaggerSupportFragment() {
*/
override fun onOptionsItemSelected(item: MenuItem): Boolean {
// Handle item selection
-
when (item.itemId) {
R.id.action_search -> {
startActivityWithFlags(requireActivity(), SearchActivity::class.java)
@@ -224,6 +224,4 @@ class ExploreFragment : CommonsDaggerSupportFragment() {
retainInstance = true
}
}
-}
-
-
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/ExploreMapRootFragment.kt b/app/src/main/java/fr/free/nrw/commons/explore/ExploreMapRootFragment.kt
index af65834eb..d405709a8 100644
--- a/app/src/main/java/fr/free/nrw/commons/explore/ExploreMapRootFragment.kt
+++ b/app/src/main/java/fr/free/nrw/commons/explore/ExploreMapRootFragment.kt
@@ -193,9 +193,20 @@ class ExploreMapRootFragment : CommonsDaggerSupportFragment, MediaDetailProvider
binding = null
}
+ fun requestLocationIfNeeded() {
+ mapFragment?.requestLocationIfNeeded()
+ }
+
+ override fun setUserVisibleHint(isVisibleToUser: Boolean) {
+ super.setUserVisibleHint(isVisibleToUser)
+ if (isVisibleToUser) {
+ requestLocationIfNeeded()
+ }
+ }
+
companion object {
fun newInstance(): ExploreMapRootFragment = ExploreMapRootFragment().apply {
retainInstance = true
}
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/depictions/WikidataItemDetailsActivity.kt b/app/src/main/java/fr/free/nrw/commons/explore/depictions/WikidataItemDetailsActivity.kt
index 32af67e95..d025fdfe1 100644
--- a/app/src/main/java/fr/free/nrw/commons/explore/depictions/WikidataItemDetailsActivity.kt
+++ b/app/src/main/java/fr/free/nrw/commons/explore/depictions/WikidataItemDetailsActivity.kt
@@ -112,8 +112,8 @@ class WikidataItemDetailsActivity : BaseActivity(), MediaDetailProvider, Categor
viewPagerAdapter!!.setTabs(
R.string.title_for_media to depictionImagesListFragment!!,
- R.string.title_for_subcategories to childDepictionsFragment,
- R.string.title_for_parent_categories to parentDepictionsFragment
+ R.string.title_for_child_classes to childDepictionsFragment,
+ R.string.title_for_parent_classes to parentDepictionsFragment
)
binding!!.viewPager.offscreenPageLimit = 2
viewPagerAdapter!!.notifyDataSetChanged()
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.kt b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.kt
index e64f12db3..a1bae09fb 100644
--- a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.kt
+++ b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.kt
@@ -140,8 +140,8 @@ class ExploreMapFragment : CommonsDaggerSupportFragment(), ExploreMapContract.Vi
requireActivity(),
requireActivity().getString(R.string.location_permission_title),
requireActivity().getString(R.string.location_permission_rationale_explore),
- requireActivity().getString(android.R.string.ok),
- requireActivity().getString(android.R.string.cancel),
+ requireActivity().getString(R.string.ok),
+ requireActivity().getString(R.string.cancel),
{ askForLocationPermission() },
null,
null
@@ -269,31 +269,60 @@ class ExploreMapFragment : CommonsDaggerSupportFragment(), ExploreMapContract.Vi
override fun onZoom(event: ZoomEvent?): Boolean = false
})
- if (!locationPermissionsHelper!!.checkLocationPermission(requireActivity())) {
- askForLocationPermission()
- }
+ // removed tha permission check here to prevent it from running on fragment creation
}
override fun onResume() {
super.onResume()
binding!!.mapView.onResume()
presenter!!.attachView(this)
- registerNetworkReceiver()
- if (isResumed) {
- if (locationPermissionsHelper!!.checkLocationPermission(requireActivity())) {
- performMapReadyActions()
- } else {
- startMapWithoutPermission()
- }
+ locationManager.addLocationListener(this)
+ if (broadcastReceiver != null) {
+ requireActivity().registerReceiver(broadcastReceiver, intentFilter)
}
+ setSearchThisAreaButtonVisibility(false)
}
override fun onPause() {
super.onPause()
// unregistering the broadcastReceiver, as it was causing an exception and a potential crash
unregisterNetworkReceiver()
+ locationManager.unregisterLocationManager()
+ locationManager.removeLocationListener(this)
}
+ fun requestLocationIfNeeded() {
+ if (!isVisible) return // skips if not visible to user
+ if (locationPermissionsHelper!!.checkLocationPermission(requireActivity())) {
+ if (locationPermissionsHelper!!.isLocationAccessToAppsTurnedOn()) {
+ locationManager.registerLocationManager()
+ drawMyLocationMarker()
+ } else {
+ locationPermissionsHelper!!.showLocationOffDialog(requireActivity(), R.string.location_off_dialog_text)
+ }
+ } else {
+ locationPermissionsHelper!!.requestForLocationAccess(
+ R.string.location_permission_title,
+ R.string.location_permission_rationale
+ )
+ }
+ }
+
+ private fun drawMyLocationMarker() {
+ val location = locationManager.getLastLocation()
+ if (location != null) {
+ val geoPoint = GeoPoint(location.latitude, location.longitude)
+ val startMarker = Marker(binding!!.mapView).apply {
+ setPosition(geoPoint)
+ setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM)
+ icon = ContextCompat.getDrawable(requireContext(), R.drawable.current_location_marker)
+ title = "Your Location"
+ textLabelFontSize = 24
+ }
+ binding!!.mapView.overlays.add(startMarker)
+ binding!!.mapView.invalidate()
+ }
+ }
/**
* Unregisters the networkReceiver
@@ -936,13 +965,17 @@ class ExploreMapFragment : CommonsDaggerSupportFragment(), ExploreMapContract.Vi
if (geoPoint != null) {
binding!!.mapView.controller.setCenter(geoPoint)
val overlays = binding!!.mapView.overlays
+ // collects the indices of items to remove
+ val indicesToRemove = mutableListOf()
for (i in overlays.indices) {
- if (overlays[i] is Marker) {
- binding!!.mapView.overlays.removeAt(i)
- } else if (overlays[i] is ScaleDiskOverlay) {
- binding!!.mapView.overlays.removeAt(i)
+ if (overlays[i] is Marker || overlays[i] is ScaleDiskOverlay) {
+ indicesToRemove.add(i)
}
}
+ // removes the items in reverse order to avoid index shifting
+ indicesToRemove.sortedDescending().forEach { index ->
+ binding!!.mapView.overlays.removeAt(index)
+ }
val diskOverlay = ScaleDiskOverlay(
requireContext(),
geoPoint, 2000, GeoConstants.UnitOfMeasure.foot
@@ -952,7 +985,6 @@ class ExploreMapFragment : CommonsDaggerSupportFragment(), ExploreMapContract.Vi
this.style = Paint.Style.STROKE
this.strokeWidth = 2f
})
-
setCirclePaint1(Paint().apply {
setColor(Color.argb(40, 128, 128, 128))
this.style = Paint.Style.FILL_AND_STROKE
@@ -961,7 +993,6 @@ class ExploreMapFragment : CommonsDaggerSupportFragment(), ExploreMapContract.Vi
setDisplaySizeMax(1700)
}
binding!!.mapView.overlays.add(diskOverlay)
-
val startMarker = Marker(
binding!!.mapView
).apply {
@@ -1079,7 +1110,24 @@ class ExploreMapFragment : CommonsDaggerSupportFragment(), ExploreMapContract.Vi
override fun onLocationPermissionDenied(toastMessage: String) = Unit
- override fun onLocationPermissionGranted() = Unit
+ override fun onLocationPermissionGranted() {
+ if (locationPermissionsHelper!!.isLocationAccessToAppsTurnedOn()) {
+ locationManager.registerLocationManager()
+ drawMyLocationMarker()
+ } else {
+ locationPermissionsHelper!!.showLocationOffDialog(requireActivity(), R.string.location_off_dialog_text)
+ }
+ onLocationChanged(LocationChangeType.PERMISSION_JUST_GRANTED, null)
+ }
+
+ fun onLocationChanged(locationChangeType: LocationChangeType, location: Location?) {
+ if (locationChangeType == LocationChangeType.PERMISSION_JUST_GRANTED) {
+ val curLatLng = locationManager.getLastLocation() ?: getMapCenter()
+ populatePlaces(curLatLng)
+ } else {
+ presenter!!.updateMap(locationChangeType)
+ }
+ }
companion object {
fun newInstance(): ExploreMapFragment {
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesDao.kt b/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesDao.kt
index e1d0740de..d16d250dd 100644
--- a/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesDao.kt
+++ b/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesDao.kt
@@ -163,11 +163,19 @@ class RecentSearchesDao @Inject constructor(
* @param cursor
* @return RecentSearch object
*/
- fun fromCursor(cursor: Cursor): RecentSearch = RecentSearch(
- uriForId(cursor.getInt(COLUMN_ID)),
- cursor.getString(COLUMN_NAME),
- Date(cursor.getLong(COLUMN_LAST_USED))
- )
+ fun fromCursor(cursor: Cursor): RecentSearch {
+ var query = cursor.getString(COLUMN_NAME)
+
+ if (query == null) {
+ query = ""
+ }
+
+ return RecentSearch(
+ uriForId(cursor.getInt(COLUMN_ID)),
+ query,
+ Date(cursor.getLong(COLUMN_LAST_USED))
+ )
+ }
/**
* This class contains the database table architechture for recent searches,
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesFragment.kt b/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesFragment.kt
index c0f1bd5db..e7903c9ed 100644
--- a/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesFragment.kt
+++ b/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesFragment.kt
@@ -67,10 +67,10 @@ class RecentSearchesFragment : CommonsDaggerSupportFragment() {
private fun showDeleteRecentAlertDialog(context: Context) {
AlertDialog.Builder(context)
.setMessage(getString(R.string.delete_recent_searches_dialog))
- .setPositiveButton(android.R.string.yes) { dialog: DialogInterface, _: Int ->
+ .setPositiveButton(R.string.yes) { dialog: DialogInterface, _: Int ->
setDeleteRecentPositiveButton(context, dialog)
}
- .setNegativeButton(android.R.string.no, null)
+ .setNegativeButton(R.string.no, null)
.setCancelable(false)
.create()
.show()
@@ -102,7 +102,7 @@ class RecentSearchesFragment : CommonsDaggerSupportFragment() {
setDeletePositiveButton(context, dialog, position)
}
)
- .setNegativeButton(android.R.string.cancel, null)
+ .setNegativeButton(R.string.cancel, null)
.setCancelable(false)
.create()
.show()
diff --git a/app/src/main/java/fr/free/nrw/commons/fileusages/FileUsagesUiModel.kt b/app/src/main/java/fr/free/nrw/commons/fileusages/FileUsagesUiModel.kt
index 63b0740d0..540c87e4c 100644
--- a/app/src/main/java/fr/free/nrw/commons/fileusages/FileUsagesUiModel.kt
+++ b/app/src/main/java/fr/free/nrw/commons/fileusages/FileUsagesUiModel.kt
@@ -1,18 +1,68 @@
package fr.free.nrw.commons.fileusages
+import android.net.Uri
+import timber.log.Timber
+
/**
- * Show where file is being used on Commons and oher wikis.
+ * Data model for displaying file usage information in the UI, including the title and link to the page.
*/
data class FileUsagesUiModel(
val title: String,
val link: String?
)
+/**
+ * Converts a FileUsage object to a UI model for Commons file usages.
+ * Creates a link to the file's page on Commons.
+ */
fun FileUsage.toUiModel(): FileUsagesUiModel {
- return FileUsagesUiModel(title = title, link = "https://commons.wikimedia.org/wiki/$title")
+ // Replace spaces with underscores and URL-encode the title for the link
+ val encodedTitle = Uri.encode(title.replace(" ", "_"))
+ return FileUsagesUiModel(
+ title = title,
+ link = "https://commons.wikimedia.org/wiki/$encodedTitle"
+ )
}
+/**
+ * Converts a GlobalFileUsage object to a UI model for file usages on other wikis.
+ * Generates a link to the page and prefixes the title with the wiki code (e.g., "(en) Title").
+ */
fun GlobalFileUsage.toUiModel(): FileUsagesUiModel {
- // link is associated with sub items under wiki group (which is not used ATM)
- return FileUsagesUiModel(title = wiki, link = null)
-}
+ // Log input values for debugging
+ Timber.d("Converting GlobalFileUsage: wiki=$wiki, title=$title")
+
+ // Check for invalid or empty inputs
+ if (wiki.isBlank() || title.isBlank()) {
+ Timber.w("Invalid input: wiki=$wiki, title=$title")
+ return FileUsagesUiModel(title = title, link = null)
+ }
+
+ // Extract wiki code for prefix (e.g., "en" from "en.wikipedia.org" or "enwiki")
+ val wikiCode = when {
+ wiki.contains(".") -> wiki.substringBefore(".") // e.g., "en" from "en.wikipedia.org"
+ wiki == "commonswiki" -> "commons"
+ wiki.endsWith("wiki") -> wiki.removeSuffix("wiki")
+ else -> wiki
+ }
+
+ // Create prefixed title, e.g., "(en) Changi East Depot"
+ val prefixedTitle = "($wikiCode) $title"
+
+ // Determine the domain for the URL
+ val domain = when {
+ wiki.contains(".") -> wiki // Already a full domain, e.g., "en.wikipedia.org"
+ wiki == "commonswiki" -> "commons.wikimedia.org"
+ wiki.endsWith("wiki") -> wiki.removeSuffix("wiki") + ".wikipedia.org"
+ else -> "$wiki.wikipedia.org" // Fallback for simple codes like "en"
+ }
+
+ // Normalize title: replace spaces with underscores and URL-encode
+ val encodedTitle = Uri.encode(title.replace(" ", "_"))
+
+ // Build the full URL
+ val url = "https://$domain/wiki/$encodedTitle"
+ Timber.d("Generated URL: $url")
+
+ return FileUsagesUiModel(title = prefixedTitle, link = url)
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/location/LocationPermissionsHelper.kt b/app/src/main/java/fr/free/nrw/commons/location/LocationPermissionsHelper.kt
index fefb59adb..47b4165ad 100644
--- a/app/src/main/java/fr/free/nrw/commons/location/LocationPermissionsHelper.kt
+++ b/app/src/main/java/fr/free/nrw/commons/location/LocationPermissionsHelper.kt
@@ -64,8 +64,8 @@ class LocationPermissionsHelper(
activity,
activity.getString(dialogTitleResource),
activity.getString(dialogTextResource),
- activity.getString(android.R.string.ok),
- activity.getString(android.R.string.cancel),
+ activity.getString(R.string.ok),
+ activity.getString(R.string.cancel),
{
ActivityCompat.requestPermissions(
activity,
diff --git a/app/src/main/java/fr/free/nrw/commons/locationpicker/LocationPickerActivity.kt b/app/src/main/java/fr/free/nrw/commons/locationpicker/LocationPickerActivity.kt
index e4fedf2e4..08dee587b 100644
--- a/app/src/main/java/fr/free/nrw/commons/locationpicker/LocationPickerActivity.kt
+++ b/app/src/main/java/fr/free/nrw/commons/locationpicker/LocationPickerActivity.kt
@@ -46,6 +46,7 @@ import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.Compani
import fr.free.nrw.commons.utils.DialogUtil
import fr.free.nrw.commons.utils.MapUtils.ZOOM_LEVEL
import fr.free.nrw.commons.utils.applyEdgeToEdgeBottomInsets
+import fr.free.nrw.commons.utils.applyEdgeToEdgeBottomPaddingInsets
import fr.free.nrw.commons.utils.applyEdgeToEdgeTopPaddingInsets
import fr.free.nrw.commons.utils.handleGeoCoordinates
import io.reactivex.android.schedulers.AndroidSchedulers
@@ -342,6 +343,10 @@ class LocationPickerActivity : BaseActivity(), LocationPermissionCallback {
}
private fun setupMapView() {
+
+ val mapBottomLayout: ConstraintLayout = findViewById(R.id.map_bottom_layout)
+ mapBottomLayout.applyEdgeToEdgeBottomPaddingInsets()
+
requestLocationPermissions()
//If location metadata is available, move map to that location.
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 d34c162dc..41e65ae4e 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
@@ -541,6 +541,7 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C
}
)
binding.progressBarEdit.visibility = View.GONE
+ binding.descriptionEdit.visibility = View.VISIBLE
}
override fun onConfigurationChanged(newConfig: Configuration) {
@@ -1026,12 +1027,12 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C
val message: String = if (result) {
context.getString(
R.string.send_thank_success_message,
- media!!.displayTitle
+ media!!.user
)
} else {
context.getString(
R.string.send_thank_failure_message,
- media!!.displayTitle
+ media!!.user
)
}
@@ -2128,22 +2129,17 @@ 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,
@@ -2157,11 +2153,8 @@ fun FileUsagesContainer(
MediaDetailViewModel.FileUsagesContainerState.Loading -> {
LinearProgressIndicator()
}
-
is MediaDetailViewModel.FileUsagesContainerState.Success -> {
-
val data = commonsContainerState.data
-
if (data.isNullOrEmpty()) {
ListItem(headlineContent = {
Text(
@@ -2181,7 +2174,7 @@ fun FileUsagesContainer(
headlineContent = {
Text(
modifier = Modifier.clickable {
- uriHandle.openUri(usage.link!!)
+ usage.link?.let { uriHandle.openUri(it) }
},
text = usage.title,
style = MaterialTheme.typography.titleSmall.copy(
@@ -2189,11 +2182,11 @@ fun FileUsagesContainer(
textDecoration = TextDecoration.Underline
)
)
- })
+ }
+ )
}
}
}
-
is MediaDetailViewModel.FileUsagesContainerState.Error -> {
ListItem(headlineContent = {
Text(
@@ -2203,12 +2196,10 @@ fun FileUsagesContainer(
)
})
}
-
MediaDetailViewModel.FileUsagesContainerState.Initial -> {}
}
}
-
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
@@ -2219,10 +2210,7 @@ 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,
@@ -2236,11 +2224,8 @@ fun FileUsagesContainer(
MediaDetailViewModel.FileUsagesContainerState.Loading -> {
LinearProgressIndicator()
}
-
is MediaDetailViewModel.FileUsagesContainerState.Success -> {
-
val data = globalContainerState.data
-
if (data.isNullOrEmpty()) {
ListItem(headlineContent = {
Text(
@@ -2259,16 +2244,20 @@ 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(
@@ -2278,10 +2267,8 @@ fun FileUsagesContainer(
)
})
}
-
MediaDetailViewModel.FileUsagesContainerState.Initial -> {}
}
}
-
}
-}
+}
\ No newline at end of file
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 db2c1f5d9..323f9756f 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 b5f760c9f..53e9970a6 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,6 +91,7 @@ public class NearbyFilterSearchRecyclerViewAdapter
label.setSelected(!label.isSelected());
holder.placeTypeLayout.setSelected(label.isSelected());
+ NearbyFilterState.setSelectedLabels(new ArrayList<>(selectedLabels));
callback.filterByMarkerType(selectedLabels, 0, false, false);
});
}
@@ -152,6 +153,7 @@ public class NearbyFilterSearchRecyclerViewAdapter
label.setSelected(false);
selectedLabels.remove(label);
}
+ NearbyFilterState.setSelectedLabels(new ArrayList<>(selectedLabels));
notifyDataSetChanged();
}
@@ -163,6 +165,7 @@ 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 d3ece9bfa..d0aec96af 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
Could not request category check for %1$s
Requesting category check for %1$s
Done
- Sending Thanks: Success
- Successfully sent thanks to %1$s
- Failed to send thanks %1$s
- Sending Thanks: Failure
+ Sending thanks: Success
+ Sent thanks to %1$s
+ Failed to send thanks to %1$s
+ Sending thanks: Failure
- Sending Thanks for %1$s
+ Sending thanks for %1$s
Does this follow the rules of copyright?
Is this correctly categorized?
Is this in-scope?
@@ -522,15 +529,14 @@ Upload your first media by tapping on the add button.
Error occurred while picking images
Please wait…
- Featured pictures are images from highly skilled photographers and illustrators that the Wikimedia Commons community has chosen as some of the highest quality on the site.
Images Uploaded via Nearby places are the images which are uploaded by discovering places on the map.
This feature allows editors to send a Thank you notification to users who make useful edits – by using a small thank link on the history page or diff page.
- Copy to subsequent media
+ Copy to the next items
Copied
Examples of good images to upload to Commons
Examples of images not to upload
Skip this image
- Download Failed!!. We cannot download the file without external storage permission.
+ Download failed. We cannot download the file without external storage permission.
Manage EXIF Tags
Select which EXIF tags to keep in uploads
@@ -543,12 +549,10 @@ Upload your first media by tapping on the add button.
Serial Numbers
Software
- Media location access denied
- We may not be able to automatically obtain location data from pictures you upload. Please add the appropriate location for each picture before submitting
-
Upload photos to Wikimedia Commons directly from your phone. Download the Commons App now: %1$s
Share app via...
Image Info
+ Don\'t show this message again
No Categories found
No Depictions found
Cancelled Upload
@@ -605,7 +609,7 @@ Upload your first media by tapping on the add button.
Share image via
You haven\'t made any contributions yet
- %s has not made any contributions yet
+ %1$s has not made any contributions yet
Account created!
Text copied to clipboard
Notification marked as read
@@ -614,7 +618,7 @@ Upload your first media by tapping on the add button.
Exists
Needs Photo
Place type:
- Bridge, museum, hotel etc.
+ Bridge, museum, hotel, etc.
Something went wrong with log-in. You must reset your password!
MEDIA
CHILD CLASSES
@@ -662,7 +666,7 @@ Upload your first media by tapping on the add button.
5. Paste the wikitext in the appropriate place.
6. Edit the wikitext for appropriate positioning, if necessary. For more information, see <a href="https://en.wikipedia.org/wiki/Wikipedia:Manual_of_Style/Images#How_to_place_an_image">here</a>.
7. Publish the article
- Copy wikicode to clipboard
+ Copy wikitext to clipboard
pause
resume
Paused
@@ -705,6 +709,8 @@ Upload your first media by tapping on the add button.
Please select the appropriate categories. Unlike depictions, categories are only in English.
Commons makes your pictures reusable and adapted by everyone. Do you want to waive all rights? Do you want to be attributed? Do you want adaptations to use the same license?
Depicts
+ Label
+ Description
Media License
Media Details
View category page
@@ -719,8 +725,7 @@ Upload your first media by tapping on the add button.
Show in map app
Edit location
The image view of the location picker
-
- The shadow of the image view of the location picker
+ The shadow of the image view of the location picker
Image Location
Check whether location is correct
Label
@@ -732,7 +737,7 @@ Upload your first media by tapping on the add button.
Back
Welcome to Custom Picture Selector
This picker shows you which pictures you have already uploaded to Commons.
- Unlike the picture on the left, the picture on the right has the Commons logo indicating it is already uploaded. \n Touch and hold for image preview.
+ Unlike the picture on the left, the picture on the right has the Commons logo indicating it is already uploaded.\n\nTouch and hold for image preview.
Awesome
This image has already been uploaded to Commons.
For technical reasons, the app can\'t reliably upload more than %1$d pictures at once. The upload limit of %1$d has been exceeded by %2$d.
@@ -748,9 +753,11 @@ Upload your first media by tapping on the add button.
Wiki Loves Monuments is an international photo contest for monuments organised by Wikimedia
Need Permission
Nearby maps need to read PHONE STATE to function properly
+ Please turn on location services to view nearby places.
+ Location access is needed to show nearby places on the map.
- Contributions of User: %s
- Achievements of User: %s
+ Contributions of User: %1$s
+ Achievements of User: %1$s
View user profile
Edit depictions
Edit categories
@@ -761,7 +768,7 @@ Upload your first media by tapping on the add button.
Location data helps Wiki editors find your picture, making it much more useful.\nYour recent uploads have no location.\nWe suggest you turn on location in your camera app\'s settings.\nThank you for uploading!
No location found
How about adding the place where this image was taken?\nLocation data helps Wiki editors find your picture, making it much more useful.\nThank you!
- Add location
+ Add Location
Please remove from this email any information that you are not comfortable sharing publicly. Also, please be aware that your email address with which you are posting, and the associated name and profile picture, will be visible publicly.
Details
Achievements are only available in the prod flavor. Please check the developer documentation.
@@ -782,8 +789,8 @@ Upload your first media by tapping on the add button.
Unmark as not for upload
Marking as not for upload
Unmarking as not for upload
- Show already actioned pictures
- Hiding already actioned pictures
+ Show already handled pictures
+ Hiding already handled pictures
No more images found
This image is already uploaded
Can not select this image for upload
@@ -819,15 +826,15 @@ Upload your first media by tapping on the add button.
Your log-in has expired. Please log in again.
No application available to open GPX files
File Saved Successfully
- Do you want to open GPX file?
- Do you want to open KML file?
- Failed to save KML file.
- Failed to save GPX file.
- Saving KML File
- Saving GPX File
+ Do you want to open the GPX file?
+ Do you want to open the KML file?
+ Failed to save the KML file.
+ Failed to save the GPX file.
+ Saving as a KML file...
+ Saving as a GPX file...
- - %d image selected
- - %d images selected
+ - %1$d image selected
+ - %1$d images selected
Please remember that all images in a multi-upload get the same categories and depictions. If the images do not share depictions and categories, please perform several separate uploads.
Note about multi-uploads
@@ -867,7 +874,6 @@ Upload your first media by tapping on the add button.
Other wikis
•
File usages
- SingleWebViewActivity
Account
Vanish Account
Vanish account warning
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
index fb3cb0ca1..3b7604026 100644
--- a/app/src/main/res/xml/preferences.xml
+++ b/app/src/main/res/xml/preferences.xml
@@ -55,13 +55,13 @@
android:defaultValue="false"
android:key="displayDeletionButton"
app:singleLineTitle="false"
- android:summary="Enable the "Delete folder" button in the custom picker"
- android:title="Show Deletion Button" />
+ android:summary="@string/show_deletion_button_explanation"
+ android:title="@string/show_deletion_button" />
+ android:title="@string/preferences_uploads">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/test/java/android/text/TextUtils.java b/app/src/test/java/android/text/TextUtils.java
index 4d63e77df..f59ab806a 100644
--- a/app/src/test/java/android/text/TextUtils.java
+++ b/app/src/test/java/android/text/TextUtils.java
@@ -21,14 +21,18 @@ public class TextUtils {
* mocks TextUtils.equals
*/
public static boolean equals(CharSequence a, CharSequence b) {
- if (a == b) return true;
+ if (a == b) {
+ return true;
+ }
int length;
if (a != null && b != null && (length = a.length()) == b.length()) {
if (a instanceof String && b instanceof String) {
return a.equals(b);
} else {
for (int i = 0; i < length; i++) {
- if (a.charAt(i) != b.charAt(i)) return false;
+ if (a.charAt(i) != b.charAt(i)) {
+ return false;
+ }
}
return true;
}
diff --git a/app/src/test/kotlin/fr/free/nrw/commons/OkHttpJsonApiClientTests.kt b/app/src/test/kotlin/fr/free/nrw/commons/OkHttpJsonApiClientTests.kt
index 9e7891560..1a9d1500b 100644
--- a/app/src/test/kotlin/fr/free/nrw/commons/OkHttpJsonApiClientTests.kt
+++ b/app/src/test/kotlin/fr/free/nrw/commons/OkHttpJsonApiClientTests.kt
@@ -3,6 +3,10 @@ package fr.free.nrw.commons
import com.google.gson.Gson
import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.verify
+import com.nhaarman.mockitokotlin2.times
+import fr.free.nrw.commons.campaigns.CampaignResponseDTO
+import fr.free.nrw.commons.campaigns.CampaignConfig
+import fr.free.nrw.commons.campaigns.models.Campaign
import fr.free.nrw.commons.explore.depictions.DepictsClient
import fr.free.nrw.commons.location.LatLng
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient
@@ -10,13 +14,22 @@ import fr.free.nrw.commons.nearby.model.NearbyQueryParams
import okhttp3.Call
import okhttp3.HttpUrl
import okhttp3.OkHttpClient
+import okhttp3.Request
import okhttp3.Response
+import okhttp3.ResponseBody
+import okhttp3.mockwebserver.MockResponse
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
import org.mockito.Mockito
-import org.mockito.Mockito.times
+import org.mockito.Mockito.eq
import org.mockito.MockitoAnnotations
+import java.io.BufferedReader
+import java.io.InputStreamReader
import java.lang.Exception
class OkHttpJsonApiClientTests {
@@ -45,34 +58,43 @@ class OkHttpJsonApiClientTests {
@Mock
lateinit var response: Response
+ @Mock
+ lateinit var responseBody: ResponseBody
+
+ private lateinit var mockWebServer: TestWebServer
+
@Before
fun setUp() {
MockitoAnnotations.openMocks(this)
- okHttpJsonApiClient =
- OkHttpJsonApiClient(
- okhttpClient,
- depictsClient,
- wikiMediaToolforgeUrl,
- sparqlQueryUrl,
- campaignsUrl,
- gson,
- )
+ mockWebServer = TestWebServer()
+ mockWebServer.setUp()
+ okHttpJsonApiClient = OkHttpJsonApiClient(
+ okhttpClient,
+ depictsClient,
+ wikiMediaToolforgeUrl,
+ sparqlQueryUrl,
+ mockWebServer.getUrl(), //use the mock server for the campaignsUrl
+ gson
+ )
Mockito.`when`(okhttpClient.newCall(any())).thenReturn(call)
Mockito.`when`(call.execute()).thenReturn(response)
+ Mockito.`when`(response.isSuccessful).thenReturn(false)
+ Mockito.`when`(response.message).thenReturn("test")
+ Mockito.`when`(response.body).thenReturn(responseBody)
+ Mockito.`when`(responseBody.string()).thenReturn("{\"error\": \"test\"}")
}
@Test
fun testGetNearbyPlacesCustomQuery() {
- Mockito.`when`(response.message).thenReturn("test")
try {
okHttpJsonApiClient.getNearbyPlaces(latLng, "test", 10.0, "test")
} catch (e: Exception) {
- assert(e.message.equals("test"))
+ assertEquals("test", e.message)
}
try {
okHttpJsonApiClient.getNearbyPlaces(NearbyQueryParams.Rectangular(latLng, latLng), "test", true, "test")
} catch (e: Exception) {
- assert(e.message.equals("test"))
+ assertEquals("test", e.message)
}
verify(okhttpClient, times(2)).newCall(any())
verify(call, times(2)).execute()
@@ -80,11 +102,10 @@ class OkHttpJsonApiClientTests {
@Test
fun testGetNearbyPlaces() {
- Mockito.`when`(response.message).thenReturn("test")
try {
okHttpJsonApiClient.getNearbyPlaces(latLng, "test", 10.0, null)
} catch (e: Exception) {
- assert(e.message.equals("test"))
+ assertEquals("test", e.message)
}
try {
okHttpJsonApiClient.getNearbyPlaces(
@@ -93,9 +114,8 @@ class OkHttpJsonApiClientTests {
true,
null
)
-
} catch (e: Exception) {
- assert(e.message.equals("test"))
+ assertEquals("test", e.message)
}
try {
okHttpJsonApiClient.getNearbyPlaces(
@@ -105,7 +125,7 @@ class OkHttpJsonApiClientTests {
null
)
} catch (e: Exception) {
- assert(e.message.equals("test"))
+ assertEquals("test", e.message)
}
verify(okhttpClient, times(3)).newCall(any())
verify(call, times(3)).execute()
@@ -113,18 +133,121 @@ class OkHttpJsonApiClientTests {
@Test
fun testGetNearbyItemCount() {
- Mockito.`when`(response.message).thenReturn("test")
try {
okHttpJsonApiClient.getNearbyItemCount(NearbyQueryParams.Radial(latLng, 10f))
} catch (e: Exception) {
- assert(e.message.equals("test"))
+ assertEquals("test", e.message)
}
try {
okHttpJsonApiClient.getNearbyItemCount(NearbyQueryParams.Rectangular(latLng, latLng))
} catch (e: Exception) {
- assert(e.message.equals("test"))
+ assertEquals("test", e.message)
}
verify(okhttpClient, times(2)).newCall(any())
verify(call, times(2)).execute()
}
-}
+
+ @Test
+ fun testGetCampaignsWithData() {
+ //loads the json response from resources
+ val jsonResponse = loadJsonFromResource("campaigns_response_with_data.json")
+
+ //mocks the succesfull response chain
+ Mockito.`when`(response.isSuccessful).thenReturn(true)
+ Mockito.`when`(response.message).thenReturn("OK")
+ Mockito.`when`(response.body).thenReturn(responseBody)
+ Mockito.`when`(responseBody.string()).thenReturn(jsonResponse)
+
+ val campaignResponse = CampaignResponseDTO().apply {
+ campaignConfig = CampaignConfig().apply {
+ showOnlyLiveCampaigns = false
+ sortBy = "startDate"
+ }
+ campaigns = listOf(
+ Campaign().apply {
+ title = "Wiki Loves Monuments"
+ isWLMCampaign = true
+ },
+ Campaign().apply {
+ title = "Wiki Loves Nature"
+ isWLMCampaign = false
+ }
+ )
+ }
+
+ //any() for the string argument and eq() for the class argument.
+ Mockito.`when`(
+ gson.fromJson(
+ any(),
+ eq(CampaignResponseDTO::class.java)
+ )
+ ).thenReturn(campaignResponse)
+
+ //call the getCampaigns
+ val result: CampaignResponseDTO? = okHttpJsonApiClient.getCampaigns().blockingGet()
+
+ //verify the results
+ assertNotNull(result)
+ assertNotNull(result?.campaigns)
+ assertEquals(2, result?.campaigns!!.size)
+ assertEquals("Wiki Loves Monuments", result.campaigns!![0].title)
+ assertTrue(result.campaigns!![0].isWLMCampaign)
+ assertEquals("Wiki Loves Nature", result.campaigns!![1].title)
+ assertEquals(false, result.campaigns!![1].isWLMCampaign)
+ assertNotNull(result.campaignConfig)
+ assertFalse(result.campaignConfig!!.showOnlyLiveCampaigns)
+ assertEquals("startDate", result.campaignConfig!!.sortBy)
+ }
+
+ @Test
+ fun testGetCampaignsEmpty() {
+ //loads the empty json response
+ val jsonResponse = loadJsonFromResource("campaigns_response_empty.json")
+
+ //mocks the successful response chain
+ Mockito.`when`(response.isSuccessful).thenReturn(true)
+ Mockito.`when`(response.message).thenReturn("OK")
+ Mockito.`when`(response.body).thenReturn(responseBody)
+ Mockito.`when`(responseBody.string()).thenReturn(jsonResponse)
+
+ val campaignResponse = CampaignResponseDTO().apply {
+ campaignConfig = CampaignConfig().apply {
+ showOnlyLiveCampaigns = false
+ sortBy = "startDate"
+ }
+ campaigns = emptyList()
+ }
+
+ //use any() for the string argument and eq() for the class argument.
+ Mockito.`when`(
+ gson.fromJson(
+ any(),
+ eq(CampaignResponseDTO::class.java)
+ )
+ ).thenReturn(campaignResponse)
+
+ //calls getCampaigns
+ val result: CampaignResponseDTO? = okHttpJsonApiClient.getCampaigns().blockingGet()
+
+ //verify the results
+ assertNotNull(result)
+ assertNotNull(result?.campaigns)
+ assertTrue(result?.campaigns!!.isEmpty())
+ assertNotNull(result.campaignConfig)
+ assertFalse(result.campaignConfig!!.showOnlyLiveCampaigns)
+ assertEquals("startDate", result.campaignConfig!!.sortBy)
+ }
+
+ fun loadJsonFromResource(fileName: String): String {
+ val resourcePath = "raw/$fileName"
+ //uses the classloader to find the resource in the test environment
+ val inputStream = javaClass.classLoader?.getResourceAsStream(resourcePath)
+
+ if (inputStream != null) {
+ //reads the entire stream content
+ return BufferedReader(InputStreamReader(inputStream)).use { it.readText() }
+ }
+ //throws an exception with the correct expected path
+ throw IllegalArgumentException("Resource $fileName not found. Please ensure the file is located in app/src/test/resources/raw/")
+ }
+}
\ No newline at end of file
diff --git a/app/src/test/kotlin/fr/free/nrw/commons/description/DescriptionEditActivityUnitTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/description/DescriptionEditActivityUnitTest.kt
index be3b7e8e3..5b5dfd7dd 100644
--- a/app/src/test/kotlin/fr/free/nrw/commons/description/DescriptionEditActivityUnitTest.kt
+++ b/app/src/test/kotlin/fr/free/nrw/commons/description/DescriptionEditActivityUnitTest.kt
@@ -193,8 +193,8 @@ class DescriptionEditActivityUnitTest {
method.isAccessible = true
method.invoke(
activity,
- android.R.string.ok,
- android.R.string.ok,
+ R.string.ok,
+ R.string.ok,
)
val dialog: AlertDialog = ShadowAlertDialog.getLatestDialog() as AlertDialog
assertEquals(dialog.isShowing, true)
diff --git a/app/src/test/kotlin/fr/free/nrw/commons/upload/categories/UploadCategoriesFragmentUnitTests.kt b/app/src/test/kotlin/fr/free/nrw/commons/upload/categories/UploadCategoriesFragmentUnitTests.kt
index 75d6b8a4f..55b8427e6 100644
--- a/app/src/test/kotlin/fr/free/nrw/commons/upload/categories/UploadCategoriesFragmentUnitTests.kt
+++ b/app/src/test/kotlin/fr/free/nrw/commons/upload/categories/UploadCategoriesFragmentUnitTests.kt
@@ -153,6 +153,13 @@ class UploadCategoriesFragmentUnitTests {
fragment.showError(R.string.no_categories_found)
}
+ @Test
+ @Throws(Exception::class)
+ fun testShowErrorDialog() {
+ Shadows.shadowOf(Looper.getMainLooper()).idle()
+ fragment.showErrorDialog("")
+ }
+
@Test
@Throws(Exception::class)
fun testSetCategoriesCaseNull() {
diff --git a/app/src/test/kotlin/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragmentUnitTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragmentUnitTest.kt
index a37bcc927..0cab47c67 100644
--- a/app/src/test/kotlin/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragmentUnitTest.kt
+++ b/app/src/test/kotlin/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragmentUnitTest.kt
@@ -145,6 +145,8 @@ class UploadMediaDetailFragmentUnitTest {
ibExpandCollapse = view.findViewById(R.id.ib_expand_collapse)
Whitebox.setInternalState(fragment, "uploadMediaDetailAdapter", uploadMediaDetailAdapter)
+ Whitebox.setInternalState(fragment, "defaultKvStore", defaultKvStore)
+ `when`(defaultKvStore.getString("description_language", "")).thenReturn("en")
}
@Test
@@ -160,16 +162,10 @@ class UploadMediaDetailFragmentUnitTest {
fragment.onCreate(Bundle())
}
- @Test
- @Throws(Exception::class)
- fun testSetImageToBeUploaded() {
- Shadows.shadowOf(Looper.getMainLooper()).idle()
- fragment.setImageToBeUploaded(null, null, location)
- }
-
@Test
@Throws(Exception::class)
fun testOnCreateView() {
+ Shadows.shadowOf(Looper.getMainLooper()).idle()
fragment.onCreateView(layoutInflater, null, savedInstanceState)
}
@@ -181,34 +177,6 @@ class UploadMediaDetailFragmentUnitTest {
fragment.onViewCreated(view, savedInstanceState)
}
- @Test
- @Throws(Exception::class)
- fun testInit() {
- Shadows.shadowOf(Looper.getMainLooper()).idle()
- Whitebox.setInternalState(fragment, "presenter", presenter)
- val method: Method =
- UploadMediaDetailFragment::class.java.getDeclaredMethod(
- "initializeFragment",
- )
- method.isAccessible = true
- method.invoke(fragment)
- }
-
- @Test
- @Throws(Exception::class)
- fun testInitCaseGetIndexInViewFlipperNonZero() {
- Shadows.shadowOf(Looper.getMainLooper()).idle()
- Whitebox.setInternalState(fragment, "presenter", presenter)
- `when`(callback.getIndexInViewFlipper(fragment)).thenReturn(1)
- `when`(callback.totalNumberOfSteps).thenReturn(5)
- val method: Method =
- UploadMediaDetailFragment::class.java.getDeclaredMethod(
- "initializeFragment",
- )
- method.isAccessible = true
- method.invoke(fragment)
- }
-
@Test
@Throws(Exception::class)
fun testShowInfoAlert() {
@@ -317,7 +285,7 @@ class UploadMediaDetailFragmentUnitTest {
@Test
@Throws(Exception::class)
fun testShowExternalMap() {
- shadowOf(Looper.getMainLooper()).idle()
+ Shadows.shadowOf(Looper.getMainLooper()).idle()
`when`(uploadItem.gpsCoords).thenReturn(imageCoordinates)
`when`(imageCoordinates.decLatitude).thenReturn(0.0)
`when`(imageCoordinates.decLongitude).thenReturn(0.0)
@@ -328,7 +296,7 @@ class UploadMediaDetailFragmentUnitTest {
@Test
@Throws(Exception::class)
fun testOnCameraPositionCallbackOnMapIconClicked() {
- shadowOf(Looper.getMainLooper()).idle()
+ Shadows.shadowOf(Looper.getMainLooper()).idle()
Mockito.mock(LocationPicker::class.java)
val intent = Mockito.mock(Intent::class.java)
val cameraPosition = Mockito.mock(CameraPosition::class.java)
@@ -357,7 +325,7 @@ class UploadMediaDetailFragmentUnitTest {
@Test
@Throws(Exception::class)
fun testOnCameraPositionCallbackAddLocationDialog() {
- shadowOf(Looper.getMainLooper()).idle()
+ Shadows.shadowOf(Looper.getMainLooper()).idle()
Mockito.mock(LocationPicker::class.java)
val intent = Mockito.mock(Intent::class.java)
val cameraPosition = Mockito.mock(CameraPosition::class.java)
@@ -427,7 +395,7 @@ class UploadMediaDetailFragmentUnitTest {
@Test
@Throws(Exception::class)
fun testRememberedZoomLevelOnNull() {
- shadowOf(Looper.getMainLooper()).idle()
+ Shadows.shadowOf(Looper.getMainLooper()).idle()
Whitebox.setInternalState(fragment, "defaultKvStore", defaultKvStore)
`when`(uploadItem.gpsCoords).thenReturn(null)
`when`(defaultKvStore.getString(LAST_ZOOM)).thenReturn("13.0")
@@ -443,7 +411,7 @@ class UploadMediaDetailFragmentUnitTest {
@Test
@Throws(Exception::class)
fun testRememberedZoomLevelOnNotNull() {
- shadowOf(Looper.getMainLooper()).idle()
+ Shadows.shadowOf(Looper.getMainLooper()).idle()
`when`(uploadItem.gpsCoords).thenReturn(imageCoordinates)
`when`(imageCoordinates.decLatitude).thenReturn(8.0)
`when`(imageCoordinates.decLongitude).thenReturn(-8.0)
@@ -456,4 +424,4 @@ class UploadMediaDetailFragmentUnitTest {
val shadowIntent: ShadowIntent = shadowOf(startedIntent)
Assert.assertEquals(shadowIntent.intentClass, LocationPickerActivity::class.java)
}
-}
+}
\ No newline at end of file
diff --git a/app/src/test/resources/raw/campaigns_response_empty.json b/app/src/test/resources/raw/campaigns_response_empty.json
new file mode 100644
index 000000000..61903e24c
--- /dev/null
+++ b/app/src/test/resources/raw/campaigns_response_empty.json
@@ -0,0 +1,7 @@
+{
+ "config": {
+ "showOnlyLiveCampaigns": false,
+ "sortBy": "startDate"
+ },
+ "campaigns": []
+}
\ No newline at end of file
diff --git a/app/src/test/resources/raw/campaigns_response_with_data.json b/app/src/test/resources/raw/campaigns_response_with_data.json
new file mode 100644
index 000000000..dab818e2a
--- /dev/null
+++ b/app/src/test/resources/raw/campaigns_response_with_data.json
@@ -0,0 +1,24 @@
+{
+ "config": {
+ "showOnlyLiveCampaigns": false,
+ "sortBy": "startDate"
+ },
+ "campaigns": [
+ {
+ "title": "Wiki Loves Monuments",
+ "description": "A campaign to photograph monuments",
+ "startDate": "2025-09-01",
+ "endDate": "2025-09-30",
+ "link": "https://commons.wikimedia.org/wiki/Campaign:Wiki_Loves_Monuments",
+ "isWLMCampaign": true
+ },
+ {
+ "title": "Wiki Loves Nature",
+ "description": "A campaign to photograph nature",
+ "startDate": "2025-06-01",
+ "endDate": "2025-06-30",
+ "link": "https://commons.wikimedia.org/wiki/Campaign:Wiki_Loves_Nature",
+ "isWLMCampaign": false
+ }
+ ]
+}
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index d26b1a62c..9a4dd53cb 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,10 +1,10 @@
[versions]
-agp = "8.12.0"
+agp = "8.13.0"
acra = "5.8.4"
activityCompose = "1.9.3"
adapterdelegates = "4.3.0"
androidmediautil = "v1.0-1"
-androidSdkVersion = "10.0.1"
+androidSdkVersion = "11.13.5"
androidPluginScalebar = "1.0.0"
androidxJunitVersion = "1.1.5"
annotation = "1.3.0"
@@ -27,7 +27,7 @@ dexterVersion = "5.0.0"
espresso = "3.6.1"
exifinterface = "1.3.7"
fragmentTesting = "1.6.2"
-frescoVersion = "1.13.0"
+frescoVersion = "3.6.0"
commonsLang3Version = "3.8.1"
glide = "4.12.0"
gson = "2.8.5"
@@ -126,6 +126,7 @@ dagger-compiler = { module = "com.google.dagger:dagger-compiler", version.ref =
# Image loading
facebook-fresco = { module = "com.facebook.fresco:fresco", version.ref = "frescoVersion" }
+facebook-fresco-middleware = { module = "com.facebook.fresco:middleware", version.ref = "frescoVersion" }
glide-compiler = { module = "com.github.bumptech.glide:compiler", version.ref = "glide" }
glide = { module = "com.github.bumptech.glide:glide", version.ref = "glide" }
kotlinx-coroutines-rx2 = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-rx2", version.ref = "kotlinxCoroutinesRx2" }
diff --git a/update-license-info/Makefile b/update-license-info/Makefile
deleted file mode 100644
index a6c96ee2a..000000000
--- a/update-license-info/Makefile
+++ /dev/null
@@ -1,14 +0,0 @@
-.FAKE : build update clean install
-
-build : ../app/src/main/res/xml/wikimedia_licenses.xml
-
-../app/src/main/res/xml/wikimedia_licenses.xml : licenses.php mediawiki-extensions-UploadWizard
- php licenses.php > ../app/src/main/res/xml/wikimedia_licenses.xml
-
-mediawiki-extensions-UploadWizard : update
-
-update :
- if [ -d mediawiki-extensions-UploadWizard ]; then (cd mediawiki-extensions-UploadWizard && git pull origin master); else git clone https://gerrit.wikimedia.org/r/mediawiki/extensions/UploadWizard mediawiki-extensions-UploadWizard; fi
-
-clean :
- rm -rf mediawiki-extensions-UploadWizard
diff --git a/update-license-info/include-stubs.php b/update-license-info/include-stubs.php
deleted file mode 100644
index c0a01a0d6..000000000
--- a/update-license-info/include-stubs.php
+++ /dev/null
@@ -1,68 +0,0 @@
- 'English' );
- }
-}
-$wgMemc = new FakeMemc();
-
-class FakeMessage {
- function plain() {
- return 'stub-message-plain';
- }
- function parse() {
- return 'stub-message-parsed';
- }
-}
-
-function wfMessage() {
- return new FakeMessage();
-}
-
-/**
- * Converts shorthand byte notation to integer form
- *
- * @param $string String
- * @return Integer
- */
-function wfShorthandToInteger( $string = '' ) {
- $string = trim( $string );
- if ( $string === '' ) {
- return -1;
- }
- $last = $string[strlen( $string ) - 1];
- $val = intval( $string );
- switch ( $last ) {
- case 'g':
- case 'G':
- $val *= 1024;
- // break intentionally missing
- case 'm':
- case 'M':
- $val *= 1024;
- // break intentionally missing
- case 'k':
- case 'K':
- $val *= 1024;
- }
-
- return $val;
-}
-
-$wgAPIModules = array();
diff --git a/update-license-info/licenses.php b/update-license-info/licenses.php
deleted file mode 100644
index badda1a08..000000000
--- a/update-license-info/licenses.php
+++ /dev/null
@@ -1,71 +0,0 @@
-
-// 2013-09-30
-
-require 'include-stubs.php';
-$config = require "mediawiki-extensions-UploadWizard/UploadWizard.config.php";
-require "mediawiki-extensions-UploadWizard/UploadWizard.i18n.php";
-$licenseList = array();
-
-foreach ( $config['licenses'] as $key => $license ) {
- // Determine template -> license mappings
- if ( isset( $license['templates'] ) ) {
- $templates = $license['templates'];
- } else {
- $templates = array( $key );
- }
-
- if ( count( $templates ) < 1 ) {
- throw new Exception("No templates for $key, this is wrong.");
- }
- if ( count( $templates ) > 1 ) {
- //echo "Skipping multi-template license: $key\n";
- continue;
- }
- $template = $templates[0];
- if ( preg_match( '/^subst:/i', $template ) ) {
- //echo "Skipping subst license: $key\n";
- continue;
- }
-
- $msg = $messages['en'][$license['msg']];
-
- $licenseInfo = array(
- 'desc' => $msg,
- 'template' => $template
- );
- if ( isset( $license['url'] ) ) {
- $url = $license['url'];
- if ( substr( $url, 0, 2 ) == '//' ) {
- $url = 'https:' . $url;
- }
- if ( isset( $license['languageCodePrefix'] ) ) {
- $url .= $license['languageCodePrefix'] . '$lang';
- }
- $licenseInfo['url'] = $url;
- }
- $licenseList[$key] = $licenseInfo;
-}
-
-//var_dump( $licenseList );
-
-echo "\n";
-echo "\n";
-foreach( $licenseList as $key => $licenseInfo ) {
- $encId = htmlspecialchars( $key );
- echo " \n";
-
-}
-echo "\n";
-
\ No newline at end of file