Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Languages order preference #5826 #6059

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 54 additions & 7 deletions app/src/main/java/fr/free/nrw/commons/mwapi/OkHttpJsonApiClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -484,19 +484,65 @@ class OkHttpJsonApiClient @Inject constructor(

@Throws(IOException::class)
fun getPlaces(
placeList: List<Place>, language: String
placeList: List<Place>, primaryLanguage: String, secondaryLanguages: String
): List<Place>? {
val wikidataQuery = FileUtils.readFromResource("/queries/query_for_item.rq")

// Split the secondary languages string into an array to use in fallback queries
val secondaryLanguagesArray = secondaryLanguages.split(",\\s*".toRegex())

// Prepare the Wikidata entity IDs (QIDs) for each place in the list
var qids = ""
for (place in placeList) {
qids += """
${"wd:" + place.wikiDataEntityId}"""
qids += "\nwd:${place.wikiDataEntityId}"
}

// Build fallback descriptions for secondary languages in case the primary language is unavailable
val fallBackDescription = StringBuilder()
secondaryLanguagesArray.forEachIndexed { index, lang ->
fallBackDescription.append("OPTIONAL {?item schema:description ?itemDescriptionPreferredLanguage_")
.append(index + 1)
.append(". FILTER (lang(?itemDescriptionPreferredLanguage_")
.append(index + 1)
.append(") = \"")
.append(lang)
.append("\")}\n")
}

// Build fallback labels for secondary languages
val fallbackLabel = StringBuilder()
secondaryLanguagesArray.forEachIndexed { index, lang ->
fallbackLabel.append("OPTIONAL {?item rdfs:label ?itemLabelPreferredLanguage_")
.append(index + 1)
.append(". FILTER (lang(?itemLabelPreferredLanguage_")
.append(index + 1)
.append(") = \"")
.append(lang)
.append("\")}\n")
}

// Build fallback class labels for secondary languages
val fallbackClassLabel = StringBuilder()
secondaryLanguagesArray.forEachIndexed { index, lang ->
fallbackClassLabel.append("OPTIONAL {?class rdfs:label ?classLabelPreferredLanguage_")
.append(index + 1)
.append(". FILTER (lang(?classLabelPreferredLanguage_")
.append(index + 1)
.append(") = \"")
.append(lang)
.append("\")}\n")
}

// Replace placeholders in the query with actual data: QIDs, language codes, and fallback options
val query = wikidataQuery
.replace("\${ENTITY}", qids)
.replace("\${LANG}", language)
val urlBuilder: HttpUrl.Builder = sparqlQueryUrl.toHttpUrlOrNull()!!
.newBuilder()
.replace("\${LANG}", primaryLanguage)
.replace("\${SECONDARYDESCRIPTION}", fallBackDescription.toString())
.replace("\${SECONDARYLABEL}", fallbackLabel.toString())
.replace("\${SECONDARYCLASSLABEL}", fallbackClassLabel.toString())

// Build the URL for the SparQL query with the formatted query string
val urlBuilder = sparqlQueryUrl.toHttpUrlOrNull()!!.newBuilder()
.addQueryParameter("query", query)
.addQueryParameter("format", "json")

Expand All @@ -514,11 +560,12 @@ ${"wd:" + place.wikiDataEntityId}"""
}
return places
} else {
throw IOException("Unexpected response code: " + response.code)
throw IOException("Unexpected response code: ${response.code}")
}
}
}


@Throws(Exception::class)
fun getPlacesAsKML(leftLatLng: LatLng, rightLatLng: LatLng): String? {
var kmlString = """<?xml version="1.0" encoding="UTF-8" standalone="no"?>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
import androidx.annotation.Nullable;
import fr.free.nrw.commons.BaseMarker;
import fr.free.nrw.commons.MapController;
import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.settings.Prefs;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
Expand All @@ -16,6 +18,7 @@
import java.util.Locale;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Named;
import timber.log.Timber;

public class NearbyController extends MapController {
Expand All @@ -34,6 +37,10 @@ public NearbyController(NearbyPlaces nearbyPlaces) {
this.nearbyPlaces = nearbyPlaces;
}

@Inject
@Named("default_preferences")
JsonKvStore defaultKvStore;


/**
* Prepares Place list to make their distance information update later.
Expand Down Expand Up @@ -139,7 +146,9 @@ public String getPlacesAsGPX(LatLng currentLocation) throws Exception {
* @throws Exception If an error occurs during the retrieval process.
*/
public List<Place> getPlaces(List<Place> placeList) throws Exception {
return nearbyPlaces.getPlaces(placeList, Locale.getDefault().getLanguage());
String secondaryLanguages = defaultKvStore.getString(Prefs.SECONDARY_LANGUAGES, "");
String primaryLanguage = defaultKvStore.getString(Prefs.DESCRIPTION_LANGUAGE, "");
return nearbyPlaces.getPlaces(placeList, primaryLanguage, secondaryLanguages);
}

public static LatLng calculateNorthEast(double latitude, double longitude, double distance) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,9 +191,9 @@ public List<Place> getFromWikidataQuery(
* @throws Exception If an error occurs during the retrieval process.
*/
public List<Place> getPlaces(final List<Place> placeList,
final String lang) throws Exception {
final String lang, final String lang2) throws Exception {
return okHttpJsonApiClient
.getPlaces(placeList, lang);
.getPlaces(placeList, lang, lang2);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package fr.free.nrw.commons.recentlanguages

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import fr.free.nrw.commons.R
import fr.free.nrw.commons.databinding.RowItemLanguagesSpinnerBinding
import fr.free.nrw.commons.utils.LangCodeUtils
import org.apache.commons.lang3.StringUtils
import java.util.HashMap

/**
* Array adapter for saved languages
*/
class SavedLanguagesAdapter constructor(
context: Context,
var savedLanguages: List<Language>, // List of saved languages
private val selectedLanguages: HashMap<*, String>, // Selected languages map
) : ArrayAdapter<String?>(context, R.layout.row_item_languages_spinner) {
/**
* Selected language code in SavedLanguagesAdapter
* Used for marking selected ones
*/
var selectedLangCode = ""

override fun isEnabled(position: Int) =
savedLanguages[position].languageCode.let {
it.isNotEmpty() && !selectedLanguages.containsValue(it) && it != selectedLangCode
}

override fun getCount() = savedLanguages.size

override fun getView(
position: Int,
convertView: View?,
parent: ViewGroup,
): View {
val binding: RowItemLanguagesSpinnerBinding
var rowView = convertView

if (rowView == null) {
val layoutInflater =
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
binding = RowItemLanguagesSpinnerBinding.inflate(layoutInflater, parent, false)
rowView = binding.root
} else {
binding = RowItemLanguagesSpinnerBinding.bind(rowView)
}

val languageCode = savedLanguages[position].languageCode
val languageName = savedLanguages[position].languageName
binding.tvLanguage.let {
it.isEnabled = isEnabled(position)
if (languageCode.isEmpty()) {
it.text = StringUtils.capitalize(languageName)
it.textAlignment = View.TEXT_ALIGNMENT_CENTER
} else {
it.text =
"${StringUtils.capitalize(languageName)}" +
" [${LangCodeUtils.fixLanguageCode(languageCode)}]"
}
}
return rowView
}

/**
* Provides code of a language from saved languages for a specific position
*/
fun getLanguageCode(position: Int): String = savedLanguages[position].languageCode

/**
* Provides name of a language from saved languages for a specific position
*/
fun getLanguageName(position: Int): String = savedLanguages[position].languageName
}
1 change: 1 addition & 0 deletions app/src/main/java/fr/free/nrw/commons/settings/Prefs.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ object Prefs {
const val UPLOADS_SHOWING = "uploadsShowing"
const val MANAGED_EXIF_TAGS = "managed_exif_tags"
const val DESCRIPTION_LANGUAGE = "languageDescription"
const val SECONDARY_LANGUAGES = "secondaryLanguages"
const val APP_UI_LANGUAGE = "appUiLanguage"
const val KEY_THEME_VALUE = "appThemePref"

Expand Down
107 changes: 107 additions & 0 deletions app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import android.widget.AdapterView
import android.widget.EditText
import android.widget.ListView
import android.widget.TextView
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
Expand Down Expand Up @@ -74,6 +75,7 @@ class SettingsFragment : PreferenceFragmentCompat() {

private var themeListPreference: ListPreference? = null
private var descriptionLanguageListPreference: Preference? = null
private var descriptionSecondaryLanguagesListPreference: Preference? = null
private var appUiLanguageListPreference: Preference? = null
private var showDeletionButtonPreference: Preference? = null
private var keyLanguageListPreference: String? = null
Expand Down Expand Up @@ -205,6 +207,12 @@ class SettingsFragment : PreferenceFragmentCompat() {
}
}

descriptionSecondaryLanguagesListPreference = findPreference("descriptionSecondaryLanguagesPref")
descriptionSecondaryLanguagesListPreference?.setOnPreferenceClickListener {
prepareSecondaryLanguagesDialog()
true
}

showDeletionButtonPreference = findPreference("displayDeletionButton")
showDeletionButtonPreference?.setOnPreferenceChangeListener { _, newValue ->
val isEnabled = newValue as Boolean
Expand Down Expand Up @@ -300,6 +308,91 @@ class SettingsFragment : PreferenceFragmentCompat() {
}
}

private fun prepareSecondaryLanguagesDialog() {
val languageCode = getCurrentLanguageCode("descriptionSecondaryLanguagesPref")
val defaultCode = getCurrentLanguageCode("descriptionDefaultLanguagePref")
val selectedLanguages = hashMapOf<Int, String>()

var deflocale = Locale.getDefault()


if (defaultCode != null){
deflocale = createLocale(defaultCode)
}

languageCode?.let {
selectedLanguages[0] = deflocale.language
}

val savedLanguages = arrayListOf<Language>()
languageCode?.split(",\\s*".toRegex())?.forEach { code ->
if (code != deflocale.language) {
val locale = Locale(code)
savedLanguages.add(Language(locale.displayLanguage, code))
}
}

val dialog = Dialog(requireActivity())
dialog.setContentView(R.layout.dialog_select_secondary_languages)
dialog.setCanceledOnTouchOutside(true)
dialog.window?.setLayout(
(resources.displayMetrics.widthPixels * 0.90).toInt(),
(resources.displayMetrics.heightPixels * 0.90).toInt()
)
dialog.show()

val editText: EditText = dialog.findViewById(R.id.search_language)
val listView: ListView = dialog.findViewById(R.id.language_list)
val savedLanguageListView: ListView = dialog.findViewById(R.id.language_history_list)
val separator: View = dialog.findViewById(R.id.separator)

updateSavedLanguages(savedLanguageListView, savedLanguages, selectedLanguages)

val languagesAdapter = LanguagesAdapter(requireActivity(), selectedLanguages)
listView.adapter = languagesAdapter

editText.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(charSequence: CharSequence, start: Int, count: Int, after: Int) {}
override fun onTextChanged(charSequence: CharSequence, start: Int, before: Int, count: Int) {
languagesAdapter.filter.filter(charSequence)
}
override fun afterTextChanged(editable: Editable?) {}
})

savedLanguageListView.setOnItemClickListener { _, _, position, _ ->
savedLanguages.removeAt(position)
updateSavedLanguages(savedLanguageListView, savedLanguages, selectedLanguages)
saveLanguageValue(
savedLanguages.joinToString(", ") { it.languageCode },
"descriptionSecondaryLanguagesPref"
)
}

listView.setOnItemClickListener { _, _, position, _ ->
val selectedLanguageCode = languagesAdapter.getLanguageCode(position)
val selectedLanguageName = languagesAdapter.getLanguageName(position)

if (savedLanguages.any { it.languageCode == selectedLanguageCode }) {
Toast.makeText(requireActivity(), "Language already selected", Toast.LENGTH_SHORT).show()
return@setOnItemClickListener
}

savedLanguages.add(Language(selectedLanguageName, selectedLanguageCode))
updateSavedLanguages(savedLanguageListView, savedLanguages, selectedLanguages)
saveLanguageValue(
savedLanguages.joinToString(", ") { it.languageCode },
"descriptionSecondaryLanguagesPref"
)
}

dialog.setOnDismissListener {
saveLanguageValue(
savedLanguages.joinToString(", ") { it.languageCode },
"descriptionSecondaryLanguagesPref"
)
}
}

/**
* Prepare and Show language selection dialog box
* Uses previously saved language if there is any, if not uses phone locale as initial language.
Expand Down Expand Up @@ -509,6 +602,16 @@ class SettingsFragment : PreferenceFragmentCompat() {
}
}

// Helper function to update saved languages
private fun updateSavedLanguages(
savedLanguageListView: ListView,
savedLanguages: List<Language>,
selectedLanguages: HashMap<Int, String>
) {
val savedLanguagesAdapter = RecentLanguagesAdapter(requireActivity(), savedLanguages, selectedLanguages)
savedLanguageListView.adapter = savedLanguagesAdapter
}

/**
* Save userSelected language in List Preference
* @param userSelectedValue
Expand All @@ -518,6 +621,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
when (preferenceKey) {
"appUiDefaultLanguagePref" -> defaultKvStore.putString(Prefs.APP_UI_LANGUAGE, userSelectedValue)
"descriptionDefaultLanguagePref" -> defaultKvStore.putString(Prefs.DESCRIPTION_LANGUAGE, userSelectedValue)
"descriptionSecondaryLanguagesPref" -> defaultKvStore.putString(Prefs.SECONDARY_LANGUAGES, userSelectedValue)
}
}

Expand All @@ -534,6 +638,9 @@ class SettingsFragment : PreferenceFragmentCompat() {
"descriptionDefaultLanguagePref" -> defaultKvStore.getString(
Prefs.DESCRIPTION_LANGUAGE, ""
)
"descriptionSecondaryLanguagesPref" -> defaultKvStore.getString(
Prefs.SECONDARY_LANGUAGES, ""
)
else -> null
}
}
Expand Down
Loading