Skip to content

Commit 6d17621

Browse files
Merge remote-tracking branch 'origin/main'
2 parents d4de3cd + 50c900e commit 6d17621

27 files changed

+614
-318
lines changed

CHANGELOG.md

+84
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,89 @@
11
# Wikimedia Commons for Android
22

3+
## v5.1.2
4+
5+
### What's changed
6+
7+
* Fix the broken category search in the explore screen
8+
9+
## v5.1.1
10+
11+
### What's changed
12+
13+
* Use Android's new EXIF interface to mitigate security issues in old
14+
EXIF interface.
15+
* Make the icon that helps view the upload queue always visible as it ensures
16+
that the queue accessible at all times.
17+
18+
## v5.1.0
19+
20+
### What's Changed
21+
22+
* Enhanced **upload queue management** in the Commons app for smoother, sequential
23+
processing, clearer progress tracking, prevention of stuck or duplicate
24+
uploads. As part of this improvement, the "Limited Connection mode" has been
25+
removed.
26+
* Added an option in "Nearby" feature enabling users to **provide feedback on
27+
Wikidata items**. Users can report if an item doesn’t exist, is at a different
28+
location, or has other issues, with submissions tagged for easy tracking and
29+
updates.
30+
* Improved the "Nearby" feature by splitting the query into two parts for faster
31+
loading and **better performance, especially in areas with dense amount of
32+
places**. This update also resolves issues with pins overlapping place names.
33+
* Upgraded AGP and **target/compile SDK to 34** and make necessary adjustments to
34+
the app such as adding **"Partial Access" support**. Also includes some minor
35+
refactoring, and replacement of deprecated circular progress bars.
36+
* Fixed an **UI issue where the 'Subcategories' and 'Parent Categories' tabs
37+
appeared blank** in the Category Details screen. Resolved by optimizing view
38+
binding handling in the parent fragments.
39+
* Fixed an issue where editing depictions removed all other structured data from
40+
images. Now, **only depictions are updated, preserving other associated data**.
41+
* Fixed **map centering** in the image upload flow to **use GPS EXIF tag location**
42+
from pictures and ensured "Show in map app" accurately reflects this location.
43+
* Fixed navigation **after uploading via Nearby by directing users to the Uploads
44+
activity** instead of returning to Nearby, preventing confusion about needing to
45+
upload again.
46+
47+
### Bug fixes and various changes
48+
49+
* Improved the "Nearby" feature to fetch labels based on the user's preferred
50+
language instead of defaulting to English.
51+
* Added a legend to the "Nearby" feature indicating pin statuses: red for items
52+
without pictures, green for those with pictures, and grey for items being
53+
checked. A floating action button now allows users to toggle the legend's
54+
visibility.
55+
* Fixed an issue where the "Nominate for deletion" option is shown to logged out
56+
users, preventing app errors and crashes.
57+
* Updated the regex pattern that filters categories with an year in it to also
58+
filter the 2020s.
59+
* Fix an issue where past depictions were not shown as suggestions, despite
60+
being saved correctly.
61+
* Fixed an issue in custom image picker where exiting the media preview showed
62+
only the first image and cleared selections. Now, previously selected images
63+
are restored correctly after exiting the preview. This was contributed.
64+
* Fixed an issue in custom image picker where scrolling behavior did not
65+
maintain position after exiting fullscreen preview, ensuring users remain at
66+
the same point in their image roll unless actioned images are filtered. This
67+
was contributed.
68+
* Fixed Nearby map not showing new pins on map move by removing the 2000m scroll
69+
threshold and adding an 800ms debounce for smoother pin updates when the map
70+
is moved. Queued searches are now canceled on fragment destruction.
71+
* Revised author information retrieval to emphasize the custom author name from
72+
the metadata instead of the default registered username.
73+
* Enhanced notification classification to properly identify "email" type
74+
notifications and prompting users to check their e-mail inbox when such
75+
notifications are clicked.
76+
* Resolved a bug in the language chooser that incorrectly greyed-out previously
77+
selected languages, ensuring only the current language is non-selectable during
78+
image upload.
79+
* Resolved pin color update issue in "Nearby" feature where the pin colour
80+
failed to be updated after a successful image upload.
81+
82+
What's listed here is only a subset of all the changes. Check the full-list of
83+
the changes in [this link](https://github.com/commons-app/apps-android-commons/compare/v5.0.2...v5.1.0).
84+
Alternatively, checkout [this release on GitHub releases page](https://github.com/commons-app/apps-android-commons/releases/tag/v5.1.0)
85+
for an exhaustive list of changes and the various contributors who contributed the same.
86+
387
## v5.0.2
488

589
- Enhanced multi-upload functionality with user prompts to clarify that all images would share the

app/build.gradle

+2-2
Original file line numberDiff line numberDiff line change
@@ -212,8 +212,8 @@ android {
212212
defaultConfig {
213213
//applicationId 'fr.free.nrw.commons'
214214

215-
versionCode 1040
216-
versionName '5.0.2'
215+
versionCode 1043
216+
versionName '5.1.2'
217217
setProperty("archivesBaseName", "app-commons-v$versionName-" + getBranchName())
218218

219219
minSdkVersion 21

app/src/main/AndroidManifest.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -262,4 +262,4 @@
262262
android:required="false" />
263263
</application>
264264

265-
</manifest>
265+
</manifest>

app/src/main/java/fr/free/nrw/commons/activity/SingleWebViewActivity.kt

+28
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import android.content.Context
55
import android.content.Intent
66
import android.os.Bundle
77
import android.webkit.ConsoleMessage
8+
import android.webkit.CookieManager
89
import android.webkit.WebChromeClient
910
import android.webkit.WebResourceRequest
1011
import android.webkit.WebView
@@ -28,13 +29,20 @@ import androidx.compose.runtime.remember
2829
import androidx.compose.ui.Modifier
2930
import androidx.compose.ui.viewinterop.AndroidView
3031
import fr.free.nrw.commons.R
32+
import fr.free.nrw.commons.di.ApplicationlessInjection
33+
import fr.free.nrw.commons.wikidata.cookies.CommonsCookieJar
34+
import okhttp3.HttpUrl.Companion.toHttpUrl
3135
import timber.log.Timber
36+
import javax.inject.Inject
3237

3338
/**
3439
* SingleWebViewActivity is a reusable activity webView based on a given url(initial url) and
3540
* closes itself when a specified success URL is reached to success url.
3641
*/
3742
class SingleWebViewActivity : ComponentActivity() {
43+
@Inject
44+
lateinit var cookieJar: CommonsCookieJar
45+
3846
@OptIn(ExperimentalMaterial3Api::class)
3947
override fun onCreate(savedInstanceState: Bundle?) {
4048
super.onCreate(savedInstanceState)
@@ -44,6 +52,11 @@ class SingleWebViewActivity : ComponentActivity() {
4452
finish()
4553
return
4654
}
55+
ApplicationlessInjection
56+
.getInstance(applicationContext)
57+
.commonsApplicationComponent
58+
.inject(this)
59+
setCookies(url)
4760
enableEdgeToEdge()
4861
setContent {
4962
Scaffold(
@@ -131,6 +144,7 @@ class SingleWebViewActivity : ComponentActivity() {
131144

132145
override fun onPageFinished(view: WebView?, url: String?) {
133146
super.onPageFinished(view, url)
147+
setCookies(url.orEmpty())
134148
}
135149

136150
}
@@ -152,6 +166,20 @@ class SingleWebViewActivity : ComponentActivity() {
152166

153167
}
154168

169+
/**
170+
* Sets cookies for the given URL using the cookies stored in the `CommonsCookieJar`.
171+
*
172+
* @param url The URL for which cookies need to be set.
173+
*/
174+
private fun setCookies(url: String) {
175+
CookieManager.getInstance().let {
176+
val cookies = cookieJar.loadForRequest(url.toHttpUrl())
177+
for (cookie in cookies) {
178+
it.setCookie(url, cookie.toString())
179+
}
180+
}
181+
}
182+
155183
companion object {
156184
private const val VANISH_ACCOUNT_URL = "VanishAccountUrl"
157185
private const val VANISH_ACCOUNT_SUCCESS_URL = "vanishAccountSuccessUrl"

app/src/main/java/fr/free/nrw/commons/category/CategoriesModel.kt

+54-20
Original file line numberDiff line numberDiff line change
@@ -127,30 +127,64 @@ class CategoriesModel
127127
/**
128128
* Fetches details of every category associated with selected depictions, converts them into
129129
* CategoryItem and returns them in a list.
130+
* If a selected depiction has no categories, the categories in which its P18 belongs are
131+
* returned in the list.
130132
*
131133
* @param selectedDepictions selected DepictItems
132134
* @return List of CategoryItem associated with selected depictions
133135
*/
134-
private fun categoriesFromDepiction(selectedDepictions: List<DepictedItem>): Observable<MutableList<CategoryItem>>? =
135-
Observable
136-
.fromIterable(
137-
selectedDepictions.map { it.commonsCategories }.flatten(),
138-
).map { categoryItem ->
139-
categoryClient
140-
.getCategoriesByName(
141-
categoryItem.name,
142-
categoryItem.name,
143-
SEARCH_CATS_LIMIT,
144-
).map {
145-
CategoryItem(
146-
it[0].name,
147-
it[0].description,
148-
it[0].thumbnail,
149-
it[0].isSelected,
150-
)
151-
}.blockingGet()
152-
}.toList()
153-
.toObservable()
136+
private fun categoriesFromDepiction(selectedDepictions: List<DepictedItem>): Observable<MutableList<CategoryItem>>? {
137+
val observables = selectedDepictions.map { depictedItem ->
138+
if (depictedItem.commonsCategories.isEmpty()) {
139+
if (depictedItem.primaryImage == null) {
140+
return@map Observable.just(emptyList<CategoryItem>())
141+
}
142+
Observable.just(
143+
depictedItem.primaryImage
144+
).map { image ->
145+
categoryClient
146+
.getCategoriesOfImage(
147+
image,
148+
SEARCH_CATS_LIMIT,
149+
).map {
150+
it.map { category ->
151+
CategoryItem(
152+
category.name,
153+
category.description,
154+
category.thumbnail,
155+
category.isSelected,
156+
)
157+
}
158+
}.blockingGet()
159+
}.flatMapIterable { it }.toList()
160+
.toObservable()
161+
} else {
162+
Observable
163+
.fromIterable(
164+
depictedItem.commonsCategories,
165+
).map { categoryItem ->
166+
categoryClient
167+
.getCategoriesByName(
168+
categoryItem.name,
169+
categoryItem.name,
170+
SEARCH_CATS_LIMIT,
171+
).map {
172+
CategoryItem(
173+
it[0].name,
174+
it[0].description,
175+
it[0].thumbnail,
176+
it[0].isSelected,
177+
)
178+
}.blockingGet()
179+
}.toList()
180+
.toObservable()
181+
}
182+
}
183+
return Observable.concat(observables)
184+
.scan(mutableListOf<CategoryItem>()) { accumulator, currentList ->
185+
accumulator.apply { addAll(currentList) }
186+
}
187+
}
154188

155189
/**
156190
* Fetches details of every category by their name, converts them into

app/src/main/java/fr/free/nrw/commons/category/CategoryClient.kt

+18
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,24 @@ class CategoryClient
7878
),
7979
)
8080

81+
/**
82+
* Fetches categories belonging to an image (P18 of some wikidata entity).
83+
*
84+
* @param image P18 of some wikidata entity
85+
* @param itemLimit How many categories to return
86+
* @return Single Observable emitting the list of categories
87+
*/
88+
fun getCategoriesOfImage(
89+
image: String,
90+
itemLimit: Int,
91+
): Single<List<CategoryItem>> =
92+
responseMapper(
93+
categoryInterface.getCategoriesByTitles(
94+
"File:${image}",
95+
itemLimit,
96+
),
97+
)
98+
8199
/**
82100
* The method takes categoryName as input and returns a List of Subcategories
83101
* It uses the generator query API to get the subcategories in a category, 500 at a time.

app/src/main/java/fr/free/nrw/commons/category/CategoryInterface.kt

+15
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,21 @@ interface CategoryInterface {
6161
@Query("gacoffset") offset: Int,
6262
): Single<MwQueryResponse>
6363

64+
/**
65+
* Fetches non-hidden categories by titles.
66+
*
67+
* @param titles titles to fetch categories for (e.g. File:<P18 of a wikidata entity>)
68+
* @param itemLimit How many categories to return
69+
* @return MwQueryResponse
70+
*/
71+
@GET(
72+
"w/api.php?action=query&format=json&formatversion=2&generator=categories&prop=categoryinfo|description|pageimages&piprop=thumbnail&pithumbsize=70&gclshow=!hidden",
73+
)
74+
fun getCategoriesByTitles(
75+
@Query("titles") titles: String?,
76+
@Query("gcllimit") itemLimit: Int,
77+
): Single<MwQueryResponse>
78+
6479
@GET("w/api.php?action=query&format=json&formatversion=2&generator=categorymembers&gcmtype=subcat&prop=info&gcmlimit=50")
6580
fun getSubCategoryList(
6681
@Query("gcmtitle") categoryName: String,

app/src/main/java/fr/free/nrw/commons/contributions/Contribution.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ data class Contribution constructor(
101101
*/
102102
fun formatCaptions(uploadMediaDetails: List<UploadMediaDetail>) =
103103
uploadMediaDetails
104-
.associate { it.languageCode!! to it.captionText!! }
104+
.associate { it.languageCode!! to it.captionText }
105105
.filter { it.value.isNotBlank() }
106106

107107
/**

app/src/main/java/fr/free/nrw/commons/description/DescriptionEditActivity.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -267,11 +267,11 @@ class DescriptionEditActivity :
267267
applicationContext,
268268
media,
269269
mediaDetail.languageCode!!,
270-
mediaDetail.captionText!!,
270+
mediaDetail.captionText,
271271
).subscribeOn(Schedulers.io())
272272
.observeOn(AndroidSchedulers.mainThread())
273273
.subscribe { s: Boolean? ->
274-
updatedCaptions[mediaDetail.languageCode!!] = mediaDetail.captionText!!
274+
updatedCaptions[mediaDetail.languageCode!!] = mediaDetail.captionText
275275
media.captions = updatedCaptions
276276
Timber.d("Caption is added.")
277277
},

app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationComponent.kt

+3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import dagger.android.AndroidInjectionModule
66
import dagger.android.AndroidInjector
77
import dagger.android.support.AndroidSupportInjectionModule
88
import fr.free.nrw.commons.CommonsApplication
9+
import fr.free.nrw.commons.activity.SingleWebViewActivity
910
import fr.free.nrw.commons.auth.LoginActivity
1011
import fr.free.nrw.commons.contributions.ContributionsModule
1112
import fr.free.nrw.commons.explore.SearchModule
@@ -51,6 +52,8 @@ interface CommonsApplicationComponent : AndroidInjector<ApplicationlessInjection
5152

5253
fun inject(activity: LoginActivity)
5354

55+
fun inject(activity: SingleWebViewActivity)
56+
5457
fun inject(fragment: SettingsFragment)
5558

5659
fun inject(fragment: MoreBottomSheetFragment)

0 commit comments

Comments
 (0)