Browse Source

ui: Hide developer settings behind 7 clicks

Peter Cai 1 year ago
parent
commit
5f0dbe3098

+ 65 - 2
app-common/src/main/java/im/angry/openeuicc/ui/SettingsFragment.kt

@@ -3,23 +3,48 @@ package im.angry.openeuicc.ui
 import android.content.Intent
 import android.content.Intent
 import android.net.Uri
 import android.net.Uri
 import android.os.Bundle
 import android.os.Bundle
+import android.widget.Toast
 import androidx.datastore.preferences.core.Preferences
 import androidx.datastore.preferences.core.Preferences
 import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.lifecycleScope
 import androidx.preference.CheckBoxPreference
 import androidx.preference.CheckBoxPreference
 import androidx.preference.Preference
 import androidx.preference.Preference
+import androidx.preference.PreferenceCategory
 import androidx.preference.PreferenceFragmentCompat
 import androidx.preference.PreferenceFragmentCompat
 import im.angry.openeuicc.common.R
 import im.angry.openeuicc.common.R
 import im.angry.openeuicc.util.*
 import im.angry.openeuicc.util.*
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.runBlocking
 
 
 class SettingsFragment: PreferenceFragmentCompat() {
 class SettingsFragment: PreferenceFragmentCompat() {
+    private lateinit var developerPref: PreferenceCategory
+
+    // Hidden developer options switch
+    private var numClicks = 0
+    private var lastClickTimestamp = -1L
+    private var lastToast: Toast? = null
+
     override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
     override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
         setPreferencesFromResource(R.xml.pref_settings, rootKey)
         setPreferencesFromResource(R.xml.pref_settings, rootKey)
 
 
+        developerPref = findPreference("pref_developer")!!
+
+        // Show / hide developer preference based on whether it is enabled
+        lifecycleScope.launch {
+            preferenceRepository.developerOptionsEnabledFlow.onEach {
+                developerPref.isVisible = it
+            }.collect()
+        }
+
         findPreference<Preference>("pref_info_app_version")
         findPreference<Preference>("pref_info_app_version")
-            ?.summary = requireContext().selfAppVersion
+            ?.apply {
+                summary = requireContext().selfAppVersion
+
+                // Enable developer options when this is clicked for 7 times
+                setOnPreferenceClickListener(this@SettingsFragment::onAppVersionClicked)
+            }
 
 
         findPreference<Preference>("pref_info_source_code")
         findPreference<Preference>("pref_info_source_code")
             ?.setOnPreferenceClickListener {
             ?.setOnPreferenceClickListener {
@@ -48,7 +73,7 @@ class SettingsFragment: PreferenceFragmentCompat() {
         findPreference<CheckBoxPreference>("pref_advanced_verbose_logging")
         findPreference<CheckBoxPreference>("pref_advanced_verbose_logging")
             ?.bindBooleanFlow(preferenceRepository.verboseLoggingFlow, PreferenceKeys.VERBOSE_LOGGING)
             ?.bindBooleanFlow(preferenceRepository.verboseLoggingFlow, PreferenceKeys.VERBOSE_LOGGING)
 
 
-        findPreference<CheckBoxPreference>("pref_advanced_experimental_download_wizard")
+        findPreference<CheckBoxPreference>("pref_developer_experimental_download_wizard")
             ?.bindBooleanFlow(preferenceRepository.experimentalDownloadWizardFlow, PreferenceKeys.EXPERIMENTAL_DOWNLOAD_WIZARD)
             ?.bindBooleanFlow(preferenceRepository.experimentalDownloadWizardFlow, PreferenceKeys.EXPERIMENTAL_DOWNLOAD_WIZARD)
     }
     }
 
 
@@ -57,6 +82,44 @@ class SettingsFragment: PreferenceFragmentCompat() {
         setupRootViewInsets(requireView().requireViewById(androidx.preference.R.id.recycler_view))
         setupRootViewInsets(requireView().requireViewById(androidx.preference.R.id.recycler_view))
     }
     }
 
 
+    @Suppress("UNUSED_PARAMETER")
+    private fun onAppVersionClicked(pref: Preference): Boolean {
+        if (developerPref.isVisible) return false
+        val now = System.currentTimeMillis()
+        if (now - lastClickTimestamp >= 1000) {
+            numClicks = 1
+        } else {
+            numClicks++
+        }
+        lastClickTimestamp = now
+
+        if (numClicks == 7) {
+            lifecycleScope.launch {
+                preferenceRepository.updatePreference(
+                    PreferenceKeys.DEVELOPER_OPTIONS_ENABLED,
+                    true
+                )
+
+                lastToast?.cancel()
+                Toast.makeText(
+                    requireContext(),
+                    R.string.developer_options_enabled,
+                    Toast.LENGTH_SHORT
+                ).show()
+            }
+        } else if (numClicks > 1) {
+            lastToast?.cancel()
+            lastToast = Toast.makeText(
+                requireContext(),
+                getString(R.string.developer_options_steps, 7 - numClicks),
+                Toast.LENGTH_SHORT
+            )
+            lastToast!!.show()
+        }
+
+        return true
+    }
+
     private fun CheckBoxPreference.bindBooleanFlow(flow: Flow<Boolean>, key: Preferences.Key<Boolean>) {
     private fun CheckBoxPreference.bindBooleanFlow(flow: Flow<Boolean>, key: Preferences.Key<Boolean>) {
         lifecycleScope.launch {
         lifecycleScope.launch {
             flow.collect { isChecked = it }
             flow.collect { isChecked = it }

+ 5 - 0
app-common/src/main/java/im/angry/openeuicc/util/PreferenceUtils.kt

@@ -25,6 +25,7 @@ object PreferenceKeys {
     val NOTIFICATION_SWITCH = booleanPreferencesKey("notification_switch")
     val NOTIFICATION_SWITCH = booleanPreferencesKey("notification_switch")
     val DISABLE_SAFEGUARD_REMOVABLE_ESIM = booleanPreferencesKey("disable_safeguard_removable_esim")
     val DISABLE_SAFEGUARD_REMOVABLE_ESIM = booleanPreferencesKey("disable_safeguard_removable_esim")
     val VERBOSE_LOGGING = booleanPreferencesKey("verbose_logging")
     val VERBOSE_LOGGING = booleanPreferencesKey("verbose_logging")
+    val DEVELOPER_OPTIONS_ENABLED = booleanPreferencesKey("developer_options_enabled")
     val EXPERIMENTAL_DOWNLOAD_WIZARD = booleanPreferencesKey("experimental_download_wizard")
     val EXPERIMENTAL_DOWNLOAD_WIZARD = booleanPreferencesKey("experimental_download_wizard")
 }
 }
 
 
@@ -49,6 +50,10 @@ class PreferenceRepository(context: Context) {
     val verboseLoggingFlow: Flow<Boolean> =
     val verboseLoggingFlow: Flow<Boolean> =
         dataStore.data.map { it[PreferenceKeys.VERBOSE_LOGGING] ?: false }
         dataStore.data.map { it[PreferenceKeys.VERBOSE_LOGGING] ?: false }
 
 
+    // ---- Developer Options ----
+    val developerOptionsEnabledFlow: Flow<Boolean> =
+        dataStore.data.map { it[PreferenceKeys.DEVELOPER_OPTIONS_ENABLED] ?: false }
+
     val experimentalDownloadWizardFlow: Flow<Boolean> =
     val experimentalDownloadWizardFlow: Flow<Boolean> =
         dataStore.data.map { it[PreferenceKeys.EXPERIMENTAL_DOWNLOAD_WIZARD] ?: false }
         dataStore.data.map { it[PreferenceKeys.EXPERIMENTAL_DOWNLOAD_WIZARD] ?: false }
 
 

+ 6 - 2
app-common/src/main/res/values/strings.xml

@@ -101,6 +101,9 @@
     <string name="logs_save">Save</string>
     <string name="logs_save">Save</string>
     <string name="logs_filename_template">Logs at %s</string>
     <string name="logs_filename_template">Logs at %s</string>
 
 
+    <string name="developer_options_steps">You are %d steps away from being a developer.</string>
+    <string name="developer_options_enabled">You are now a developer!</string>
+
     <string name="pref_settings">Settings</string>
     <string name="pref_settings">Settings</string>
     <string name="pref_notifications">Notifications</string>
     <string name="pref_notifications">Notifications</string>
     <string name="pref_notifications_desc">eSIM profile operations send notifications to the carrier. Fine-tune this behavior as needed here.</string>
     <string name="pref_notifications_desc">eSIM profile operations send notifications to the carrier. Fine-tune this behavior as needed here.</string>
@@ -117,8 +120,9 @@
     <string name="pref_advanced_verbose_logging_desc">Enable verbose logs, which may contain sensitive information. Only share your logs with someone you trust after turning this on.</string>
     <string name="pref_advanced_verbose_logging_desc">Enable verbose logs, which may contain sensitive information. Only share your logs with someone you trust after turning this on.</string>
     <string name="pref_advanced_logs">Logs</string>
     <string name="pref_advanced_logs">Logs</string>
     <string name="pref_advanced_logs_desc">View recent debug logs of the application</string>
     <string name="pref_advanced_logs_desc">View recent debug logs of the application</string>
-    <string name="pref_advanced_experimental_download_wizard">Experimental Download Wizard</string>
-    <string name="pref_advanced_experimental_download_wizard_desc">Enable the experimental new download wizard. Note that it is not fully working yet.</string>
+    <string name="pref_developer">Developer Options</string>
+    <string name="pref_developer_experimental_download_wizard">Experimental Download Wizard</string>
+    <string name="pref_developer_experimental_download_wizard_desc">Enable the experimental new download wizard. Note that it is not fully working yet.</string>
     <string name="pref_info">Info</string>
     <string name="pref_info">Info</string>
     <string name="pref_info_app_version">App Version</string>
     <string name="pref_info_app_version">App Version</string>
     <string name="pref_info_source_code">Source Code</string>
     <string name="pref_info_source_code">Source Code</string>

+ 11 - 3
app-common/src/main/res/xml/pref_settings.xml

@@ -42,11 +42,19 @@
             app:title="@string/pref_advanced_logs"
             app:title="@string/pref_advanced_logs"
             app:summary="@string/pref_advanced_logs_desc" />
             app:summary="@string/pref_advanced_logs_desc" />
 
 
+    </PreferenceCategory>
+
+    <PreferenceCategory
+        app:key="pref_developer"
+        app:title="@string/pref_developer"
+        app:iconSpaceReserved="false">
+
         <CheckBoxPreference
         <CheckBoxPreference
-            app:key="pref_advanced_experimental_download_wizard"
+            app:key="pref_developer_experimental_download_wizard"
             app:iconSpaceReserved="false"
             app:iconSpaceReserved="false"
-            app:title="@string/pref_advanced_experimental_download_wizard"
-            app:summary="@string/pref_advanced_experimental_download_wizard_desc" />
+            app:title="@string/pref_developer_experimental_download_wizard"
+            app:summary="@string/pref_developer_experimental_download_wizard_desc" />
+
     </PreferenceCategory>
     </PreferenceCategory>
 
 
     <PreferenceCategory
     <PreferenceCategory