From 0e3abd3537d7ba6aa741944386a2ff24acad34a9 Mon Sep 17 00:00:00 2001 From: Ariel Costas Date: Sat, 3 Sep 2022 21:53:01 +0200 Subject: [PATCH] Add better colors to be picked Colors picked are red, orange, yellow, green, sky and purple -100 and -900 from https://tailwindcss.com/docs/customizing-colors#default-color-palette Signed-off-by: Ariel Costas --- .../buran/ui/settings/SettingsFragment.kt | 1031 +++++++++-------- 1 file changed, 530 insertions(+), 501 deletions(-) diff --git a/app/src/main/java/corewala/buran/ui/settings/SettingsFragment.kt b/app/src/main/java/corewala/buran/ui/settings/SettingsFragment.kt index 32f7486..8de50d1 100644 --- a/app/src/main/java/corewala/buran/ui/settings/SettingsFragment.kt +++ b/app/src/main/java/corewala/buran/ui/settings/SettingsFragment.kt @@ -22,505 +22,534 @@ import dev.sasikanth.colorsheet.utils.ColorSheetUtils const val PREFS_SET_CLIENT_CERT_REQ = 20 -class SettingsFragment: PreferenceFragmentCompat(), Preference.OnPreferenceChangeListener { - - lateinit var prefs: SharedPreferences - - private lateinit var clientCertPref: Preference - - override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { - - prefs = preferenceManager.sharedPreferences - - val context = preferenceManager.context - val screen = preferenceManager.createPreferenceScreen(context) - - /** - * Buran App Settings - */ - val appCategory = PreferenceCategory(context) - appCategory.key = "app_category" - appCategory.title = getString(R.string.configure_buran) - screen.addPreference(appCategory) - - //Home --------------------------------------------- - val homePreference = EditTextPreference(context) - homePreference.title = getString(R.string.home_capsule) - homePreference.key = "home_capsule" - homePreference.dialogTitle = getString(R.string.home_capsule) - - val homecapsule = preferenceManager.sharedPreferences.getString( - "home_capsule", - Buran.DEFAULT_HOME_CAPSULE - )?.trim() - - homePreference.summary = if(homecapsule.isNullOrEmpty()){ - context.getString(R.string.no_home_capsule_set) - }else if( - !homecapsule.startsWith("gemini://") - or homecapsule.contains(" ") - or !homecapsule.contains(".") - ){ - context.getString(R.string.not_valid_address) - }else{ - homecapsule - } - - homePreference.positiveButtonText = getString(R.string.update) - homePreference.negativeButtonText = getString(R.string.cancel) - homePreference.setOnPreferenceChangeListener { _, newValue -> - val newHomecapsule = newValue.toString().trim() - homePreference.summary = if(newHomecapsule.isNullOrEmpty()){ - context.getString(R.string.no_home_capsule_set) - }else if( - !newHomecapsule.startsWith("gemini://") - or newHomecapsule.contains(" ") - or !newHomecapsule.contains(".") - ){ - context.getString(R.string.not_valid_address) - }else{ - newHomecapsule - } - true - } - homePreference.setOnBindEditTextListener{ editText -> - editText.imeOptions = EditorInfo.IME_ACTION_DONE - editText.setSelection(editText.text.toString().length)//Set caret position to end - } - appCategory.addPreference(homePreference) - - //Search --------------------------------------------- - val searchPreference = EditTextPreference(context) - searchPreference.title = getString(R.string.search_engine) - searchPreference.key = "search_base" - searchPreference.dialogTitle = getString(R.string.search_base) - - val searchengine = preferenceManager.sharedPreferences.getString( - "search_base", - Buran.DEFAULT_SEARCH_BASE - )?.trim() - - searchPreference.summary = if(searchengine.isNullOrEmpty()){ - Buran.DEFAULT_SEARCH_BASE - }else if( - !searchengine.startsWith("gemini://") - or searchengine.contains(" ") - or !searchengine.contains(".") - ){ - context.getString(R.string.not_valid_address) - }else if(!searchengine.endsWith("?")){ - context.getString(R.string.not_valid_search_string) - }else{ - searchengine - } - - searchPreference.positiveButtonText = getString(R.string.update) - searchPreference.negativeButtonText = getString(R.string.cancel) - searchPreference.setOnPreferenceChangeListener { _, newValue -> - val newSearchBase = newValue.toString().trim() - searchPreference.summary = if(newSearchBase.isNullOrEmpty()){ - Buran.DEFAULT_SEARCH_BASE - }else if( - !newSearchBase.startsWith("gemini://") - or newSearchBase.contains(" ") - or !newSearchBase.contains(".") - ){ - context.getString(R.string.not_valid_address) - }else if(!newSearchBase.endsWith("?")){ - context.getString(R.string.not_valid_search_string) - }else{ - newSearchBase - } - true - } - searchPreference.setOnBindEditTextListener{ editText -> - editText.imeOptions = EditorInfo.IME_ACTION_DONE - editText.setSelection(editText.text.toString().length)//Set caret position to end - } - appCategory.addPreference(searchPreference) - - //Updates --------------------------------------------- - val sideloadedHashCode = -899861527 - val isSideloaded = context.packageManager.getPackageInfo( - context.packageName, - PackageManager.GET_SIGNATURES - ).signatures[0].hashCode() == sideloadedHashCode - - val checkForUpdates = SwitchPreferenceCompat(context) - checkForUpdates.setDefaultValue(false) - checkForUpdates.key = "check_for_updates" - checkForUpdates.title = getString(R.string.check_for_updates) - checkForUpdates.isVisible = isSideloaded - appCategory.addPreference(checkForUpdates) - - //Certificates - buildClientCertificateSection(context, screen) - - //Appearance -------------------------------------------- - buildAppearanceSection(context, appCategory) - - //Accessibility ------------------------------------ - buildsAccessibility(context, screen) - - //Web ---------------------------------------------- - buildWebSection(context, screen) - - preferenceScreen = screen - } - - private fun buildWebSection(context: Context?, screen: PreferenceScreen){ - val webCategory = PreferenceCategory(context) - webCategory.key = "web_category" - webCategory.title = getString(R.string.web_content) - screen.addPreference(webCategory) - - val aboutCustomTabPref = Preference(context) - aboutCustomTabPref.summary = getString(R.string.web_content_label) - aboutCustomTabPref.isPersistent = false - aboutCustomTabPref.isSelectable = false - webCategory.addPreference(aboutCustomTabPref) - - val useCustomTabsPreference = SwitchPreferenceCompat(context) - useCustomTabsPreference.setDefaultValue(true) - useCustomTabsPreference.key = Buran.PREF_KEY_USE_CUSTOM_TAB - useCustomTabsPreference.title = getString(R.string.web_content_switch_label) - webCategory.addPreference(useCustomTabsPreference) - - val showInlineImages = SwitchPreferenceCompat(context) - showInlineImages.setDefaultValue(false) - showInlineImages.key = "show_inline_images" - showInlineImages.title = getString(R.string.show_inline_images) - webCategory.addPreference(showInlineImages) - - val httpGeminiProxy = EditTextPreference(context) - httpGeminiProxy.title = getString(R.string.http_proxy) - httpGeminiProxy.key = "http_proxy" - httpGeminiProxy.dialogTitle = getString(R.string.http_proxy) - - val httpProxy = preferenceManager.sharedPreferences.getString( - "http_proxy", - null - )?.trim() - - httpGeminiProxy.summary = if(httpProxy.isNullOrEmpty()){ - getString(R.string.no_http_proxy_set) - }else if( - !httpProxy.startsWith("gemini://") - or httpProxy.contains(" ") - or !httpProxy.contains(".") - ){ - getString(R.string.not_valid_address) - }else{ - httpProxy - } - - httpGeminiProxy.positiveButtonText = getString(R.string.update) - httpGeminiProxy.negativeButtonText = getString(R.string.cancel) - httpGeminiProxy.setOnPreferenceChangeListener { _, newValue -> - val newHomecapsule = newValue.toString().trim() - httpGeminiProxy.summary = if(newHomecapsule.isNullOrEmpty()){ - getString(R.string.no_http_proxy_set) - }else if( - !newHomecapsule.startsWith("gemini://") - or newHomecapsule.contains(" ") - or !newHomecapsule.contains(".") - ){ - getString(R.string.not_valid_address) - }else{ - newHomecapsule - } - true - } - httpGeminiProxy.setOnBindEditTextListener{ editText -> - editText.imeOptions = EditorInfo.IME_ACTION_DONE - editText.setSelection(editText.text.toString().length)//Set caret position to end - } - webCategory.addPreference(httpGeminiProxy) - } - - private fun buildAppearanceSection(context: Context?, appCategory: PreferenceCategory) { - val appearanceCategory = PreferenceCategory(context) - appearanceCategory.key = "appearance_category" - appearanceCategory.title = getString(R.string.appearance) - appCategory.addPreference(appearanceCategory) - - val themeLabels = mutableListOf() - val themeValues = mutableListOf() - themeLabels.add(getString(R.string.system_default)) - themeLabels.add(getString(R.string.light)) - themeLabels.add(getString(R.string.dark)) - themeValues.add("theme_FollowSystem") - themeValues.add("theme_Light") - themeValues.add("theme_Dark") - - val themePreference = ListPreference(context) - themePreference.key = "theme" - themePreference.setDialogTitle(R.string.theme) - themePreference.setTitle(R.string.theme) - themePreference.setSummary(R.string.prefs_override_theme) - themePreference.setDefaultValue("theme_FollowSystem") - themePreference.entries = themeLabels.toTypedArray() - themePreference.entryValues = themeValues.toTypedArray() - appearanceCategory.addPreference(themePreference) - - themePreference.setOnPreferenceChangeListener { _, theme -> - when (theme) { - "theme_FollowSystem" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) - "theme_Light" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO) - "theme_Dark" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES) - } - - true - } - - // Background colour picker - val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity) - val backgroundColourPreference = Preference(context) - backgroundColourPreference.key = "background_colour" - backgroundColourPreference.setTitle(R.string.prefs_override_page_background_title) - backgroundColourPreference.setSummary(R.string.prefs_override_page_background) - backgroundColourPreference.setOnPreferenceClickListener { - ColorSheet().colorPicker( - selectedColor = sharedPreferences.getInt( - "background_colour_id", - ColorSheet.NO_COLOR - ), - colors = listOf(Color.BLUE, Color.RED, Color.GREEN).toIntArray(), - noColorOption = true, - listener = { - val editor = sharedPreferences.edit() - editor.putString(backgroundColourPreference.key, ColorSheetUtils.colorToHex(it)) - editor.putInt("background_colour_id", it) - editor.commit() - backgroundColourPreference.callChangeListener(it) - } - ).show(parentFragmentManager) - true - } - - appearanceCategory.addPreference(backgroundColourPreference) - } - - private fun buildsAccessibility(context: Context?, screen: PreferenceScreen){ - val accessibilityCategory = PreferenceCategory(context) - accessibilityCategory.key = "accessibility_category" - accessibilityCategory.title = getString(R.string.accessibility) - screen.addPreference(accessibilityCategory) - - //Accessibility - inline icons - val showInlineIconsPreference = SwitchPreferenceCompat(context) - showInlineIconsPreference.setDefaultValue(true) - showInlineIconsPreference.key = "show_inline_icons" - showInlineIconsPreference.title = getString(R.string.show_inline_icons) - accessibilityCategory.addPreference(showInlineIconsPreference) - - //Accessibility - full-width buttons - val showLinkButtonsPreference = SwitchPreferenceCompat(context) - showLinkButtonsPreference.setDefaultValue(false) - showLinkButtonsPreference.key = "show_link_buttons" - showLinkButtonsPreference.title = getString(R.string.show_link_buttons) - accessibilityCategory.addPreference(showLinkButtonsPreference) - - //Accessibility - gemtext attention guides - val attentionGuidingText = SwitchPreferenceCompat(context) - attentionGuidingText.setDefaultValue(false) - attentionGuidingText.key = "use_attention_guides" - attentionGuidingText.title = getString(R.string.use_attention_guides) - accessibilityCategory.addPreference(attentionGuidingText) - } - - private fun buildClientCertificateSection(context: Context?, screen: PreferenceScreen) { - - val certificateCategory = PreferenceCategory(context) - certificateCategory.key = "certificate_category" - certificateCategory.title = getString(R.string.client_certificate) - screen.addPreference(certificateCategory) - - val aboutPref = Preference(context) - aboutPref.summary = getString(R.string.pkcs_notice) - aboutPref.isPersistent = false - aboutPref.isSelectable = false - certificateCategory.addPreference(aboutPref) - - clientCertPref = Preference(context) - clientCertPref.title = getString(R.string.client_certificate) - clientCertPref.key = Buran.PREF_KEY_CLIENT_CERT_HUMAN_READABLE - - val clientCertUriHumanReadable = preferenceManager.sharedPreferences.getString( - Buran.PREF_KEY_CLIENT_CERT_HUMAN_READABLE, - null - ) - - val hasCert = clientCertUriHumanReadable != null - if (!hasCert) { - clientCertPref.summary = getString(R.string.tap_to_select_client_certificate) - } else { - clientCertPref.summary = clientCertUriHumanReadable - } - - clientCertPref.setOnPreferenceClickListener { - val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { - addCategory(Intent.CATEGORY_OPENABLE) - addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - type = "*/*" - } - startActivityForResult(intent, PREFS_SET_CLIENT_CERT_REQ) - true - } - - certificateCategory.addPreference(clientCertPref) - - - val clientCertPassword = EditTextPreference(context) - clientCertPassword.key = Buran.PREF_KEY_CLIENT_CERT_PASSWORD - clientCertPassword.title = getString(R.string.client_certificate_password) - - var certPassword = preferenceManager.sharedPreferences.getString( - Buran.PREF_KEY_CLIENT_CERT_PASSWORD, - null - ) - - clientCertPassword.dialogTitle = getString(R.string.client_certificate_password) - if (certPassword != null && certPassword.isNotEmpty()) { - clientCertPassword.summary = getDots(certPassword) - } else { - clientCertPassword.summary = getString(R.string.no_password) - } - clientCertPassword.isVisible = !preferenceManager.sharedPreferences.getBoolean("use_biometrics", false) - certificateCategory.addPreference(clientCertPassword) - - val useBiometrics = SwitchPreferenceCompat(context) - useBiometrics.setDefaultValue(false) - useBiometrics.key = "use_biometrics" - useBiometrics.title = getString(R.string.biometric_cert_verification) - useBiometrics.isVisible = false - certificateCategory.addPreference(useBiometrics) - - - val passwordCiphertext = EditTextPreference(context) - passwordCiphertext.key = "password_ciphertext" - passwordCiphertext.isVisible = false - certificateCategory.addPreference(passwordCiphertext) - - val passwordInitVector = EditTextPreference(context) - passwordInitVector.key = "password_init_vector" - passwordInitVector.isVisible = false - certificateCategory.addPreference(passwordInitVector) - - if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P){ - useBiometrics.isVisible = (certPassword?.isNotEmpty() ?: false) or useBiometrics.isChecked - - useBiometrics.setOnPreferenceChangeListener { _, newValue -> - val biometricManager = BuranBiometricManager() - - val callback = object : BiometricPrompt.AuthenticationCallback() { - override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { - super.onAuthenticationError(errorCode, errString) - println("Authentication error: $errorCode: $errString") - useBiometrics.isChecked = !(newValue as Boolean) - } - override fun onAuthenticationFailed() { - super.onAuthenticationFailed() - println("Authentication failed") - useBiometrics.isChecked = !(newValue as Boolean) - } - override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { - super.onAuthenticationSucceeded(result) - println("Authentication succeeded") - - if(newValue as Boolean){ - println(certPassword) - val encryptedData = biometricManager.encryptData(certPassword!!, result.cryptoObject?.cipher!!) - val ciphertext = encryptedData.ciphertext - val initializationVector = encryptedData.initializationVector - passwordInitVector.text = initializationVector.contentToString() - passwordCiphertext.text = ciphertext.contentToString() - clientCertPassword.text = null - }else{ - val ciphertext = biometricManager.decodeByteArray(passwordCiphertext.text) - clientCertPassword.text = biometricManager.decryptData(ciphertext, result.cryptoObject?.cipher!!) - clientCertPassword.summary = getDots(clientCertPassword.text) - } - clientCertPassword.isVisible = !(newValue as Boolean) - } - } - - biometricManager.createBiometricPrompt(requireContext(), this, null, callback) - - if(newValue as Boolean){ - biometricManager.authenticateToEncryptData() - }else{ - val initializationVector = biometricManager.decodeByteArray(passwordInitVector.text) - biometricManager.authenticateToDecryptData(initializationVector) - } - - true - } - } - - clientCertPassword.setOnPreferenceChangeListener { _, newValue -> - val passphrase = "$newValue" - if (passphrase.isEmpty()) { - clientCertPassword.summary = getString(R.string.no_password) - useBiometrics.isVisible = false - } else { - clientCertPassword.summary = getDots(passphrase) - useBiometrics.isVisible = true - } - certPassword = passphrase - true//update the value - } - } - - private fun getDots(value: String): String { - val sb = StringBuilder() - repeat(value.length){ - sb.append("•") - } - return sb.toString() - } - - override fun onPreferenceChange(preference: Preference?, newValue: Any?): Boolean { - return false - } - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - if(requestCode == PREFS_SET_CLIENT_CERT_REQ && resultCode == RESULT_OK){ - data?.data?.also { uri -> - preferenceManager.sharedPreferences.edit().putString( - Buran.PREF_KEY_CLIENT_CERT_URI, - uri.toString() - ).apply() - persistPermissions(uri) - findFilename(uri) - } - - } - super.onActivityResult(requestCode, resultCode, data) - } - - private fun persistPermissions(uri: Uri) { - val contentResolver = requireContext().contentResolver - - val takeFlags: Int = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION - contentResolver.takePersistableUriPermission(uri, takeFlags) - } - - private fun findFilename(uri: Uri) { - - var readableReference = uri.toString() - if (uri.scheme == "content") { - requireContext().contentResolver.query(uri, null, null, null, null).use { cursor -> - if (cursor != null && cursor.moveToFirst()) { - readableReference = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)) - } - } - } - - preferenceManager.sharedPreferences.edit().putString( - Buran.PREF_KEY_CLIENT_CERT_HUMAN_READABLE, - readableReference - ).apply() - clientCertPref.summary = readableReference - } +class SettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferenceChangeListener { + + lateinit var prefs: SharedPreferences + + private lateinit var clientCertPref: Preference + + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + + prefs = preferenceManager.sharedPreferences + + val context = preferenceManager.context + val screen = preferenceManager.createPreferenceScreen(context) + + /** + * Buran App Settings + */ + val appCategory = PreferenceCategory(context) + appCategory.key = "app_category" + appCategory.title = getString(R.string.configure_buran) + screen.addPreference(appCategory) + + //Home --------------------------------------------- + val homePreference = EditTextPreference(context) + homePreference.title = getString(R.string.home_capsule) + homePreference.key = "home_capsule" + homePreference.dialogTitle = getString(R.string.home_capsule) + + val homecapsule = preferenceManager.sharedPreferences.getString( + "home_capsule", + Buran.DEFAULT_HOME_CAPSULE + )?.trim() + + homePreference.summary = if (homecapsule.isNullOrEmpty()) { + context.getString(R.string.no_home_capsule_set) + } else if ( + !homecapsule.startsWith("gemini://") + or homecapsule.contains(" ") + or !homecapsule.contains(".") + ) { + context.getString(R.string.not_valid_address) + } else { + homecapsule + } + + homePreference.positiveButtonText = getString(R.string.update) + homePreference.negativeButtonText = getString(R.string.cancel) + homePreference.setOnPreferenceChangeListener { _, newValue -> + val newHomecapsule = newValue.toString().trim() + homePreference.summary = if (newHomecapsule.isNullOrEmpty()) { + context.getString(R.string.no_home_capsule_set) + } else if ( + !newHomecapsule.startsWith("gemini://") + or newHomecapsule.contains(" ") + or !newHomecapsule.contains(".") + ) { + context.getString(R.string.not_valid_address) + } else { + newHomecapsule + } + true + } + homePreference.setOnBindEditTextListener { editText -> + editText.imeOptions = EditorInfo.IME_ACTION_DONE + editText.setSelection(editText.text.toString().length)//Set caret position to end + } + appCategory.addPreference(homePreference) + + //Search --------------------------------------------- + val searchPreference = EditTextPreference(context) + searchPreference.title = getString(R.string.search_engine) + searchPreference.key = "search_base" + searchPreference.dialogTitle = getString(R.string.search_base) + + val searchengine = preferenceManager.sharedPreferences.getString( + "search_base", + Buran.DEFAULT_SEARCH_BASE + )?.trim() + + searchPreference.summary = if (searchengine.isNullOrEmpty()) { + Buran.DEFAULT_SEARCH_BASE + } else if ( + !searchengine.startsWith("gemini://") + or searchengine.contains(" ") + or !searchengine.contains(".") + ) { + context.getString(R.string.not_valid_address) + } else if (!searchengine.endsWith("?")) { + context.getString(R.string.not_valid_search_string) + } else { + searchengine + } + + searchPreference.positiveButtonText = getString(R.string.update) + searchPreference.negativeButtonText = getString(R.string.cancel) + searchPreference.setOnPreferenceChangeListener { _, newValue -> + val newSearchBase = newValue.toString().trim() + searchPreference.summary = if (newSearchBase.isNullOrEmpty()) { + Buran.DEFAULT_SEARCH_BASE + } else if ( + !newSearchBase.startsWith("gemini://") + or newSearchBase.contains(" ") + or !newSearchBase.contains(".") + ) { + context.getString(R.string.not_valid_address) + } else if (!newSearchBase.endsWith("?")) { + context.getString(R.string.not_valid_search_string) + } else { + newSearchBase + } + true + } + searchPreference.setOnBindEditTextListener { editText -> + editText.imeOptions = EditorInfo.IME_ACTION_DONE + editText.setSelection(editText.text.toString().length)//Set caret position to end + } + appCategory.addPreference(searchPreference) + + //Updates --------------------------------------------- + val sideloadedHashCode = -899861527 + val isSideloaded = context.packageManager.getPackageInfo( + context.packageName, + PackageManager.GET_SIGNATURES + ).signatures[0].hashCode() == sideloadedHashCode + + val checkForUpdates = SwitchPreferenceCompat(context) + checkForUpdates.setDefaultValue(false) + checkForUpdates.key = "check_for_updates" + checkForUpdates.title = getString(R.string.check_for_updates) + checkForUpdates.isVisible = isSideloaded + appCategory.addPreference(checkForUpdates) + + //Certificates + buildClientCertificateSection(context, screen) + + //Appearance -------------------------------------------- + buildAppearanceSection(context, appCategory) + + //Accessibility ------------------------------------ + buildsAccessibility(context, screen) + + //Web ---------------------------------------------- + buildWebSection(context, screen) + + preferenceScreen = screen + } + + private fun buildWebSection(context: Context?, screen: PreferenceScreen) { + val webCategory = PreferenceCategory(context) + webCategory.key = "web_category" + webCategory.title = getString(R.string.web_content) + screen.addPreference(webCategory) + + val aboutCustomTabPref = Preference(context) + aboutCustomTabPref.summary = getString(R.string.web_content_label) + aboutCustomTabPref.isPersistent = false + aboutCustomTabPref.isSelectable = false + webCategory.addPreference(aboutCustomTabPref) + + val useCustomTabsPreference = SwitchPreferenceCompat(context) + useCustomTabsPreference.setDefaultValue(true) + useCustomTabsPreference.key = Buran.PREF_KEY_USE_CUSTOM_TAB + useCustomTabsPreference.title = getString(R.string.web_content_switch_label) + webCategory.addPreference(useCustomTabsPreference) + + val showInlineImages = SwitchPreferenceCompat(context) + showInlineImages.setDefaultValue(false) + showInlineImages.key = "show_inline_images" + showInlineImages.title = getString(R.string.show_inline_images) + webCategory.addPreference(showInlineImages) + + val httpGeminiProxy = EditTextPreference(context) + httpGeminiProxy.title = getString(R.string.http_proxy) + httpGeminiProxy.key = "http_proxy" + httpGeminiProxy.dialogTitle = getString(R.string.http_proxy) + + val httpProxy = preferenceManager.sharedPreferences.getString( + "http_proxy", + null + )?.trim() + + httpGeminiProxy.summary = if (httpProxy.isNullOrEmpty()) { + getString(R.string.no_http_proxy_set) + } else if ( + !httpProxy.startsWith("gemini://") + or httpProxy.contains(" ") + or !httpProxy.contains(".") + ) { + getString(R.string.not_valid_address) + } else { + httpProxy + } + + httpGeminiProxy.positiveButtonText = getString(R.string.update) + httpGeminiProxy.negativeButtonText = getString(R.string.cancel) + httpGeminiProxy.setOnPreferenceChangeListener { _, newValue -> + val newHomecapsule = newValue.toString().trim() + httpGeminiProxy.summary = if (newHomecapsule.isNullOrEmpty()) { + getString(R.string.no_http_proxy_set) + } else if ( + !newHomecapsule.startsWith("gemini://") + or newHomecapsule.contains(" ") + or !newHomecapsule.contains(".") + ) { + getString(R.string.not_valid_address) + } else { + newHomecapsule + } + true + } + httpGeminiProxy.setOnBindEditTextListener { editText -> + editText.imeOptions = EditorInfo.IME_ACTION_DONE + editText.setSelection(editText.text.toString().length)//Set caret position to end + } + webCategory.addPreference(httpGeminiProxy) + } + + private fun buildAppearanceSection(context: Context?, appCategory: PreferenceCategory) { + val appearanceCategory = PreferenceCategory(context) + appearanceCategory.key = "appearance_category" + appearanceCategory.title = getString(R.string.appearance) + appCategory.addPreference(appearanceCategory) + + val themeLabels = mutableListOf() + val themeValues = mutableListOf() + themeLabels.add(getString(R.string.system_default)) + themeLabels.add(getString(R.string.light)) + themeLabels.add(getString(R.string.dark)) + themeValues.add("theme_FollowSystem") + themeValues.add("theme_Light") + themeValues.add("theme_Dark") + + val themePreference = ListPreference(context) + themePreference.key = "theme" + themePreference.setDialogTitle(R.string.theme) + themePreference.setTitle(R.string.theme) + themePreference.setSummary(R.string.prefs_override_theme) + themePreference.setDefaultValue("theme_FollowSystem") + themePreference.entries = themeLabels.toTypedArray() + themePreference.entryValues = themeValues.toTypedArray() + appearanceCategory.addPreference(themePreference) + + themePreference.setOnPreferenceChangeListener { _, theme -> + when (theme) { + "theme_FollowSystem" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) + "theme_Light" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO) + "theme_Dark" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES) + } + + true + } + + // Background colour picker + val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity) + val backgroundColourPreference = Preference(context) + backgroundColourPreference.key = "background_colour" + backgroundColourPreference.setTitle(R.string.prefs_override_page_background_title) + backgroundColourPreference.setSummary(R.string.prefs_override_page_background) + backgroundColourPreference.setOnPreferenceClickListener { + ColorSheet().colorPicker( + selectedColor = sharedPreferences.getInt( + "background_colour_id", + ColorSheet.NO_COLOR + ), + colors = listOf( + Color.parseColor("#fee2e2"), + Color.parseColor("#ffedd5"), + Color.parseColor("#fef9c3"), + Color.parseColor("#dcfce7"), + Color.parseColor("#e0f2fe"), + Color.parseColor("#f3e8ff"), + + Color.parseColor("#7f1d1d"), + Color.parseColor("#7c2d12"), + Color.parseColor("#713f12"), + Color.parseColor("#14532d"), + Color.parseColor("#0c4a6e"), + Color.parseColor("#581c87"), + + ).toIntArray(), + noColorOption = true, + listener = { + val editor = sharedPreferences.edit() + editor.putString(backgroundColourPreference.key, ColorSheetUtils.colorToHex(it)) + editor.putInt("background_colour_id", it) + editor.commit() + backgroundColourPreference.callChangeListener(it) + } + ).show(parentFragmentManager) + true + } + + appearanceCategory.addPreference(backgroundColourPreference) + } + + private fun buildsAccessibility(context: Context?, screen: PreferenceScreen) { + val accessibilityCategory = PreferenceCategory(context) + accessibilityCategory.key = "accessibility_category" + accessibilityCategory.title = getString(R.string.accessibility) + screen.addPreference(accessibilityCategory) + + //Accessibility - inline icons + val showInlineIconsPreference = SwitchPreferenceCompat(context) + showInlineIconsPreference.setDefaultValue(true) + showInlineIconsPreference.key = "show_inline_icons" + showInlineIconsPreference.title = getString(R.string.show_inline_icons) + accessibilityCategory.addPreference(showInlineIconsPreference) + + //Accessibility - full-width buttons + val showLinkButtonsPreference = SwitchPreferenceCompat(context) + showLinkButtonsPreference.setDefaultValue(false) + showLinkButtonsPreference.key = "show_link_buttons" + showLinkButtonsPreference.title = getString(R.string.show_link_buttons) + accessibilityCategory.addPreference(showLinkButtonsPreference) + + //Accessibility - gemtext attention guides + val attentionGuidingText = SwitchPreferenceCompat(context) + attentionGuidingText.setDefaultValue(false) + attentionGuidingText.key = "use_attention_guides" + attentionGuidingText.title = getString(R.string.use_attention_guides) + accessibilityCategory.addPreference(attentionGuidingText) + } + + private fun buildClientCertificateSection(context: Context?, screen: PreferenceScreen) { + + val certificateCategory = PreferenceCategory(context) + certificateCategory.key = "certificate_category" + certificateCategory.title = getString(R.string.client_certificate) + screen.addPreference(certificateCategory) + + val aboutPref = Preference(context) + aboutPref.summary = getString(R.string.pkcs_notice) + aboutPref.isPersistent = false + aboutPref.isSelectable = false + certificateCategory.addPreference(aboutPref) + + clientCertPref = Preference(context) + clientCertPref.title = getString(R.string.client_certificate) + clientCertPref.key = Buran.PREF_KEY_CLIENT_CERT_HUMAN_READABLE + + val clientCertUriHumanReadable = preferenceManager.sharedPreferences.getString( + Buran.PREF_KEY_CLIENT_CERT_HUMAN_READABLE, + null + ) + + val hasCert = clientCertUriHumanReadable != null + if (!hasCert) { + clientCertPref.summary = getString(R.string.tap_to_select_client_certificate) + } else { + clientCertPref.summary = clientCertUriHumanReadable + } + + clientCertPref.setOnPreferenceClickListener { + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + type = "*/*" + } + startActivityForResult(intent, PREFS_SET_CLIENT_CERT_REQ) + true + } + + certificateCategory.addPreference(clientCertPref) + + + val clientCertPassword = EditTextPreference(context) + clientCertPassword.key = Buran.PREF_KEY_CLIENT_CERT_PASSWORD + clientCertPassword.title = getString(R.string.client_certificate_password) + + var certPassword = preferenceManager.sharedPreferences.getString( + Buran.PREF_KEY_CLIENT_CERT_PASSWORD, + null + ) + + clientCertPassword.dialogTitle = getString(R.string.client_certificate_password) + if (certPassword != null && certPassword.isNotEmpty()) { + clientCertPassword.summary = getDots(certPassword) + } else { + clientCertPassword.summary = getString(R.string.no_password) + } + clientCertPassword.isVisible = + !preferenceManager.sharedPreferences.getBoolean("use_biometrics", false) + certificateCategory.addPreference(clientCertPassword) + + val useBiometrics = SwitchPreferenceCompat(context) + useBiometrics.setDefaultValue(false) + useBiometrics.key = "use_biometrics" + useBiometrics.title = getString(R.string.biometric_cert_verification) + useBiometrics.isVisible = false + certificateCategory.addPreference(useBiometrics) + + + val passwordCiphertext = EditTextPreference(context) + passwordCiphertext.key = "password_ciphertext" + passwordCiphertext.isVisible = false + certificateCategory.addPreference(passwordCiphertext) + + val passwordInitVector = EditTextPreference(context) + passwordInitVector.key = "password_init_vector" + passwordInitVector.isVisible = false + certificateCategory.addPreference(passwordInitVector) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + useBiometrics.isVisible = + (certPassword?.isNotEmpty() ?: false) or useBiometrics.isChecked + + useBiometrics.setOnPreferenceChangeListener { _, newValue -> + val biometricManager = BuranBiometricManager() + + val callback = object : BiometricPrompt.AuthenticationCallback() { + override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { + super.onAuthenticationError(errorCode, errString) + println("Authentication error: $errorCode: $errString") + useBiometrics.isChecked = !(newValue as Boolean) + } + + override fun onAuthenticationFailed() { + super.onAuthenticationFailed() + println("Authentication failed") + useBiometrics.isChecked = !(newValue as Boolean) + } + + override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { + super.onAuthenticationSucceeded(result) + println("Authentication succeeded") + + if (newValue as Boolean) { + println(certPassword) + val encryptedData = biometricManager.encryptData( + certPassword!!, + result.cryptoObject?.cipher!! + ) + val ciphertext = encryptedData.ciphertext + val initializationVector = encryptedData.initializationVector + passwordInitVector.text = initializationVector.contentToString() + passwordCiphertext.text = ciphertext.contentToString() + clientCertPassword.text = null + } else { + val ciphertext = + biometricManager.decodeByteArray(passwordCiphertext.text) + clientCertPassword.text = biometricManager.decryptData( + ciphertext, + result.cryptoObject?.cipher!! + ) + clientCertPassword.summary = getDots(clientCertPassword.text) + } + clientCertPassword.isVisible = !(newValue as Boolean) + } + } + + biometricManager.createBiometricPrompt(requireContext(), this, null, callback) + + if (newValue as Boolean) { + biometricManager.authenticateToEncryptData() + } else { + val initializationVector = + biometricManager.decodeByteArray(passwordInitVector.text) + biometricManager.authenticateToDecryptData(initializationVector) + } + + true + } + } + + clientCertPassword.setOnPreferenceChangeListener { _, newValue -> + val passphrase = "$newValue" + if (passphrase.isEmpty()) { + clientCertPassword.summary = getString(R.string.no_password) + useBiometrics.isVisible = false + } else { + clientCertPassword.summary = getDots(passphrase) + useBiometrics.isVisible = true + } + certPassword = passphrase + true//update the value + } + } + + private fun getDots(value: String): String { + val sb = StringBuilder() + repeat(value.length) { + sb.append("•") + } + return sb.toString() + } + + override fun onPreferenceChange(preference: Preference?, newValue: Any?): Boolean { + return false + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + if (requestCode == PREFS_SET_CLIENT_CERT_REQ && resultCode == RESULT_OK) { + data?.data?.also { uri -> + preferenceManager.sharedPreferences.edit().putString( + Buran.PREF_KEY_CLIENT_CERT_URI, + uri.toString() + ).apply() + persistPermissions(uri) + findFilename(uri) + } + + } + super.onActivityResult(requestCode, resultCode, data) + } + + private fun persistPermissions(uri: Uri) { + val contentResolver = requireContext().contentResolver + + val takeFlags: Int = + Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION + contentResolver.takePersistableUriPermission(uri, takeFlags) + } + + private fun findFilename(uri: Uri) { + + var readableReference = uri.toString() + if (uri.scheme == "content") { + requireContext().contentResolver.query(uri, null, null, null, null).use { cursor -> + if (cursor != null && cursor.moveToFirst()) { + readableReference = + cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)) + } + } + } + + preferenceManager.sharedPreferences.edit().putString( + Buran.PREF_KEY_CLIENT_CERT_HUMAN_READABLE, + readableReference + ).apply() + clientCertPref.summary = readableReference + } }