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 deleted file mode 100644 index 334214347..000000000 --- a/app/src/main/java/fr/free/nrw/commons/settings/Prefs.java +++ /dev/null @@ -1,21 +0,0 @@ -package fr.free.nrw.commons.settings; - -public class Prefs { - public static String GLOBAL_PREFS = "fr.free.nrw.commons.preferences"; - - public static String TRACKING_ENABLED = "eventLogging"; - public static final String DEFAULT_LICENSE = "defaultLicense"; - 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 APP_UI_LANGUAGE = "appUiLanguage"; - public static final String KEY_THEME_VALUE = "appThemePref"; - - public static class Licenses { - public static final String CC_BY_SA_3 = "CC BY-SA 3.0"; - public static final String CC_BY_3 = "CC BY 3.0"; - public static final String CC_BY_SA_4 = "CC BY-SA 4.0"; - public static final String CC_BY_4 = "CC BY 4.0"; - public static final String CC0 = "CC0"; - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/settings/Prefs.kt b/app/src/main/java/fr/free/nrw/commons/settings/Prefs.kt new file mode 100644 index 000000000..13e8efb57 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/settings/Prefs.kt @@ -0,0 +1,21 @@ +package fr.free.nrw.commons.settings + +object Prefs { + const val GLOBAL_PREFS = "fr.free.nrw.commons.preferences" + + const val TRACKING_ENABLED = "eventLogging" + const val DEFAULT_LICENSE = "defaultLicense" + const val UPLOADS_SHOWING = "uploadsShowing" + const val MANAGED_EXIF_TAGS = "managed_exif_tags" + const val DESCRIPTION_LANGUAGE = "languageDescription" + const val APP_UI_LANGUAGE = "appUiLanguage" + const val KEY_THEME_VALUE = "appThemePref" + + object Licenses { + const val CC_BY_SA_3 = "CC BY-SA 3.0" + const val CC_BY_3 = "CC BY 3.0" + const val CC_BY_SA_4 = "CC BY-SA 4.0" + const val CC_BY_4 = "CC BY 4.0" + const val CC0 = "CC0" + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/settings/SettingsActivity.java b/app/src/main/java/fr/free/nrw/commons/settings/SettingsActivity.java deleted file mode 100644 index ff5024b32..000000000 --- a/app/src/main/java/fr/free/nrw/commons/settings/SettingsActivity.java +++ /dev/null @@ -1,69 +0,0 @@ -package fr.free.nrw.commons.settings; - -import android.os.Bundle; -import android.view.MenuItem; - -import android.view.View; -import androidx.appcompat.app.AppCompatDelegate; - -import fr.free.nrw.commons.databinding.ActivitySettingsBinding; -import fr.free.nrw.commons.theme.BaseActivity; - -/** - * allows the user to change the settings - */ -public class SettingsActivity extends BaseActivity { - - private ActivitySettingsBinding binding; -// private AppCompatDelegate settingsDelegate; - /** - * to be called when the activity starts - * @param savedInstanceState the previously saved state - */ - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - binding = ActivitySettingsBinding.inflate(getLayoutInflater()); - final View view = binding.getRoot(); - setContentView(view); - - setSupportActionBar(binding.toolbarBinding.toolbar); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - } - - // Get an action bar - /** - * takes care of actions taken after the creation has happened - * @param savedInstanceState the saved state - */ - @Override - protected void onPostCreate(Bundle savedInstanceState) { - super.onPostCreate(savedInstanceState); -// if (settingsDelegate == null) { -// settingsDelegate = AppCompatDelegate.create(this, null); -// } -// settingsDelegate.onPostCreate(savedInstanceState); - } - - @Override - public boolean onSupportNavigateUp() { - onBackPressed(); - return true; - } - - /** - * Handle action-bar clicks - * @param item the selected item - * @return true on success, false on failure - */ - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - finish(); - return true; - default: - return super.onOptionsItemSelected(item); - } - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/settings/SettingsActivity.kt b/app/src/main/java/fr/free/nrw/commons/settings/SettingsActivity.kt new file mode 100644 index 000000000..da79244bc --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/settings/SettingsActivity.kt @@ -0,0 +1,63 @@ +package fr.free.nrw.commons.settings + +import android.os.Bundle +import android.view.MenuItem +import fr.free.nrw.commons.databinding.ActivitySettingsBinding +import fr.free.nrw.commons.theme.BaseActivity + + +/** + * allows the user to change the settings + */ +class SettingsActivity : BaseActivity() { + + private lateinit var binding: ActivitySettingsBinding +// private var settingsDelegate: AppCompatDelegate? = null + + /** + * to be called when the activity starts + * @param savedInstanceState the previously saved state + */ + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivitySettingsBinding.inflate(layoutInflater) + val view = binding.root + setContentView(view) + + setSupportActionBar(binding.toolbarBinding.toolbar) + supportActionBar?.setDisplayHomeAsUpEnabled(true) + } + + // Get an action bar + /** + * takes care of actions taken after the creation has happened + * @param savedInstanceState the saved state + */ + override fun onPostCreate(savedInstanceState: Bundle?) { + super.onPostCreate(savedInstanceState) +// if (settingsDelegate == null) { +// settingsDelegate = AppCompatDelegate.create(this, null) +// } +// settingsDelegate?.onPostCreate(savedInstanceState) + } + + override fun onSupportNavigateUp(): Boolean { + onBackPressed() + return true + } + + /** + * Handle action-bar clicks + * @param item the selected item + * @return true on success, false on failure + */ + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + android.R.id.home -> { + finish() + true + } + else -> super.onOptionsItemSelected(item) + } + } +} 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 deleted file mode 100644 index d4ed379f0..000000000 --- a/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.java +++ /dev/null @@ -1,575 +0,0 @@ -package fr.free.nrw.commons.settings; - -import static android.content.Context.MODE_PRIVATE; - -import android.Manifest.permission; -import android.app.Activity; -import android.app.Dialog; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.res.Configuration; -import android.net.Uri; -import android.os.Bundle; -import android.text.Editable; -import android.text.TextWatcher; -import android.view.View; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemClickListener; -import android.widget.EditText; -import android.widget.ListView; -import android.widget.TextView; -import androidx.activity.result.ActivityResultCallback; -import androidx.activity.result.ActivityResultLauncher; -import androidx.activity.result.contract.ActivityResultContracts; -import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult; -import androidx.preference.ListPreference; -import androidx.preference.MultiSelectListPreference; -import androidx.preference.Preference; -import androidx.preference.Preference.OnPreferenceClickListener; -import androidx.preference.PreferenceFragmentCompat; -import androidx.preference.PreferenceGroupAdapter; -import androidx.preference.PreferenceScreen; -import androidx.preference.PreferenceViewHolder; -import androidx.recyclerview.widget.RecyclerView.Adapter; -import com.karumi.dexter.Dexter; -import com.karumi.dexter.MultiplePermissionsReport; -import com.karumi.dexter.PermissionToken; -import com.karumi.dexter.listener.PermissionRequest; -import com.karumi.dexter.listener.multi.MultiplePermissionsListener; -import fr.free.nrw.commons.R; -import fr.free.nrw.commons.Utils; -import fr.free.nrw.commons.campaigns.CampaignView; -import fr.free.nrw.commons.contributions.ContributionController; -import fr.free.nrw.commons.contributions.MainActivity; -import fr.free.nrw.commons.di.ApplicationlessInjection; -import fr.free.nrw.commons.kvstore.JsonKvStore; -import fr.free.nrw.commons.location.LocationServiceManager; -import fr.free.nrw.commons.logging.CommonsLogSender; -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.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.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import javax.inject.Inject; -import javax.inject.Named; -import timber.log.Timber; - -public class SettingsFragment extends PreferenceFragmentCompat { - - @Inject - @Named("default_preferences") - JsonKvStore defaultKvStore; - - @Inject - CommonsLogSender commonsLogSender; - - @Inject - RecentLanguagesDao recentLanguagesDao; - - @Inject - ContributionController contributionController; - - @Inject - LocationServiceManager locationManager; - - private ListPreference themeListPreference; - private Preference descriptionLanguageListPreference; - private Preference appUiLanguageListPreference; - private Preference showDeletionButtonPreference; - 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 final ActivityResultLauncher cameraPickLauncherForResult = - registerForActivityResult(new StartActivityForResult(), - result -> { - contributionController.handleActivityResultWithCallback(requireActivity(), callbacks -> { - contributionController.onPictureReturnedFromCamera(result, requireActivity(), callbacks); - }); - }); - - private ActivityResultLauncher inAppCameraLocationPermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), new ActivityResultCallback>() { - @Override - public void onActivityResult(Map result) { - boolean areAllGranted = true; - for (final boolean b : result.values()) { - areAllGranted = areAllGranted && b; - } - if (!areAllGranted && shouldShowRequestPermissionRationale(permission.ACCESS_FINE_LOCATION)) { - contributionController.handleShowRationaleFlowCameraLocation(getActivity(), inAppCameraLocationPermissionLauncher, cameraPickLauncherForResult); - } - } - }); - - @Override - public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { - ApplicationlessInjection - .getInstance(getActivity().getApplicationContext()) - .getCommonsApplicationComponent() - .inject(this); - - // Set the preferences from an XML resource - setPreferencesFromResource(R.xml.preferences, rootKey); - - themeListPreference = findPreference(Prefs.KEY_THEME_VALUE); - prepareTheme(); - - MultiSelectListPreference multiSelectListPref = findPreference(Prefs.MANAGED_EXIF_TAGS); - if (multiSelectListPref != null) { - multiSelectListPref.setOnPreferenceChangeListener((preference, newValue) -> { - if (newValue instanceof HashSet && !((HashSet) newValue).contains(getString(R.string.exif_tag_location))) { - defaultKvStore.putBoolean("has_user_manually_removed_location", true); - } - return true; - }); - } - - Preference inAppCameraLocationPref = findPreference("inAppCameraLocationPref"); - - inAppCameraLocationPref.setOnPreferenceChangeListener( - (preference, newValue) -> { - boolean isInAppCameraLocationTurnedOn = (boolean) newValue; - if (isInAppCameraLocationTurnedOn) { - createDialogsAndHandleLocationPermissions(getActivity()); - } - return true; - } - ); - - // Gets current language code from shared preferences - String languageCode; - - appUiLanguageListPreference = findPreference("appUiDefaultLanguagePref"); - assert appUiLanguageListPreference != null; - keyLanguageListPreference = appUiLanguageListPreference.getKey(); - languageCode = getCurrentLanguageCode(keyLanguageListPreference); - assert languageCode != null; - if (languageCode.equals("")) { - // If current language code is empty, means none selected by user yet so use phone local - appUiLanguageListPreference.setSummary(Locale.getDefault().getDisplayLanguage()); - } else { - // If any language is selected by user previously, use it - Locale defLocale = createLocale(languageCode); - appUiLanguageListPreference.setSummary((defLocale).getDisplayLanguage(defLocale)); - } - appUiLanguageListPreference.setOnPreferenceClickListener(new OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - prepareAppLanguages(appUiLanguageListPreference.getKey()); - return true; - } - }); - - descriptionLanguageListPreference = findPreference("descriptionDefaultLanguagePref"); - assert descriptionLanguageListPreference != null; - keyLanguageListPreference = descriptionLanguageListPreference.getKey(); - languageCode = getCurrentLanguageCode(keyLanguageListPreference); - assert languageCode != null; - if (languageCode.equals("")) { - // If current language code is empty, means none selected by user yet so use phone local - descriptionLanguageListPreference.setSummary(Locale.getDefault().getDisplayLanguage()); - } else { - // If any language is selected by user previously, use it - Locale defLocale = createLocale(languageCode); - descriptionLanguageListPreference.setSummary(defLocale.getDisplayLanguage(defLocale)); - } - descriptionLanguageListPreference.setOnPreferenceClickListener(new OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - prepareAppLanguages(descriptionLanguageListPreference.getKey()); - return true; - } - }); - - // - showDeletionButtonPreference = findPreference("displayDeletionButton"); - if (showDeletionButtonPreference != null) { - showDeletionButtonPreference.setOnPreferenceChangeListener((preference, newValue) -> { - boolean isEnabled = (boolean) newValue; - // Save preference when user toggles the button - defaultKvStore.putBoolean("displayDeletionButton", isEnabled); - return true; - }); - } - - - Preference betaTesterPreference = findPreference("becomeBetaTester"); - betaTesterPreference.setOnPreferenceClickListener(preference -> { - Utils.handleWebUrl(getActivity(), Uri.parse(getResources().getString(R.string.beta_opt_in_link))); - return true; - }); - Preference sendLogsPreference = findPreference("sendLogFile"); - sendLogsPreference.setOnPreferenceClickListener(preference -> { - checkPermissionsAndSendLogs(); - return true; - }); - - Preference documentBasedPickerPreference = findPreference("openDocumentPhotoPickerPref"); - documentBasedPickerPreference.setOnPreferenceChangeListener( - (preference, newValue) -> { - boolean isGetContentPickerTurnedOn = !(boolean) newValue; - if (isGetContentPickerTurnedOn) { - showLocationLossWarning(); - } - return true; - } - ); - // Disable some settings when not logged in. - if (defaultKvStore.getBoolean("login_skipped", false)) { - findPreference("useExternalStorage").setEnabled(false); - findPreference("useAuthorName").setEnabled(false); - findPreference("displayNearbyCardView").setEnabled(false); - findPreference("descriptionDefaultLanguagePref").setEnabled(false); - findPreference("displayLocationPermissionForCardView").setEnabled(false); - findPreference(CampaignView.CAMPAIGNS_DEFAULT_PREFERENCE).setEnabled(false); - findPreference("managed_exif_tags").setEnabled(false); - findPreference("openDocumentPhotoPickerPref").setEnabled(false); - findPreference("inAppCameraLocationPref").setEnabled(false); - } - } - - /** - * Asks users to provide location access - * - * @param activity - */ - private void createDialogsAndHandleLocationPermissions(Activity activity) { - inAppCameraLocationPermissionLauncher.launch(new String[]{permission.ACCESS_FINE_LOCATION}); - } - - /** - * On some devices, the new Photo Picker with GET_CONTENT takeover - * redacts location tags from EXIF metadata - * - * Show warning to the user when ACTION_GET_CONTENT intent is enabled - */ - private void showLocationLossWarning() { - DialogUtil.showAlertDialog( - getActivity(), - null, - getString(R.string.location_loss_warning), - getString(R.string.ok), - getString(R.string.read_help_link), - () -> {}, - () -> Utils.handleWebUrl(requireContext(), Uri.parse(GET_CONTENT_PICKER_HELP_URL)), - null, - true - ); - } - - @Override - protected Adapter onCreateAdapter(final PreferenceScreen preferenceScreen) { - return new PreferenceGroupAdapter(preferenceScreen) { - @Override - public void onBindViewHolder(PreferenceViewHolder holder, int position) { - super.onBindViewHolder(holder, position); - Preference preference = getItem(position); - View iconFrame = holder.itemView.findViewById(R.id.icon_frame); - if (iconFrame != null) { - iconFrame.setVisibility(View.GONE); - } - } - }; - } - - /** - * Sets the theme pref - */ - private void prepareTheme() { - themeListPreference.setOnPreferenceChangeListener((preference, newValue) -> { - getActivity().recreate(); - return true; - }); - } - - /** - * Prepare and Show language selection dialog box - * Uses previously saved language if there is any, if not uses phone locale as initial language. - * Disable default/already selected language from dialog box - * Get ListPreference key and act accordingly for each ListPreference. - * saves value chosen by user to shared preferences - * to remember later and recall MainActivity to reflect language changes - * @param keyListPreference - */ - private void prepareAppLanguages(final String keyListPreference) { - - // Gets current language code from shared preferences - final String languageCode = getCurrentLanguageCode(keyListPreference); - final List recentLanguages = recentLanguagesDao.getRecentLanguages(); - HashMap selectedLanguages = new HashMap<>(); - - if (keyListPreference.equals("appUiDefaultLanguagePref")) { - - assert languageCode != null; - if (languageCode.equals("")) { - selectedLanguages.put(0, Locale.getDefault().getLanguage()); - } else { - selectedLanguages.put(0, languageCode); - } - } else if (keyListPreference.equals("descriptionDefaultLanguagePref")) { - - assert languageCode != null; - if (languageCode.equals("")) { - selectedLanguages.put(0, Locale.getDefault().getLanguage()); - - } else { - selectedLanguages.put(0, languageCode); - } - } - - LanguagesAdapter languagesAdapter = new LanguagesAdapter( - getActivity(), - selectedLanguages - ); - - Dialog dialog = new Dialog(getActivity()); - dialog.setContentView(R.layout.dialog_select_language); - dialog.setCanceledOnTouchOutside(true); - dialog.getWindow().setLayout((int)(getActivity().getResources().getDisplayMetrics().widthPixels*0.90), - (int)(getActivity().getResources().getDisplayMetrics().heightPixels*0.90)); - dialog.show(); - - EditText editText = dialog.findViewById(R.id.search_language); - ListView listView = dialog.findViewById(R.id.language_list); - languageHistoryListView = dialog.findViewById(R.id.language_history_list); - recentLanguagesTextView = dialog.findViewById(R.id.recent_searches); - separator = dialog.findViewById(R.id.separator); - - setUpRecentLanguagesSection(recentLanguages, selectedLanguages); - - listView.setAdapter(languagesAdapter); - - editText.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence charSequence, int i, int i1, - int i2) { - hideRecentLanguagesSection(); - } - - @Override - public void onTextChanged(CharSequence charSequence, int i, int i1, - int i2) { - languagesAdapter.getFilter().filter(charSequence); - } - - @Override - public void afterTextChanged(Editable editable) { - - } - }); - - languageHistoryListView.setOnItemClickListener((adapterView, view, position, id) -> { - onRecentLanguageClicked(keyListPreference, dialog, adapterView, position); - }); - - listView.setOnItemClickListener(new OnItemClickListener() { - @Override - public void onItemClick(AdapterView adapterView, View view, int i, - long l) { - String languageCode = ((LanguagesAdapter) adapterView.getAdapter()) - .getLanguageCode(i); - final String languageName = ((LanguagesAdapter) adapterView.getAdapter()) - .getLanguageName(i); - final boolean isExists = recentLanguagesDao.findRecentLanguage(languageCode); - if (isExists) { - recentLanguagesDao.deleteRecentLanguage(languageCode); - } - recentLanguagesDao.addRecentLanguage(new Language(languageName, languageCode)); - saveLanguageValue(languageCode, keyListPreference); - Locale defLocale = createLocale(languageCode); - if(keyListPreference.equals("appUiDefaultLanguagePref")) { - appUiLanguageListPreference.setSummary(defLocale.getDisplayLanguage(defLocale)); - setLocale(requireActivity(), languageCode); - getActivity().recreate(); - final Intent intent = new Intent(getActivity(), MainActivity.class); - startActivity(intent); - }else { - descriptionLanguageListPreference.setSummary(defLocale.getDisplayLanguage(defLocale)); - } - dialog.dismiss(); - } - }); - - dialog.setOnDismissListener( - dialogInterface -> languagesAdapter.getFilter().filter("")); - } - - /** - * Set up recent languages section - * - * @param recentLanguages recently used languages - * @param selectedLanguages selected languages - */ - private void setUpRecentLanguagesSection(List recentLanguages, - HashMap selectedLanguages) { - if (recentLanguages.isEmpty()) { - languageHistoryListView.setVisibility(View.GONE); - recentLanguagesTextView.setVisibility(View.GONE); - separator.setVisibility(View.GONE); - } else { - if (recentLanguages.size() > 5) { - for (int i = recentLanguages.size()-1; i >=5; i--) { - recentLanguagesDao - .deleteRecentLanguage(recentLanguages.get(i).getLanguageCode()); - } - } - languageHistoryListView.setVisibility(View.VISIBLE); - recentLanguagesTextView.setVisibility(View.VISIBLE); - separator.setVisibility(View.VISIBLE); - final RecentLanguagesAdapter recentLanguagesAdapter - = new RecentLanguagesAdapter( - getActivity(), - recentLanguagesDao.getRecentLanguages(), - selectedLanguages); - languageHistoryListView.setAdapter(recentLanguagesAdapter); - } - } - - /** - * Handles click event for recent language section - */ - private void onRecentLanguageClicked(String keyListPreference, Dialog dialog, AdapterView adapterView, - int position) { - final String recentLanguageCode = ((RecentLanguagesAdapter) adapterView.getAdapter()) - .getLanguageCode(position); - final String recentLanguageName = ((RecentLanguagesAdapter) adapterView.getAdapter()) - .getLanguageName(position); - final boolean isExists = recentLanguagesDao.findRecentLanguage(recentLanguageCode); - if (isExists) { - recentLanguagesDao.deleteRecentLanguage(recentLanguageCode); - } - recentLanguagesDao.addRecentLanguage( - new Language(recentLanguageName, recentLanguageCode)); - saveLanguageValue(recentLanguageCode, keyListPreference); - final Locale defLocale = createLocale(recentLanguageCode); - if (keyListPreference.equals("appUiDefaultLanguagePref")) { - appUiLanguageListPreference.setSummary(defLocale.getDisplayLanguage(defLocale)); - setLocale(requireActivity(), recentLanguageCode); - getActivity().recreate(); - final Intent intent = new Intent(getActivity(), MainActivity.class); - startActivity(intent); - } else { - descriptionLanguageListPreference.setSummary(defLocale.getDisplayLanguage(defLocale)); - } - dialog.dismiss(); - } - - /** - * Remove the section of recent languages - */ - private void hideRecentLanguagesSection() { - languageHistoryListView.setVisibility(View.GONE); - recentLanguagesTextView.setVisibility(View.GONE); - separator.setVisibility(View.GONE); - } - - /** - * Changing the default app language with selected one and save it to SharedPreferences - */ - public void setLocale(final Activity activity, String userSelectedValue) { - if (userSelectedValue.equals("")) { - userSelectedValue = Locale.getDefault().getLanguage(); - } - final Locale locale = createLocale(userSelectedValue); - Locale.setDefault(locale); - final Configuration configuration = new Configuration(); - configuration.locale = locale; - activity.getBaseContext().getResources().updateConfiguration(configuration, - activity.getBaseContext().getResources().getDisplayMetrics()); - - final SharedPreferences.Editor editor = activity.getSharedPreferences("Settings", MODE_PRIVATE).edit(); - editor.putString("language", userSelectedValue); - editor.apply(); - } - - /** - * Create Locale based on different types of language codes - * @param languageCode - * @return Locale and throws error for invalid language codes - */ - public static Locale createLocale(String languageCode) { - String[] parts = languageCode.split("-"); - switch (parts.length) { - case 1: - return new Locale(parts[0]); - case 2: - return new Locale(parts[0], parts[1]); - case 3: - return new Locale(parts[0], parts[1], parts[2]); - default: - throw new IllegalArgumentException("Invalid language code: " + languageCode); - } - } - - /** - * Save userselected language in List Preference - * @param userSelectedValue - * @param preferenceKey - */ - private void saveLanguageValue(final String userSelectedValue, final String preferenceKey) { - if (preferenceKey.equals("appUiDefaultLanguagePref")) { - defaultKvStore.putString(Prefs.APP_UI_LANGUAGE, userSelectedValue); - } else if (preferenceKey.equals("descriptionDefaultLanguagePref")) { - defaultKvStore.putString(Prefs.DESCRIPTION_LANGUAGE, userSelectedValue); - } - } - - /** - * Gets current language code from shared preferences - * @param preferenceKey - * @return - */ - private String getCurrentLanguageCode(final String preferenceKey) { - if (preferenceKey.equals("appUiDefaultLanguagePref")) { - return defaultKvStore.getString(Prefs.APP_UI_LANGUAGE, ""); - } - if (preferenceKey.equals("descriptionDefaultLanguagePref")) { - return defaultKvStore.getString(Prefs.DESCRIPTION_LANGUAGE, ""); - } - return null; - } - - /** - * First checks for external storage permissions and then sends logs via email - */ - private void checkPermissionsAndSendLogs() { - if (PermissionUtils.hasPermission(getActivity(), PermissionUtils.getPERMISSIONS_STORAGE())) { - commonsLogSender.send(getActivity(), null); - } else { - requestExternalStoragePermissions(); - } - } - - /** - * Requests external storage permissions and shows a toast stating that log collection has - * started - */ - private void requestExternalStoragePermissions() { - Dexter.withActivity(getActivity()) - .withPermissions(PermissionUtils.getPERMISSIONS_STORAGE()) - .withListener(new MultiplePermissionsListener() { - @Override - public void onPermissionsChecked(MultiplePermissionsReport report) { - ViewUtil.showLongToast(getActivity(), - getResources().getString(R.string.log_collection_started)); - } - - @Override - public void onPermissionRationaleShouldBeShown( - List permissions, PermissionToken token) { - - } - }) - .onSameThread() - .check(); - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.kt b/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.kt new file mode 100644 index 000000000..53f6b28fe --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.kt @@ -0,0 +1,556 @@ +package fr.free.nrw.commons.settings + +import android.Manifest.permission +import android.app.Activity +import android.app.Dialog +import android.content.Context.MODE_PRIVATE +import android.content.Intent +import android.content.res.Configuration +import android.net.Uri +import android.os.Bundle +import android.text.Editable +import android.text.TextWatcher +import android.view.View +import android.widget.AdapterView +import android.widget.EditText +import android.widget.ListView +import android.widget.TextView +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions +import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult +import androidx.preference.ListPreference +import androidx.preference.MultiSelectListPreference +import androidx.preference.Preference +import androidx.preference.PreferenceFragmentCompat +import androidx.preference.PreferenceGroupAdapter +import androidx.preference.PreferenceScreen +import androidx.preference.PreferenceViewHolder +import androidx.recyclerview.widget.RecyclerView.Adapter +import com.karumi.dexter.Dexter +import com.karumi.dexter.MultiplePermissionsReport +import com.karumi.dexter.PermissionToken +import com.karumi.dexter.listener.PermissionRequest +import com.karumi.dexter.listener.multi.MultiplePermissionsListener +import fr.free.nrw.commons.R +import fr.free.nrw.commons.Utils +import fr.free.nrw.commons.campaigns.CampaignView +import fr.free.nrw.commons.contributions.ContributionController +import fr.free.nrw.commons.contributions.MainActivity +import fr.free.nrw.commons.di.ApplicationlessInjection +import fr.free.nrw.commons.kvstore.JsonKvStore +import fr.free.nrw.commons.location.LocationServiceManager +import fr.free.nrw.commons.logging.CommonsLogSender +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.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.Locale +import javax.inject.Inject +import javax.inject.Named + +class SettingsFragment : PreferenceFragmentCompat() { + + @Inject + @field: Named("default_preferences") + lateinit var defaultKvStore: JsonKvStore + + @Inject + lateinit var commonsLogSender: CommonsLogSender + + @Inject + lateinit var recentLanguagesDao: RecentLanguagesDao + + @Inject + lateinit var contributionController: ContributionController + + @Inject + lateinit var locationManager: LocationServiceManager + + private var themeListPreference: ListPreference? = null + private var descriptionLanguageListPreference: Preference? = null + private var appUiLanguageListPreference: Preference? = null + private var showDeletionButtonPreference: Preference? = null + private var keyLanguageListPreference: String? = null + private var recentLanguagesTextView: TextView? = null + private var separator: View? = null + private var languageHistoryListView: ListView? = null + private lateinit var inAppCameraLocationPermissionLauncher: ActivityResultLauncher> + private val GET_CONTENT_PICKER_HELP_URL = "https://commons-app.github.io/docs.html#get-content" + + private val cameraPickLauncherForResult: ActivityResultLauncher = + registerForActivityResult(StartActivityForResult()) { result -> + contributionController.handleActivityResultWithCallback(requireActivity()) { callbacks -> + contributionController.onPictureReturnedFromCamera(result, requireActivity(), callbacks) + } + } + + /** + * to be called when the fragment creates preferences + * @param savedInstanceState the previously saved state + * @param rootKey the root key for preferences + */ + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + ApplicationlessInjection + .getInstance(requireActivity().applicationContext) + .commonsApplicationComponent + .inject(this) + + // Set the preferences from an XML resource + setPreferencesFromResource(R.xml.preferences, rootKey) + + themeListPreference = findPreference(Prefs.KEY_THEME_VALUE) + prepareTheme() + + val multiSelectListPref: MultiSelectListPreference? = findPreference( + Prefs.MANAGED_EXIF_TAGS + ) + multiSelectListPref?.setOnPreferenceChangeListener { _, newValue -> + if (newValue is HashSet<*> && !newValue.contains(getString(R.string.exif_tag_location))) + { + defaultKvStore.putBoolean("has_user_manually_removed_location", true) + } + true + } + + val inAppCameraLocationPref: Preference? = findPreference("inAppCameraLocationPref") + inAppCameraLocationPref?.setOnPreferenceChangeListener { _, newValue -> + val isInAppCameraLocationTurnedOn = newValue as Boolean + if (isInAppCameraLocationTurnedOn) { + createDialogsAndHandleLocationPermissions(requireActivity()) + } + true + } + + inAppCameraLocationPermissionLauncher = registerForActivityResult( + RequestMultiplePermissions() + ) { result -> + var areAllGranted = true + for (b in result.values) { + areAllGranted = areAllGranted && b + } + if ( + !areAllGranted + && + shouldShowRequestPermissionRationale(permission.ACCESS_FINE_LOCATION) + ) { + contributionController.handleShowRationaleFlowCameraLocation( + requireActivity(), + inAppCameraLocationPermissionLauncher, + cameraPickLauncherForResult + ) + } + } + + // Gets current language code from shared preferences + var languageCode: String? + + appUiLanguageListPreference = findPreference("appUiDefaultLanguagePref") + appUiLanguageListPreference?.let { appUiLanguageListPreference -> + keyLanguageListPreference = appUiLanguageListPreference.key + languageCode = getCurrentLanguageCode(keyLanguageListPreference!!) + + languageCode?.let { code -> + if (code.isEmpty()) { + // If current language code is empty, means none selected by user yet so use + // phone locale + appUiLanguageListPreference.summary = Locale.getDefault().displayLanguage + } else { + // If any language is selected by user previously, use it + val defLocale = createLocale(code) + appUiLanguageListPreference.summary = defLocale.getDisplayLanguage(defLocale) + } + } + + appUiLanguageListPreference.setOnPreferenceClickListener { + prepareAppLanguages(keyLanguageListPreference!!) + true + } + } + + descriptionLanguageListPreference = findPreference("descriptionDefaultLanguagePref") + descriptionLanguageListPreference?.let { descriptionLanguageListPreference -> + languageCode = getCurrentLanguageCode(descriptionLanguageListPreference.key) + + languageCode?.let { code -> + if (code.isEmpty()) { + // If current language code is empty, means none selected by user yet so use + // phone locale + descriptionLanguageListPreference.summary = Locale.getDefault().displayLanguage + } else { + // If any language is selected by user previously, use it + val defLocale = createLocale(code) + descriptionLanguageListPreference.summary = defLocale.getDisplayLanguage( + defLocale + ) + } + } + + descriptionLanguageListPreference.setOnPreferenceClickListener { + prepareAppLanguages(it.key) + true + } + } + + showDeletionButtonPreference = findPreference("displayDeletionButton") + showDeletionButtonPreference?.setOnPreferenceChangeListener { _, newValue -> + val isEnabled = newValue as Boolean + // Save preference when user toggles the button + defaultKvStore.putBoolean("displayDeletionButton", isEnabled) + true + } + + val betaTesterPreference: Preference? = findPreference("becomeBetaTester") + betaTesterPreference?.setOnPreferenceClickListener { + Utils.handleWebUrl(requireActivity(), Uri.parse(getString(R.string.beta_opt_in_link))) + true + } + + val sendLogsPreference: Preference? = findPreference("sendLogFile") + sendLogsPreference?.setOnPreferenceClickListener { + checkPermissionsAndSendLogs() + true + } + + val documentBasedPickerPreference: Preference? = findPreference( + "openDocumentPhotoPickerPref" + ) + documentBasedPickerPreference?.setOnPreferenceChangeListener { _, newValue -> + val isGetContentPickerTurnedOn = newValue as Boolean + if (!isGetContentPickerTurnedOn) { + showLocationLossWarning() + } + true + } + + // Disable some settings when not logged in. + if (defaultKvStore.getBoolean("login_skipped", false)) { + findPreference("useExternalStorage")?.isEnabled = false + findPreference("useAuthorName")?.isEnabled = false + findPreference("displayNearbyCardView")?.isEnabled = false + findPreference("descriptionDefaultLanguagePref")?.isEnabled = false + findPreference("displayLocationPermissionForCardView")?.isEnabled = false + findPreference(CampaignView.CAMPAIGNS_DEFAULT_PREFERENCE)?.isEnabled = false + findPreference("managed_exif_tags")?.isEnabled = false + findPreference("openDocumentPhotoPickerPref")?.isEnabled = false + findPreference("inAppCameraLocationPref")?.isEnabled = false + } + } + + /** + * Asks users to provide location access + * + * @param activity + */ + private fun createDialogsAndHandleLocationPermissions(activity: Activity) { + inAppCameraLocationPermissionLauncher.launch(arrayOf(permission.ACCESS_FINE_LOCATION)) + } + + /** + * On some devices, the new Photo Picker with GET_CONTENT takeover + * redacts location tags from EXIF metadata + * + * Show warning to the user when ACTION_GET_CONTENT intent is enabled + */ + private fun showLocationLossWarning() { + DialogUtil.showAlertDialog( + requireActivity(), + null, + getString(R.string.location_loss_warning), + getString(R.string.ok), + getString(R.string.read_help_link), + { }, + { Utils.handleWebUrl(requireContext(), Uri.parse(GET_CONTENT_PICKER_HELP_URL)) }, + null, + true + ) + } + + override fun onCreateAdapter(preferenceScreen: PreferenceScreen): Adapter + { + return object : PreferenceGroupAdapter(preferenceScreen) { + override fun onBindViewHolder(holder: PreferenceViewHolder, position: Int) { + super.onBindViewHolder(holder, position) + val preference = getItem(position) + val iconFrame: View? = holder.itemView.findViewById(R.id.icon_frame) + iconFrame?.visibility = View.GONE + } + } + } + + /** + * Sets the theme pref + */ + private fun prepareTheme() { + themeListPreference?.setOnPreferenceChangeListener { _, _ -> + requireActivity().recreate() + true + } + } + + /** + * Prepare and Show language selection dialog box + * Uses previously saved language if there is any, if not uses phone locale as initial language. + * Disable default/already selected language from dialog box + * Get ListPreference key and act accordingly for each ListPreference. + * saves value chosen by user to shared preferences + * to remember later and recall MainActivity to reflect language changes + * @param keyListPreference + */ + private fun prepareAppLanguages(keyListPreference: String) { + // Gets current language code from shared preferences + val languageCode = getCurrentLanguageCode(keyListPreference) + val recentLanguages = recentLanguagesDao.getRecentLanguages() + val selectedLanguages = hashMapOf() + + if (keyListPreference == "appUiDefaultLanguagePref") { + if (languageCode.isNullOrEmpty()) { + selectedLanguages[0] = Locale.getDefault().language + } else { + selectedLanguages[0] = languageCode + } + } else if (keyListPreference == "descriptionDefaultLanguagePref") { + if (languageCode.isNullOrEmpty()) { + selectedLanguages[0] = Locale.getDefault().language + } else { + selectedLanguages[0] = languageCode + } + } + + val languagesAdapter = LanguagesAdapter(requireActivity(), selectedLanguages) + + val dialog = Dialog(requireActivity()) + dialog.setContentView(R.layout.dialog_select_language) + 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) + languageHistoryListView = dialog.findViewById(R.id.language_history_list) + recentLanguagesTextView = dialog.findViewById(R.id.recent_searches) + separator = dialog.findViewById(R.id.separator) + + setUpRecentLanguagesSection(recentLanguages, selectedLanguages) + + listView.adapter = languagesAdapter + + editText.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(charSequence: CharSequence, start: Int, count: Int, after: Int) { + hideRecentLanguagesSection() + } + + override fun onTextChanged(charSequence: CharSequence, start: Int, before: Int, count: Int) { + languagesAdapter.filter.filter(charSequence) + } + + override fun afterTextChanged(editable: Editable?) {} + }) + + languageHistoryListView?.setOnItemClickListener { adapterView, _, position, _ -> + onRecentLanguageClicked(keyListPreference, dialog, adapterView, position) + } + + listView.setOnItemClickListener { adapterView, _, position, _ -> + val lCode = (adapterView.adapter as LanguagesAdapter).getLanguageCode(position) + val languageName = (adapterView.adapter as LanguagesAdapter).getLanguageName(position) + val isExists = recentLanguagesDao.findRecentLanguage(lCode) + if (isExists) { + recentLanguagesDao.deleteRecentLanguage(lCode) + } + recentLanguagesDao.addRecentLanguage(Language(languageName, lCode)) + saveLanguageValue(lCode, keyListPreference) + val defLocale = createLocale(lCode) + if (keyListPreference == "appUiDefaultLanguagePref") { + appUiLanguageListPreference?.summary = defLocale.getDisplayLanguage(defLocale) + setLocale(requireActivity(), lCode) + requireActivity().recreate() + val intent = Intent(requireActivity(), MainActivity::class.java) + startActivity(intent) + } else { + descriptionLanguageListPreference?.summary = defLocale.getDisplayLanguage(defLocale) + } + dialog.dismiss() + } + + dialog.setOnDismissListener { languagesAdapter.filter.filter("") } + } + + /** + * Set up recent languages section + * + * @param recentLanguages recently used languages + * @param selectedLanguages selected languages + */ + private fun setUpRecentLanguagesSection( + recentLanguages: List, + selectedLanguages: HashMap + ) { + if (recentLanguages.isEmpty()) { + languageHistoryListView?.visibility = View.GONE + recentLanguagesTextView?.visibility = View.GONE + separator?.visibility = View.GONE + } else { + if (recentLanguages.size > 5) { + for (i in recentLanguages.size - 1 downTo 5) { + recentLanguagesDao.deleteRecentLanguage(recentLanguages[i].languageCode) + } + } + languageHistoryListView?.visibility = View.VISIBLE + recentLanguagesTextView?.visibility = View.VISIBLE + separator?.visibility = View.VISIBLE + val recentLanguagesAdapter = RecentLanguagesAdapter( + requireActivity(), + recentLanguagesDao.getRecentLanguages(), + selectedLanguages + ) + languageHistoryListView?.adapter = recentLanguagesAdapter + } + } + + /** + * Handles click event for recent language section + */ + private fun onRecentLanguageClicked( + keyListPreference: String, + dialog: Dialog, + adapterView: AdapterView<*>, + position: Int + ) { + val recentLanguageCode = (adapterView.adapter as RecentLanguagesAdapter).getLanguageCode(position) + val recentLanguageName = (adapterView.adapter as RecentLanguagesAdapter).getLanguageName(position) + val isExists = recentLanguagesDao.findRecentLanguage(recentLanguageCode) + if (isExists) { + recentLanguagesDao.deleteRecentLanguage(recentLanguageCode) + } + recentLanguagesDao.addRecentLanguage(Language(recentLanguageName, recentLanguageCode)) + saveLanguageValue(recentLanguageCode, keyListPreference) + val defLocale = createLocale(recentLanguageCode) + if (keyListPreference == "appUiDefaultLanguagePref") { + appUiLanguageListPreference?.summary = defLocale.getDisplayLanguage(defLocale) + setLocale(requireActivity(), recentLanguageCode) + requireActivity().recreate() + val intent = Intent(requireActivity(), MainActivity::class.java) + startActivity(intent) + } else { + descriptionLanguageListPreference?.summary = defLocale.getDisplayLanguage(defLocale) + } + dialog.dismiss() + } + + /** + * Remove the section of recent languages + */ + private fun hideRecentLanguagesSection() { + languageHistoryListView?.visibility = View.GONE + recentLanguagesTextView?.visibility = View.GONE + separator?.visibility = View.GONE + } + + /** + * Changing the default app language with selected one and save it to SharedPreferences + */ + fun setLocale(activity: Activity, userSelectedValue: String) { + var selectedLanguage = userSelectedValue + if (selectedLanguage == "") { + selectedLanguage = Locale.getDefault().language + } + val locale = createLocale(selectedLanguage) + Locale.setDefault(locale) + val configuration = Configuration() + configuration.locale = locale + activity.baseContext.resources.updateConfiguration(configuration, activity.baseContext.resources.displayMetrics) + + val editor = activity.getSharedPreferences("Settings", MODE_PRIVATE).edit() + editor.putString("language", selectedLanguage) + editor.apply() + } + + /** + * Create Locale based on different types of language codes + * @param languageCode + * @return Locale and throws error for invalid language codes + */ + fun createLocale(languageCode: String): Locale { + val parts = languageCode.split("-") + return when (parts.size) { + 1 -> Locale(parts[0]) + 2 -> Locale(parts[0], parts[1]) + 3 -> Locale(parts[0], parts[1], parts[2]) + else -> throw IllegalArgumentException("Invalid language code: $languageCode") + } + } + + /** + * Save userSelected language in List Preference + * @param userSelectedValue + * @param preferenceKey + */ + private fun saveLanguageValue(userSelectedValue: String, preferenceKey: String) { + when (preferenceKey) { + "appUiDefaultLanguagePref" -> defaultKvStore.putString(Prefs.APP_UI_LANGUAGE, userSelectedValue) + "descriptionDefaultLanguagePref" -> defaultKvStore.putString(Prefs.DESCRIPTION_LANGUAGE, userSelectedValue) + } + } + + /** + * Gets current language code from shared preferences + * @param preferenceKey + * @return + */ + private fun getCurrentLanguageCode(preferenceKey: String): String? { + return when (preferenceKey) { + "appUiDefaultLanguagePref" -> defaultKvStore.getString( + Prefs.APP_UI_LANGUAGE, "" + ) + "descriptionDefaultLanguagePref" -> defaultKvStore.getString( + Prefs.DESCRIPTION_LANGUAGE, "" + ) + else -> null + } + } + + /** + * First checks for external storage permissions and then sends logs via email + */ + private fun checkPermissionsAndSendLogs() { + if ( + PermissionUtils.hasPermission( + requireActivity(), + PermissionUtils.PERMISSIONS_STORAGE + ) + ) { + commonsLogSender.send(requireActivity(), null) + } else { + requestExternalStoragePermissions() + } + } + + /** + * Requests external storage permissions and shows a toast stating that log collection has + * started + */ + private fun requestExternalStoragePermissions() { + Dexter.withActivity(requireActivity()) + .withPermissions(*PermissionUtils.PERMISSIONS_STORAGE) + .withListener(object : MultiplePermissionsListener { + override fun onPermissionsChecked(report: MultiplePermissionsReport) { + ViewUtil.showLongToast(requireActivity(), getString(R.string.log_collection_started)) + } + + override fun onPermissionRationaleShouldBeShown( + permissions: List, token: PermissionToken + ) { + // No action needed + } + }) + .onSameThread() + .check() + } + +}