SettingsFragment.kt 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. package im.angry.openeuicc.ui
  2. import android.content.Intent
  3. import android.content.pm.PackageManager
  4. import android.net.Uri
  5. import android.os.Build
  6. import android.os.Bundle
  7. import android.provider.Settings
  8. import android.widget.Toast
  9. import androidx.lifecycle.lifecycleScope
  10. import androidx.preference.CheckBoxPreference
  11. import androidx.preference.Preference
  12. import androidx.preference.PreferenceCategory
  13. import androidx.preference.PreferenceFragmentCompat
  14. import im.angry.openeuicc.common.R
  15. import im.angry.openeuicc.util.*
  16. import kotlinx.coroutines.flow.collect
  17. import kotlinx.coroutines.flow.onEach
  18. import kotlinx.coroutines.launch
  19. import kotlinx.coroutines.runBlocking
  20. open class SettingsFragment: PreferenceFragmentCompat() {
  21. private lateinit var developerPref: PreferenceCategory
  22. // Hidden developer options switch
  23. private var numClicks = 0
  24. private var lastClickTimestamp = -1L
  25. private var lastToast: Toast? = null
  26. override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
  27. setPreferencesFromResource(R.xml.pref_settings, rootKey)
  28. developerPref = requirePreference("pref_developer")
  29. // Show / hide developer preference based on whether it is enabled
  30. lifecycleScope.launch {
  31. preferenceRepository.developerOptionsEnabledFlow
  32. .onEach { developerPref.isVisible = it }
  33. .collect()
  34. }
  35. requirePreference<Preference>("pref_info_app_version").apply {
  36. summary = requireContext().selfAppVersion
  37. // Enable developer options when this is clicked for 7 times
  38. setOnPreferenceClickListener(::onAppVersionClicked)
  39. }
  40. requirePreference<Preference>("pref_advanced_language").apply {
  41. if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return@apply
  42. isVisible = true
  43. intent = Intent(Settings.ACTION_APP_LOCALE_SETTINGS).apply {
  44. data = Uri.fromParts("package", requireContext().packageName, null)
  45. }
  46. }
  47. requirePreference<Preference>("pref_advanced_logs").apply {
  48. intent = Intent(requireContext(), LogsActivity::class.java)
  49. }
  50. requirePreference<CheckBoxPreference>("pref_notifications_download")
  51. .bindBooleanFlow(preferenceRepository.notificationDownloadFlow)
  52. requirePreference<CheckBoxPreference>("pref_notifications_delete")
  53. .bindBooleanFlow(preferenceRepository.notificationDeleteFlow)
  54. requirePreference<CheckBoxPreference>("pref_notifications_switch")
  55. .bindBooleanFlow(preferenceRepository.notificationSwitchFlow)
  56. requirePreference<CheckBoxPreference>("pref_advanced_disable_safeguard_removable_esim")
  57. .bindBooleanFlow(preferenceRepository.disableSafeguardFlow)
  58. requirePreference<CheckBoxPreference>("pref_advanced_verbose_logging")
  59. .bindBooleanFlow(preferenceRepository.verboseLoggingFlow)
  60. requirePreference<CheckBoxPreference>("pref_developer_unfiltered_profile_list")
  61. .bindBooleanFlow(preferenceRepository.unfilteredProfileListFlow)
  62. requirePreference<CheckBoxPreference>("pref_developer_ignore_tls_certificate")
  63. .bindBooleanFlow(preferenceRepository.ignoreTLSCertificateFlow)
  64. requirePreference<CheckBoxPreference>("pref_developer_euicc_memory_reset").apply {
  65. isVisible = context.packageManager.hasSystemFeature(PackageManager.FEATURE_USB_HOST)
  66. bindBooleanFlow(preferenceRepository.euiccMemoryResetFlow)
  67. }
  68. }
  69. protected fun <T : Preference> requirePreference(key: CharSequence) =
  70. findPreference<T>(key)!!
  71. override fun onStart() {
  72. super.onStart()
  73. setupRootViewInsets(requireView().requireViewById(R.id.recycler_view))
  74. }
  75. @Suppress("UNUSED_PARAMETER")
  76. private fun onAppVersionClicked(pref: Preference): Boolean {
  77. if (developerPref.isVisible) return false
  78. val now = System.currentTimeMillis()
  79. if (now - lastClickTimestamp >= 1000) {
  80. numClicks = 1
  81. } else {
  82. numClicks++
  83. }
  84. lastClickTimestamp = now
  85. if (numClicks == 7) {
  86. lifecycleScope.launch {
  87. preferenceRepository.developerOptionsEnabledFlow.updatePreference(true)
  88. lastToast?.cancel()
  89. Toast.makeText(
  90. requireContext(),
  91. R.string.developer_options_enabled,
  92. Toast.LENGTH_SHORT
  93. ).show()
  94. }
  95. } else if (numClicks > 1) {
  96. lastToast?.cancel()
  97. lastToast = Toast.makeText(
  98. requireContext(),
  99. getString(R.string.developer_options_steps, 7 - numClicks),
  100. Toast.LENGTH_SHORT
  101. )
  102. lastToast!!.show()
  103. }
  104. return true
  105. }
  106. protected fun CheckBoxPreference.bindBooleanFlow(flow: PreferenceFlowWrapper<Boolean>) {
  107. lifecycleScope.launch {
  108. flow.collect { isChecked = it }
  109. }
  110. setOnPreferenceChangeListener { _, newValue ->
  111. runBlocking {
  112. flow.updatePreference(newValue as Boolean)
  113. }
  114. true
  115. }
  116. }
  117. protected fun mergePreferenceOverlay(overlayKey: String, targetKey: String) {
  118. val overlayCat = requirePreference<PreferenceCategory>(overlayKey)
  119. val targetCat = requirePreference<PreferenceCategory>(targetKey)
  120. val prefs = buildList {
  121. for (i in 0..<overlayCat.preferenceCount) {
  122. add(overlayCat.getPreference(i))
  123. }
  124. }
  125. prefs.forEach {
  126. overlayCat.removePreference(it)
  127. targetCat.addPreference(it)
  128. }
  129. overlayCat.parent?.removePreference(overlayCat)
  130. }
  131. }