diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index a5d456928e..f39734eb4b 100644
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -1,16 +1,12 @@
-
-
-
-
@@ -25,13 +21,11 @@
-
-
@@ -47,6 +41,5 @@
-
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index a9c0c498cf..bb3309714d 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -16,6 +16,11 @@ if (isRunningOnTravisAndIsNotPRBuild) {
dependencies {
+ def appcompat_version = "1.7.0"
+ implementation "androidx.appcompat:appcompat:$appcompat_version"
+ // For loading and tinting drawables on older versions of the platform
+ implementation "androidx.appcompat:appcompat-resources:$appcompat_version"
+
// Utils
implementation 'in.yuvi:http.fluent:1.3'
implementation 'com.google.code.gson:gson:2.8.5'
@@ -380,13 +385,16 @@ android {
compose true
}
composeOptions {
- kotlinCompilerExtensionVersion '1.3.2'
+ kotlinCompilerExtensionVersion '1.5.8'
}
namespace 'fr.free.nrw.commons'
lint {
abortOnError false
disable 'MissingTranslation', 'ExtraTranslation'
}
+ androidResources {
+ generateLocaleConfig true
+ }
}
String getTestUserName() {
diff --git a/app/src/androidTest/java/fr/free/nrw/commons/ui/AppLanguagesSystemTest.java b/app/src/androidTest/java/fr/free/nrw/commons/ui/AppLanguagesSystemTest.java
new file mode 100644
index 0000000000..3abe28e365
--- /dev/null
+++ b/app/src/androidTest/java/fr/free/nrw/commons/ui/AppLanguagesSystemTest.java
@@ -0,0 +1,58 @@
+package fr.free.nrw.commons.ui;
+
+import androidx.test.uiautomator.UiScrollable;
+import androidx.test.uiautomator.UiSelector;
+import androidx.test.uiautomator.UiObject;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.platform.app.InstrumentationRegistry;
+import android.content.Intent;
+import android.provider.Settings;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class AppLanguagesSystemTest {
+
+ private UiDevice device;
+
+ @Before
+ public void setUp() {
+ // Initiate UI Automator
+ device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+
+ // Use Intent to start system application
+ Intent intent = new Intent(Settings.ACTION_SETTINGS);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ InstrumentationRegistry.getInstrumentation().getContext().startActivity(intent);
+
+ // wait Settings starting
+ device.waitForIdle();
+ }
+
+ @Test
+ public void testCommonsAppLanguageOptionExists() throws Exception {
+ // 1. Find and click "System"
+ UiScrollable settingsList = new UiScrollable(new UiSelector().scrollable(true));
+ UiObject systemOption = settingsList.getChildByText(new UiSelector().text("System"), "System");
+ systemOption.click();
+
+ // 2. scroll and find "Languages & input"
+ UiObject languagesInputOption = device.findObject(new UiSelector()
+ .className("android.widget.TextView")
+ .textContains("Gboard"));
+ languagesInputOption.clickAndWaitForNewWindow();
+
+
+
+
+ // 3. detect "App languages" and click
+ UiObject appLanguagesOption = device.findObject(new UiSelector().text("App languages"));
+ appLanguagesOption.clickAndWaitForNewWindow();
+
+ // 4. detect "Commons" APP is there
+ UiScrollable appLanguagesList = new UiScrollable(new UiSelector().scrollable(true));
+ UiObject commonsAppOption = appLanguagesList.getChildByText(new UiSelector().text("Commons"), "Commons");
+
+ assert(commonsAppOption.exists());
+ }
+}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 6a47a46447..ee7cebde26 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -193,6 +193,13 @@
+
+
+
{
onSupportNavigateUp();
@@ -485,13 +485,13 @@ public enum ActiveFragment {
/**
* Load default language in onCreate from SharedPreferences
*/
- private void loadLocale() {
- final SharedPreferences preferences = getSharedPreferences("Settings",
- Activity.MODE_PRIVATE);
- final String language = preferences.getString("language", "");
- final SettingsFragment settingsFragment = new SettingsFragment();
- settingsFragment.setLocale(this, language);
- }
+// private void loadLocale() {
+// final SharedPreferences preferences = getSharedPreferences("Settings",
+// Activity.MODE_PRIVATE);
+// final String language = preferences.getString("language", "");
+// final SettingsFragment settingsFragment = new SettingsFragment();
+// settingsFragment.setLocale(this, language);
+// }
public NavTabLayout.OnNavigationItemSelectedListener getNavListener() {
return navListener;
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/database/NotForUploadStatusDao.kt b/app/src/main/java/fr/free/nrw/commons/customselector/database/NotForUploadStatusDao.kt
index b75a6e1d4f..872388f40d 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/database/NotForUploadStatusDao.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/database/NotForUploadStatusDao.kt
@@ -15,19 +15,19 @@ abstract class NotForUploadStatusDao {
* Insert into Not For Upload status.
*/
@Insert(onConflict = OnConflictStrategy.REPLACE)
- abstract suspend fun insert(notForUploadStatus: NotForUploadStatus)
+ abstract fun insert(notForUploadStatus: NotForUploadStatus)
/**
* Delete Not For Upload status entry.
*/
@Delete
- abstract suspend fun delete(notForUploadStatus: NotForUploadStatus)
+ abstract fun delete(notForUploadStatus: NotForUploadStatus)
/**
* Query Not For Upload status with image sha1.
*/
@Query("SELECT * FROM images_not_for_upload_table WHERE imageSHA1 = (:imageSHA1) ")
- abstract suspend fun getFromImageSHA1(imageSHA1: String): NotForUploadStatus?
+ abstract fun getFromImageSHA1(imageSHA1: String): NotForUploadStatus?
/**
* Asynchronous image sha1 query.
@@ -38,7 +38,7 @@ abstract class NotForUploadStatusDao {
* Deletion Not For Upload status with image sha1.
*/
@Query("DELETE FROM images_not_for_upload_table WHERE imageSHA1 = (:imageSHA1) ")
- abstract suspend fun deleteWithImageSHA1(imageSHA1: String)
+ abstract fun deleteWithImageSHA1(imageSHA1: String)
/**
* Asynchronous image sha1 deletion.
@@ -49,5 +49,5 @@ abstract class NotForUploadStatusDao {
* Check whether the imageSHA1 is present in database
*/
@Query("SELECT COUNT() FROM images_not_for_upload_table WHERE imageSHA1 = (:imageSHA1) ")
- abstract suspend fun find(imageSHA1: String): Int
+ abstract fun find(imageSHA1: String): Int
}
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/database/UploadedStatusDao.kt b/app/src/main/java/fr/free/nrw/commons/customselector/database/UploadedStatusDao.kt
index 378af5b8db..03cbb176fe 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/database/UploadedStatusDao.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/database/UploadedStatusDao.kt
@@ -17,31 +17,31 @@ abstract class UploadedStatusDao {
* Insert into uploaded status.
*/
@Insert(onConflict = OnConflictStrategy.REPLACE)
- abstract suspend fun insert(uploadedStatus: UploadedStatus)
+ abstract fun insert(uploadedStatus: UploadedStatus)
/**
* Update uploaded status entry.
*/
@Update
- abstract suspend fun update(uploadedStatus: UploadedStatus)
+ abstract fun update(uploadedStatus: UploadedStatus)
/**
* Delete uploaded status entry.
*/
@Delete
- abstract suspend fun delete(uploadedStatus: UploadedStatus)
+ abstract fun delete(uploadedStatus: UploadedStatus)
/**
* Query uploaded status with image sha1.
*/
@Query("SELECT * FROM uploaded_table WHERE imageSHA1 = (:imageSHA1) ")
- abstract suspend fun getFromImageSHA1(imageSHA1: String): UploadedStatus?
+ abstract fun getFromImageSHA1(imageSHA1: String): UploadedStatus?
/**
* Query uploaded status with modified image sha1.
*/
@Query("SELECT * FROM uploaded_table WHERE modifiedImageSHA1 = (:modifiedImageSHA1) ")
- abstract suspend fun getFromModifiedImageSHA1(modifiedImageSHA1: String): UploadedStatus?
+ abstract fun getFromModifiedImageSHA1(modifiedImageSHA1: String): UploadedStatus?
/**
* Asynchronous insert into uploaded status table.
@@ -55,7 +55,7 @@ abstract class UploadedStatusDao {
* Check whether the imageSHA1 is present in database
*/
@Query("SELECT COUNT() FROM uploaded_table WHERE imageSHA1 = (:imageSHA1) AND imageResult = (:imageResult) ")
- abstract suspend fun findByImageSHA1(
+ abstract fun findByImageSHA1(
imageSHA1: String,
imageResult: Boolean,
): Int
@@ -66,7 +66,7 @@ abstract class UploadedStatusDao {
@Query(
"SELECT COUNT() FROM uploaded_table WHERE modifiedImageSHA1 = (:modifiedImageSHA1) AND modifiedImageResult = (:modifiedImageResult) ",
)
- abstract suspend fun findByModifiedImageSHA1(
+ abstract fun findByModifiedImageSHA1(
modifiedImageSHA1: String,
modifiedImageResult: Boolean,
): Int
diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java
index 7336c1b407..384e7f1a9a 100644
--- a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java
+++ b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java
@@ -707,7 +707,8 @@ public void updateCategories() {
*/
private void buildDepictionList(List idAndCaptions) {
binding.mediaDetailDepictionContainer.removeAllViews();
- String locale = Locale.getDefault().getLanguage();
+ String fullCode = Locale.getDefault().getLanguage();
+ String locale = fullCode.contains(",") ? fullCode.substring(0, fullCode.indexOf(',')).trim() : fullCode;
for (IdAndCaptions idAndCaption : idAndCaptions) {
binding.mediaDetailDepictionContainer.addView(buildDepictLabel(
getDepictionCaption(idAndCaption, locale),
diff --git a/app/src/main/java/fr/free/nrw/commons/mwapi/OkHttpJsonApiClient.java b/app/src/main/java/fr/free/nrw/commons/mwapi/OkHttpJsonApiClient.java
index 8d6b742319..dd041d8bc6 100644
--- a/app/src/main/java/fr/free/nrw/commons/mwapi/OkHttpJsonApiClient.java
+++ b/app/src/main/java/fr/free/nrw/commons/mwapi/OkHttpJsonApiClient.java
@@ -409,15 +409,55 @@ public List getNearbyPlaces(
*/
@Nullable
public List getPlaces(
- final List placeList, final String language) throws IOException {
+ final List placeList, final String language, final String secondaryLanguages) throws IOException {
final String wikidataQuery = FileUtils.readFromResource("/queries/query_for_item.rq");
+ final String[] secondaryLanguageArray = secondaryLanguages.split(",\\s*"); // could be used to generate backup SparQL Queries
+
String qids = "";
for (final Place place : placeList) {
qids += "\n" + ("wd:" + place.getWikiDataEntityId());
}
+
+ StringBuilder fallBackDescription = new StringBuilder();
+ for (int i = 0; i < secondaryLanguageArray.length; i++) {
+ fallBackDescription.append("OPTIONAL {?item schema:description ?itemDescriptionPreferredLanguage_")
+ .append(i + 1)
+ .append(". FILTER (lang(?itemDescriptionPreferredLanguage_")
+ .append(i + 1)
+ .append(") = \"")
+ .append(secondaryLanguageArray[i])
+ .append("\")}\n");
+ }
+
+ StringBuilder fallbackLabel = new StringBuilder();
+ for (int i = 0; i < secondaryLanguageArray.length; i++) {
+ fallbackLabel.append("OPTIONAL {?item rdfs:label ?itemLabelPreferredLanguage_")
+ .append(i + 1)
+ .append(". FILTER (lang(?itemLabelPreferredLanguage_")
+ .append(i + 1)
+ .append(") = \"")
+ .append(secondaryLanguageArray[i])
+ .append("\")}\n");
+ }
+
+ StringBuilder fallbackClassLabel = new StringBuilder();
+ for (int i = 0; i < secondaryLanguageArray.length; i++) {
+ fallbackClassLabel.append("OPTIONAL {?class rdfs:label ?classLabelPreferredLanguage_")
+ .append(i + 1)
+ .append(". FILTER (lang(?classLabelPreferredLanguage_")
+ .append(i + 1)
+ .append(") = \"")
+ .append(secondaryLanguageArray[i])
+ .append("\")}\n");
+ }
+
final String query = wikidataQuery
.replace("${ENTITY}", qids)
- .replace("${LANG}", language);
+ .replace("${LANG}", language)
+ .replace("${SECONDARYDESCRIPTION}", fallBackDescription.toString())
+ .replace("${SECONDARYLABEL}", fallbackLabel.toString())
+ .replace("${SECONDARYCLASSLABEL}", fallbackClassLabel.toString());
+
final HttpUrl.Builder urlBuilder = HttpUrl
.parse(sparqlQueryUrl)
.newBuilder()
diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyController.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyController.java
index 7bb3119617..d5f551e27f 100644
--- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyController.java
+++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyController.java
@@ -8,6 +8,8 @@
import fr.free.nrw.commons.BaseMarker;
import fr.free.nrw.commons.MapController;
import fr.free.nrw.commons.location.LatLng;
+import fr.free.nrw.commons.kvstore.JsonKvStore;
+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.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,9 @@ public NearbyController(NearbyPlaces nearbyPlaces) {
this.nearbyPlaces = nearbyPlaces;
}
+ @Inject
+ @Named("default_preferences")
+ JsonKvStore defaultKvStore;
/**
* Prepares Place list to make their distance information update later.
@@ -56,6 +62,7 @@ public NearbyPlacesInfo loadAttractionsFromLocation(final LatLng currentLatLng,
Timber.d("Loading attractions nearby, but currentLatLng is null");
return null;
}
+
List places = nearbyPlaces
.radiusExpander(searchLatLng, Locale.getDefault().getLanguage(), returnClosestResult,
customQuery);
@@ -139,7 +146,10 @@ public String getPlacesAsGPX(LatLng currentLocation) throws Exception {
* @throws Exception If an error occurs during the retrieval process.
*/
public List getPlaces(List placeList) throws Exception {
- return nearbyPlaces.getPlaces(placeList, Locale.getDefault().getLanguage());
+
+ String secondaryLanguages = defaultKvStore.getString(Prefs.SECONDARY_LANGUAGE, "");
+
+ return nearbyPlaces.getPlaces(placeList, Locale.getDefault().getLanguage(), secondaryLanguages);
}
public static LatLng calculateNorthEast(double latitude, double longitude, double distance) {
diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyPlaces.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyPlaces.java
index 46f0a2a9eb..cfc45945ee 100644
--- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyPlaces.java
+++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyPlaces.java
@@ -131,9 +131,9 @@ public List getFromWikidataQuery(
* @throws Exception If an error occurs during the retrieval process.
*/
public List getPlaces(final List placeList,
- final String lang) throws Exception {
+ final String lang, final String lang2) throws Exception {
return okHttpJsonApiClient
- .getPlaces(placeList, lang);
+ .getPlaces(placeList, lang, lang2);
}
/**
diff --git a/app/src/main/java/fr/free/nrw/commons/recentlanguages/RecentLanguagesAdapter.kt b/app/src/main/java/fr/free/nrw/commons/recentlanguages/RecentLanguagesAdapter.kt
index 81ef5533d9..b71670e2a5 100644
--- a/app/src/main/java/fr/free/nrw/commons/recentlanguages/RecentLanguagesAdapter.kt
+++ b/app/src/main/java/fr/free/nrw/commons/recentlanguages/RecentLanguagesAdapter.kt
@@ -27,7 +27,8 @@ class RecentLanguagesAdapter constructor(
override fun isEnabled(position: Int) =
recentLanguages[position].languageCode.let {
- it.isNotEmpty() && !selectedLanguages.containsValue(it) && it != selectedLangCode
+// it.isNotEmpty() && !it.contains(selectedLanguages.values.first())
+ true
}
override fun getCount() = recentLanguages.size
diff --git a/app/src/main/java/fr/free/nrw/commons/recentlanguages/SavedLanguagesAdapter.kt b/app/src/main/java/fr/free/nrw/commons/recentlanguages/SavedLanguagesAdapter.kt
new file mode 100644
index 0000000000..c067a6e49e
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/recentlanguages/SavedLanguagesAdapter.kt
@@ -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, // List of saved languages
+ private val selectedLanguages: HashMap<*, String>, // Selected languages map
+) : ArrayAdapter(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
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/settings/Prefs.java b/app/src/main/java/fr/free/nrw/commons/settings/Prefs.java
index 3342143479..d0451e24ca 100644
--- a/app/src/main/java/fr/free/nrw/commons/settings/Prefs.java
+++ b/app/src/main/java/fr/free/nrw/commons/settings/Prefs.java
@@ -8,6 +8,8 @@ public class Prefs {
public static final String UPLOADS_SHOWING = "uploadsshowing";
public static final String MANAGED_EXIF_TAGS = "managed_exif_tags";
public static final String DESCRIPTION_LANGUAGE = "languageDescription";
+ public static final String SECONDARY_LANGUAGE = "languageSecondary";
+
public static final String APP_UI_LANGUAGE = "appUiLanguage";
public static final String KEY_THEME_VALUE = "appThemePref";
diff --git a/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.java b/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.java
index 94e799aa29..cd40d5b5e2 100644
--- a/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.java
+++ b/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.java
@@ -3,6 +3,7 @@
import static android.content.Context.MODE_PRIVATE;
import android.Manifest.permission;
+import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Dialog;
import android.content.Intent;
@@ -18,9 +19,13 @@
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
+import android.widget.Toast;
+import android.widget.ArrayAdapter;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
+import androidx.appcompat.app.AppCompatDelegate;
+import androidx.core.os.LocaleListCompat;
import androidx.preference.ListPreference;
import androidx.preference.MultiSelectListPreference;
import androidx.preference.Preference;
@@ -47,10 +52,13 @@
import fr.free.nrw.commons.recentlanguages.Language;
import fr.free.nrw.commons.recentlanguages.RecentLanguagesAdapter;
import fr.free.nrw.commons.recentlanguages.RecentLanguagesDao;
+import fr.free.nrw.commons.recentlanguages.SavedLanguagesAdapter;
import fr.free.nrw.commons.upload.LanguagesAdapter;
import fr.free.nrw.commons.utils.DialogUtil;
import fr.free.nrw.commons.utils.PermissionUtils;
import fr.free.nrw.commons.utils.ViewUtil;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -79,13 +87,16 @@ public class SettingsFragment extends PreferenceFragmentCompat {
private ListPreference themeListPreference;
private Preference descriptionLanguageListPreference;
+ private Preference descriptionSecondaryLanguageListPreference;
private Preference appUiLanguageListPreference;
private String keyLanguageListPreference;
private TextView recentLanguagesTextView;
private View separator;
private ListView languageHistoryListView;
private static final String GET_CONTENT_PICKER_HELP_URL = "https://commons-app.github.io/docs.html#get-content";
- private ActivityResultLauncher inAppCameraLocationPermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), new ActivityResultCallback