SettingsFragment.kt 6.7 KB

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