Draft for MR 1

This commit is contained in:
Adith 2024-12-21 18:25:25 +05:30
parent 4c9637c821
commit 1fbbc072d4
10 changed files with 326 additions and 10 deletions

View file

@ -388,19 +388,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")
@ -418,11 +464,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"?>

View file

@ -7,7 +7,9 @@ import androidx.annotation.MainThread;
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;
@ -16,6 +18,7 @@ import java.util.ListIterator;
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 {
@ -34,6 +37,10 @@ public class NearbyController extends MapController {
this.nearbyPlaces = nearbyPlaces;
}
@Inject
@Named("default_preferences")
JsonKvStore defaultKvStore;
/**
* Prepares Place list to make their distance information update later.
@ -139,7 +146,8 @@ public class NearbyController extends MapController {
* @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, "");
return nearbyPlaces.getPlaces(placeList, Locale.getDefault().getLanguage(), secondaryLanguages);
}
public static LatLng calculateNorthEast(double latitude, double longitude, double distance) {

View file

@ -131,9 +131,9 @@ public class NearbyPlaces {
* @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);
}
/**

View file

@ -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
}

View file

@ -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"

View file

@ -16,6 +16,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
@ -73,6 +74,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
@ -204,6 +206,12 @@ class SettingsFragment : PreferenceFragmentCompat() {
}
}
descriptionSecondaryLanguagesListPreference = findPreference("descriptionSecondaryLanguagesPref")
descriptionSecondaryLanguagesListPreference?.setOnPreferenceClickListener {
prepareSecondaryLanguagesDialog()
true
}
showDeletionButtonPreference = findPreference("displayDeletionButton")
showDeletionButtonPreference?.setOnPreferenceChangeListener { _, newValue ->
val isEnabled = newValue as Boolean
@ -299,6 +307,82 @@ class SettingsFragment : PreferenceFragmentCompat() {
}
}
private fun prepareSecondaryLanguagesDialog() {
val languageCode = getCurrentLanguageCode("descriptionSecondaryLanguagesPref")
val selectedLanguages = hashMapOf<Int, String>()
languageCode?.let {
selectedLanguages[0] = Locale.getDefault().language
}
val savedLanguages = arrayListOf<Language>()
languageCode?.split(",\\s*".toRegex())?.forEach { code ->
if (code != Locale.getDefault().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.
@ -497,6 +581,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
@ -506,6 +600,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)
}
}
@ -522,6 +617,9 @@ class SettingsFragment : PreferenceFragmentCompat() {
"descriptionDefaultLanguagePref" -> defaultKvStore.getString(
Prefs.DESCRIPTION_LANGUAGE, ""
)
"descriptionSecondaryLanguagesPref" -> defaultKvStore.getString(
Prefs.SECONDARY_LANGUAGES, ""
)
else -> null
}
}

View file

@ -0,0 +1,74 @@
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:id="@+id/search_language"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:hint="Type Language Name"
android:padding="12dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/language_order_preference"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:text="Language order preference"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/search_language" />
<ListView
android:id="@+id/language_history_list"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toTopOf="@id/separator"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/language_order_preference" />
<View
android:id="@+id/separator"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/black"
android:layout_marginTop="10dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/language_history_list" />
<TextView
android:id="@+id/all_languages"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="All Languages"
app:layout_constraintTop_toBottomOf="@id/separator"
app:layout_constraintEnd_toEndOf="@+id/language_history_list"
android:layout_margin="8dp"
app:layout_constraintStart_toStartOf="parent" />
<ListView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="8dp"
android:id="@+id/language_list"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/all_languages" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -551,6 +551,7 @@ Upload your first media by tapping on the add button.</string>
<string name="dialog_box_text_nomination">Why should %1$s be deleted?</string>
<string name="review_is_uploaded_by">%1$s is uploaded by: %2$s</string>
<string name="default_description_language">Default description language</string>
<string name="secondary_description_languages">Secondary Description Language</string>
<string name="delete_helper_show_deletion_title">Nominating for deletion</string>
<string name="delete_helper_show_deletion_title_success">Success</string>
<string name="delete_helper_show_deletion_message_if">Nominated %1$s for deletion.</string>

View file

@ -31,6 +31,13 @@
app:singleLineTitle="false"
android:title="@string/default_description_language" />
<!-- New Secondary Language Picker -->
<Preference
android:key="descriptionSecondaryLanguagesPref"
app:useSimpleSummaryProvider="true"
app:singleLineTitle="false"
android:title="@string/secondary_description_languages" />
<SwitchPreference
android:defaultValue="true"
android:key="displayNearbyCardView"

View file

@ -19,11 +19,13 @@ WHERE {
# Get the label in the preferred language of the user, or any other language if no label is available in that language.
OPTIONAL {?item rdfs:label ?itemLabelPreferredLanguage. FILTER (lang(?itemLabelPreferredLanguage) = "${LANG}")}
${SECONDARYLABEL}
OPTIONAL {?item rdfs:label ?itemLabelAnyLanguage}
BIND(COALESCE(?itemLabelPreferredLanguage, ?itemLabelAnyLanguage, "?") as ?label)
# Get the description in the preferred language of the user, or any other language if no description is available in that language.
OPTIONAL {?item schema:description ?itemDescriptionPreferredLanguage. FILTER (lang(?itemDescriptionPreferredLanguage) = "${LANG}")}
${SECONDARYDESCRIPTION}
OPTIONAL {?item schema:description ?itemDescriptionAnyLanguage}
BIND(COALESCE(?itemDescriptionPreferredLanguage, ?itemDescriptionAnyLanguage, "?") as ?description)
@ -31,6 +33,7 @@ WHERE {
OPTIONAL {
?item p:P31/ps:P31 ?class.
OPTIONAL {?class rdfs:label ?classLabelPreferredLanguage. FILTER (lang(?classLabelPreferredLanguage) = "${LANG}")}
${SECONDARYCLASSLABEL}
OPTIONAL {?class rdfs:label ?classLabelAnyLanguage}
BIND(COALESCE(?classLabelPreferredLanguage, ?classLabelAnyLanguage, "?") as ?classLabel)
}