Skip to content

Commit 1fbbc07

Browse files
committed
Draft for MR 1
1 parent 4c9637c commit 1fbbc07

File tree

10 files changed

+326
-10
lines changed

10 files changed

+326
-10
lines changed

app/src/main/java/fr/free/nrw/commons/mwapi/OkHttpJsonApiClient.kt

+54-7
Original file line numberDiff line numberDiff line change
@@ -388,19 +388,65 @@ class OkHttpJsonApiClient @Inject constructor(
388388

389389
@Throws(IOException::class)
390390
fun getPlaces(
391-
placeList: List<Place>, language: String
391+
placeList: List<Place>, primaryLanguage: String, secondaryLanguages: String
392392
): List<Place>? {
393393
val wikidataQuery = FileUtils.readFromResource("/queries/query_for_item.rq")
394+
395+
// Split the secondary languages string into an array to use in fallback queries
396+
val secondaryLanguagesArray = secondaryLanguages.split(",\\s*".toRegex())
397+
398+
// Prepare the Wikidata entity IDs (QIDs) for each place in the list
394399
var qids = ""
395400
for (place in placeList) {
396-
qids += """
397-
${"wd:" + place.wikiDataEntityId}"""
401+
qids += "\nwd:${place.wikiDataEntityId}"
402+
}
403+
404+
// Build fallback descriptions for secondary languages in case the primary language is unavailable
405+
val fallBackDescription = StringBuilder()
406+
secondaryLanguagesArray.forEachIndexed { index, lang ->
407+
fallBackDescription.append("OPTIONAL {?item schema:description ?itemDescriptionPreferredLanguage_")
408+
.append(index + 1)
409+
.append(". FILTER (lang(?itemDescriptionPreferredLanguage_")
410+
.append(index + 1)
411+
.append(") = \"")
412+
.append(lang)
413+
.append("\")}\n")
414+
}
415+
416+
// Build fallback labels for secondary languages
417+
val fallbackLabel = StringBuilder()
418+
secondaryLanguagesArray.forEachIndexed { index, lang ->
419+
fallbackLabel.append("OPTIONAL {?item rdfs:label ?itemLabelPreferredLanguage_")
420+
.append(index + 1)
421+
.append(". FILTER (lang(?itemLabelPreferredLanguage_")
422+
.append(index + 1)
423+
.append(") = \"")
424+
.append(lang)
425+
.append("\")}\n")
426+
}
427+
428+
// Build fallback class labels for secondary languages
429+
val fallbackClassLabel = StringBuilder()
430+
secondaryLanguagesArray.forEachIndexed { index, lang ->
431+
fallbackClassLabel.append("OPTIONAL {?class rdfs:label ?classLabelPreferredLanguage_")
432+
.append(index + 1)
433+
.append(". FILTER (lang(?classLabelPreferredLanguage_")
434+
.append(index + 1)
435+
.append(") = \"")
436+
.append(lang)
437+
.append("\")}\n")
398438
}
439+
440+
// Replace placeholders in the query with actual data: QIDs, language codes, and fallback options
399441
val query = wikidataQuery
400442
.replace("\${ENTITY}", qids)
401-
.replace("\${LANG}", language)
402-
val urlBuilder: HttpUrl.Builder = sparqlQueryUrl.toHttpUrlOrNull()!!
403-
.newBuilder()
443+
.replace("\${LANG}", primaryLanguage)
444+
.replace("\${SECONDARYDESCRIPTION}", fallBackDescription.toString())
445+
.replace("\${SECONDARYLABEL}", fallbackLabel.toString())
446+
.replace("\${SECONDARYCLASSLABEL}", fallbackClassLabel.toString())
447+
448+
// Build the URL for the SparQL query with the formatted query string
449+
val urlBuilder = sparqlQueryUrl.toHttpUrlOrNull()!!.newBuilder()
404450
.addQueryParameter("query", query)
405451
.addQueryParameter("format", "json")
406452

@@ -418,11 +464,12 @@ ${"wd:" + place.wikiDataEntityId}"""
418464
}
419465
return places
420466
} else {
421-
throw IOException("Unexpected response code: " + response.code)
467+
throw IOException("Unexpected response code: ${response.code}")
422468
}
423469
}
424470
}
425471

472+
426473
@Throws(Exception::class)
427474
fun getPlacesAsKML(leftLatLng: LatLng, rightLatLng: LatLng): String? {
428475
var kmlString = """<?xml version="1.0" encoding="UTF-8" standalone="no"?>

app/src/main/java/fr/free/nrw/commons/nearby/NearbyController.java

+9-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
import androidx.annotation.Nullable;
88
import fr.free.nrw.commons.BaseMarker;
99
import fr.free.nrw.commons.MapController;
10+
import fr.free.nrw.commons.kvstore.JsonKvStore;
1011
import fr.free.nrw.commons.location.LatLng;
12+
import fr.free.nrw.commons.settings.Prefs;
1113
import java.util.ArrayList;
1214
import java.util.Collections;
1315
import java.util.HashMap;
@@ -16,6 +18,7 @@
1618
import java.util.Locale;
1719
import java.util.Map;
1820
import javax.inject.Inject;
21+
import javax.inject.Named;
1922
import timber.log.Timber;
2023

2124
public class NearbyController extends MapController {
@@ -34,6 +37,10 @@ public NearbyController(NearbyPlaces nearbyPlaces) {
3437
this.nearbyPlaces = nearbyPlaces;
3538
}
3639

40+
@Inject
41+
@Named("default_preferences")
42+
JsonKvStore defaultKvStore;
43+
3744

3845
/**
3946
* Prepares Place list to make their distance information update later.
@@ -139,7 +146,8 @@ public String getPlacesAsGPX(LatLng currentLocation) throws Exception {
139146
* @throws Exception If an error occurs during the retrieval process.
140147
*/
141148
public List<Place> getPlaces(List<Place> placeList) throws Exception {
142-
return nearbyPlaces.getPlaces(placeList, Locale.getDefault().getLanguage());
149+
String secondaryLanguages = defaultKvStore.getString(Prefs.SECONDARY_LANGUAGES, "");
150+
return nearbyPlaces.getPlaces(placeList, Locale.getDefault().getLanguage(), secondaryLanguages);
143151
}
144152

145153
public static LatLng calculateNorthEast(double latitude, double longitude, double distance) {

app/src/main/java/fr/free/nrw/commons/nearby/NearbyPlaces.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -131,9 +131,9 @@ public List<Place> getFromWikidataQuery(
131131
* @throws Exception If an error occurs during the retrieval process.
132132
*/
133133
public List<Place> getPlaces(final List<Place> placeList,
134-
final String lang) throws Exception {
134+
final String lang, final String lang2) throws Exception {
135135
return okHttpJsonApiClient
136-
.getPlaces(placeList, lang);
136+
.getPlaces(placeList, lang, lang2);
137137
}
138138

139139
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package fr.free.nrw.commons.recentlanguages
2+
3+
import android.content.Context
4+
import android.view.LayoutInflater
5+
import android.view.View
6+
import android.view.ViewGroup
7+
import android.widget.ArrayAdapter
8+
import fr.free.nrw.commons.R
9+
import fr.free.nrw.commons.databinding.RowItemLanguagesSpinnerBinding
10+
import fr.free.nrw.commons.utils.LangCodeUtils
11+
import org.apache.commons.lang3.StringUtils
12+
import java.util.HashMap
13+
14+
/**
15+
* Array adapter for saved languages
16+
*/
17+
class SavedLanguagesAdapter constructor(
18+
context: Context,
19+
var savedLanguages: List<Language>, // List of saved languages
20+
private val selectedLanguages: HashMap<*, String>, // Selected languages map
21+
) : ArrayAdapter<String?>(context, R.layout.row_item_languages_spinner) {
22+
/**
23+
* Selected language code in SavedLanguagesAdapter
24+
* Used for marking selected ones
25+
*/
26+
var selectedLangCode = ""
27+
28+
override fun isEnabled(position: Int) =
29+
savedLanguages[position].languageCode.let {
30+
it.isNotEmpty() && !selectedLanguages.containsValue(it) && it != selectedLangCode
31+
}
32+
33+
override fun getCount() = savedLanguages.size
34+
35+
override fun getView(
36+
position: Int,
37+
convertView: View?,
38+
parent: ViewGroup,
39+
): View {
40+
val binding: RowItemLanguagesSpinnerBinding
41+
var rowView = convertView
42+
43+
if (rowView == null) {
44+
val layoutInflater =
45+
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
46+
binding = RowItemLanguagesSpinnerBinding.inflate(layoutInflater, parent, false)
47+
rowView = binding.root
48+
} else {
49+
binding = RowItemLanguagesSpinnerBinding.bind(rowView)
50+
}
51+
52+
val languageCode = savedLanguages[position].languageCode
53+
val languageName = savedLanguages[position].languageName
54+
binding.tvLanguage.let {
55+
it.isEnabled = isEnabled(position)
56+
if (languageCode.isEmpty()) {
57+
it.text = StringUtils.capitalize(languageName)
58+
it.textAlignment = View.TEXT_ALIGNMENT_CENTER
59+
} else {
60+
it.text =
61+
"${StringUtils.capitalize(languageName)}" +
62+
" [${LangCodeUtils.fixLanguageCode(languageCode)}]"
63+
}
64+
}
65+
return rowView
66+
}
67+
68+
/**
69+
* Provides code of a language from saved languages for a specific position
70+
*/
71+
fun getLanguageCode(position: Int): String = savedLanguages[position].languageCode
72+
73+
/**
74+
* Provides name of a language from saved languages for a specific position
75+
*/
76+
fun getLanguageName(position: Int): String = savedLanguages[position].languageName
77+
}

app/src/main/java/fr/free/nrw/commons/settings/Prefs.kt

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ object Prefs {
88
const val UPLOADS_SHOWING = "uploadsShowing"
99
const val MANAGED_EXIF_TAGS = "managed_exif_tags"
1010
const val DESCRIPTION_LANGUAGE = "languageDescription"
11+
const val SECONDARY_LANGUAGES = "secondaryLanguages"
1112
const val APP_UI_LANGUAGE = "appUiLanguage"
1213
const val KEY_THEME_VALUE = "appThemePref"
1314

app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.kt

+98
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import android.widget.AdapterView
1616
import android.widget.EditText
1717
import android.widget.ListView
1818
import android.widget.TextView
19+
import android.widget.Toast
1920
import androidx.activity.result.ActivityResultLauncher
2021
import androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions
2122
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
@@ -73,6 +74,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
7374

7475
private var themeListPreference: ListPreference? = null
7576
private var descriptionLanguageListPreference: Preference? = null
77+
private var descriptionSecondaryLanguagesListPreference: Preference? = null
7678
private var appUiLanguageListPreference: Preference? = null
7779
private var showDeletionButtonPreference: Preference? = null
7880
private var keyLanguageListPreference: String? = null
@@ -204,6 +206,12 @@ class SettingsFragment : PreferenceFragmentCompat() {
204206
}
205207
}
206208

209+
descriptionSecondaryLanguagesListPreference = findPreference("descriptionSecondaryLanguagesPref")
210+
descriptionSecondaryLanguagesListPreference?.setOnPreferenceClickListener {
211+
prepareSecondaryLanguagesDialog()
212+
true
213+
}
214+
207215
showDeletionButtonPreference = findPreference("displayDeletionButton")
208216
showDeletionButtonPreference?.setOnPreferenceChangeListener { _, newValue ->
209217
val isEnabled = newValue as Boolean
@@ -299,6 +307,82 @@ class SettingsFragment : PreferenceFragmentCompat() {
299307
}
300308
}
301309

310+
private fun prepareSecondaryLanguagesDialog() {
311+
val languageCode = getCurrentLanguageCode("descriptionSecondaryLanguagesPref")
312+
val selectedLanguages = hashMapOf<Int, String>()
313+
languageCode?.let {
314+
selectedLanguages[0] = Locale.getDefault().language
315+
}
316+
317+
val savedLanguages = arrayListOf<Language>()
318+
languageCode?.split(",\\s*".toRegex())?.forEach { code ->
319+
if (code != Locale.getDefault().language) {
320+
val locale = Locale(code)
321+
savedLanguages.add(Language(locale.displayLanguage, code))
322+
}
323+
}
324+
325+
val dialog = Dialog(requireActivity())
326+
dialog.setContentView(R.layout.dialog_select_secondary_languages)
327+
dialog.setCanceledOnTouchOutside(true)
328+
dialog.window?.setLayout(
329+
(resources.displayMetrics.widthPixels * 0.90).toInt(),
330+
(resources.displayMetrics.heightPixels * 0.90).toInt()
331+
)
332+
dialog.show()
333+
334+
val editText: EditText = dialog.findViewById(R.id.search_language)
335+
val listView: ListView = dialog.findViewById(R.id.language_list)
336+
val savedLanguageListView: ListView = dialog.findViewById(R.id.language_history_list)
337+
val separator: View = dialog.findViewById(R.id.separator)
338+
339+
updateSavedLanguages(savedLanguageListView, savedLanguages, selectedLanguages)
340+
341+
val languagesAdapter = LanguagesAdapter(requireActivity(), selectedLanguages)
342+
listView.adapter = languagesAdapter
343+
344+
editText.addTextChangedListener(object : TextWatcher {
345+
override fun beforeTextChanged(charSequence: CharSequence, start: Int, count: Int, after: Int) {}
346+
override fun onTextChanged(charSequence: CharSequence, start: Int, before: Int, count: Int) {
347+
languagesAdapter.filter.filter(charSequence)
348+
}
349+
override fun afterTextChanged(editable: Editable?) {}
350+
})
351+
352+
savedLanguageListView.setOnItemClickListener { _, _, position, _ ->
353+
savedLanguages.removeAt(position)
354+
updateSavedLanguages(savedLanguageListView, savedLanguages, selectedLanguages)
355+
saveLanguageValue(
356+
savedLanguages.joinToString(", ") { it.languageCode },
357+
"descriptionSecondaryLanguagesPref"
358+
)
359+
}
360+
361+
listView.setOnItemClickListener { _, _, position, _ ->
362+
val selectedLanguageCode = languagesAdapter.getLanguageCode(position)
363+
val selectedLanguageName = languagesAdapter.getLanguageName(position)
364+
365+
if (savedLanguages.any { it.languageCode == selectedLanguageCode }) {
366+
Toast.makeText(requireActivity(), "Language already selected", Toast.LENGTH_SHORT).show()
367+
return@setOnItemClickListener
368+
}
369+
370+
savedLanguages.add(Language(selectedLanguageName, selectedLanguageCode))
371+
updateSavedLanguages(savedLanguageListView, savedLanguages, selectedLanguages)
372+
saveLanguageValue(
373+
savedLanguages.joinToString(", ") { it.languageCode },
374+
"descriptionSecondaryLanguagesPref"
375+
)
376+
}
377+
378+
dialog.setOnDismissListener {
379+
saveLanguageValue(
380+
savedLanguages.joinToString(", ") { it.languageCode },
381+
"descriptionSecondaryLanguagesPref"
382+
)
383+
}
384+
}
385+
302386
/**
303387
* Prepare and Show language selection dialog box
304388
* Uses previously saved language if there is any, if not uses phone locale as initial language.
@@ -497,6 +581,16 @@ class SettingsFragment : PreferenceFragmentCompat() {
497581
}
498582
}
499583

584+
// Helper function to update saved languages
585+
private fun updateSavedLanguages(
586+
savedLanguageListView: ListView,
587+
savedLanguages: List<Language>,
588+
selectedLanguages: HashMap<Int, String>
589+
) {
590+
val savedLanguagesAdapter = RecentLanguagesAdapter(requireActivity(), savedLanguages, selectedLanguages)
591+
savedLanguageListView.adapter = savedLanguagesAdapter
592+
}
593+
500594
/**
501595
* Save userSelected language in List Preference
502596
* @param userSelectedValue
@@ -506,6 +600,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
506600
when (preferenceKey) {
507601
"appUiDefaultLanguagePref" -> defaultKvStore.putString(Prefs.APP_UI_LANGUAGE, userSelectedValue)
508602
"descriptionDefaultLanguagePref" -> defaultKvStore.putString(Prefs.DESCRIPTION_LANGUAGE, userSelectedValue)
603+
"descriptionSecondaryLanguagesPref" -> defaultKvStore.putString(Prefs.SECONDARY_LANGUAGES, userSelectedValue)
509604
}
510605
}
511606

@@ -522,6 +617,9 @@ class SettingsFragment : PreferenceFragmentCompat() {
522617
"descriptionDefaultLanguagePref" -> defaultKvStore.getString(
523618
Prefs.DESCRIPTION_LANGUAGE, ""
524619
)
620+
"descriptionSecondaryLanguagesPref" -> defaultKvStore.getString(
621+
Prefs.SECONDARY_LANGUAGES, ""
622+
)
525623
else -> null
526624
}
527625
}

0 commit comments

Comments
 (0)