diff --git a/app/src/main/java/com/itsaky/androidide/activities/PluginManagerActivity.kt b/app/src/main/java/com/itsaky/androidide/activities/PluginManagerActivity.kt index a3129fbffb..eb3d8f71a2 100644 --- a/app/src/main/java/com/itsaky/androidide/activities/PluginManagerActivity.kt +++ b/app/src/main/java/com/itsaky/androidide/activities/PluginManagerActivity.kt @@ -24,7 +24,7 @@ import com.itsaky.androidide.R import com.itsaky.androidide.adapters.PluginListAdapter import com.itsaky.androidide.app.EdgeToEdgeIDEActivity import com.itsaky.androidide.databinding.ActivityPluginManagerBinding -import com.itsaky.androidide.idetooltips.TooltipManager +import com.itsaky.androidide.idetooltips.TooltipCategory import com.itsaky.androidide.idetooltips.TooltipTag import com.itsaky.androidide.plugins.PluginInfo import com.itsaky.androidide.ui.models.PluginManagerUiEffect @@ -32,6 +32,7 @@ import com.itsaky.androidide.ui.models.PluginManagerUiEvent import com.itsaky.androidide.utils.DURATION_INDEFINITE import com.itsaky.androidide.utils.DialogUtils.showRestartPrompt import com.itsaky.androidide.utils.UrlManager +import com.itsaky.androidide.utils.displayTooltipOnLongPress import com.itsaky.androidide.utils.errorIcon import com.itsaky.androidide.utils.flashError import com.itsaky.androidide.utils.flashSuccess @@ -119,9 +120,8 @@ class PluginManagerActivity : EdgeToEdgeIDEActivity() { override fun onCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.menu_plugin_manager, menu) binding.toolbar.post { - binding.toolbar.findViewById(R.id.action_discover_plugins)?.setOnLongClickListener { view -> - TooltipManager.showIdeCategoryTooltip(this, view, TooltipTag.PLUGIN_MANAGER) - true + binding.toolbar.findViewById(R.id.action_discover_plugins)?.let { view -> + view.displayTooltipOnLongPress(this, view, TooltipCategory.CATEGORY_IDE, TooltipTag.PLUGIN_MANAGER_DOWNLOAD) } } return true @@ -177,25 +177,13 @@ class PluginManagerActivity : EdgeToEdgeIDEActivity() { } private fun setupTooltipLongPress() { - val showTooltip: (View) -> Unit = { view -> - TooltipManager.showIdeCategoryTooltip(this, view, TooltipTag.PLUGIN_MANAGER) - } - binding.toolbar.setOnLongClickListener { - showTooltip(it) - true - } - binding.fabInstallPlugin.setOnLongClickListener { - showTooltip(it) - true - } - binding.emptyState.setOnLongClickListener { - showTooltip(it) - true - } - binding.recyclerView.setOnLongClickListener { - showTooltip(it) - true + val show: (View, String) -> Unit = { view, tag -> + view.displayTooltipOnLongPress(this, view, TooltipCategory.CATEGORY_IDE, tag) } + show(binding.toolbar, TooltipTag.PLUGIN_MANAGER_TOOLBAR) + show(binding.fabInstallPlugin, TooltipTag.PLUGIN_MANAGER_FAB_INSTALL) + show(binding.emptyState, TooltipTag.PLUGIN_MANAGER_EMPTY_STATE) + show(binding.recyclerView, TooltipTag.PLUGIN_MANAGER_LIST) } private fun setupFeedbackButton() { diff --git a/app/src/main/java/com/itsaky/androidide/activities/PreferencesActivity.kt b/app/src/main/java/com/itsaky/androidide/activities/PreferencesActivity.kt index 925313d930..12ea83e25c 100755 --- a/app/src/main/java/com/itsaky/androidide/activities/PreferencesActivity.kt +++ b/app/src/main/java/com/itsaky/androidide/activities/PreferencesActivity.kt @@ -17,9 +17,6 @@ package com.itsaky.androidide.activities import android.os.Bundle -import android.view.GestureDetector -import android.view.HapticFeedbackConstants -import android.view.MotionEvent import android.view.View import androidx.core.graphics.Insets import androidx.core.os.BundleCompat @@ -30,6 +27,7 @@ import com.itsaky.androidide.app.EdgeToEdgeIDEActivity import com.itsaky.androidide.databinding.ActivityPreferencesBinding import com.itsaky.androidide.fragments.IDEPreferencesFragment import com.itsaky.androidide.idetooltips.TooltipManager +import com.itsaky.androidide.idetooltips.TooltipTag import com.itsaky.androidide.preferences.PluginSettingsEntryPreference import com.itsaky.androidide.preferences.addRootPreferences import com.itsaky.androidide.preferences.pluginSettingsPreferences @@ -50,20 +48,6 @@ class PreferencesActivity : EdgeToEdgeIDEActivity() { */ private var contributedPreferences: List? = null - private val gestureDetector by lazy { - GestureDetector( - this, - object : GestureDetector.SimpleOnGestureListener() { - override fun onLongPress(e: MotionEvent) { - binding.root.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) - val currentFragment = supportFragmentManager.findFragmentById(binding.fragmentContainer.id) as? IDEPreferencesFragment - val tooltipTag = currentFragment?.getCurrentScreenTooltip() ?: "" - TooltipManager.showIdeCategoryTooltip(this@PreferencesActivity, binding.root, tooltipTag) - } - }, - ) - } - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -72,6 +56,22 @@ class PreferencesActivity : EdgeToEdgeIDEActivity() { supportActionBar!!.setDisplayHomeAsUpEnabled(true) binding.toolbar.setNavigationOnClickListener { onBackPressedDispatcher.onBackPressed() } + binding.toolbar.setOnLongClickListener { + TooltipManager.showIdeCategoryTooltip(this, binding.toolbar, currentScreenTooltipTag()) + true + } + + // Fallback for a long-press that lands below a short screen's content: the RecyclerView is + // wrap_content-sized to its own rows, so on a short screen (e.g. Git, About) it doesn't fill + // this NestedScrollView, and the remaining space would otherwise have no tooltip at all. + binding.fragmentContainerParent.setOnLongClickListener { + TooltipManager.showIdeCategoryTooltip( + this, + binding.fragmentContainerParent, + currentScreenTooltipTag(), + ) + true + } feedbackButtonManager = FeedbackButtonManager( @@ -107,6 +107,7 @@ class PreferencesActivity : EdgeToEdgeIDEActivity() { IDEPreferencesFragment.EXTRA_CHILDREN, ArrayList(prefs.children), ) + args.putString(IDEPreferencesFragment.EXTRA_SCREEN_TOOLTIP_TAG, TooltipTag.PREFS_TOP) // A fresh instance every time: arguments cannot be set on a fragment whose state was saved. loadFragment(IDEPreferencesFragment().also { it.arguments = args }) @@ -157,16 +158,17 @@ class PreferencesActivity : EdgeToEdgeIDEActivity() { super.loadFragment(fragment, binding.fragmentContainer.id) } + /** The tag of whichever [IDEPreferencesFragment] screen is currently on top, or [TooltipTag.PREFS_TOP]. */ + private fun currentScreenTooltipTag(): String { + val fragment = supportFragmentManager.findFragmentById(binding.fragmentContainer.id) + return (fragment as? IDEPreferencesFragment)?.screenTooltipTag ?: TooltipTag.PREFS_TOP + } + override fun onDestroy() { super.onDestroy() _binding = null } - override fun dispatchTouchEvent(ev: MotionEvent): Boolean { - gestureDetector.onTouchEvent(ev) - return super.dispatchTouchEvent(ev) - } - override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) contributedPreferences?.let { diff --git a/app/src/main/java/com/itsaky/androidide/adapters/PluginListAdapter.kt b/app/src/main/java/com/itsaky/androidide/adapters/PluginListAdapter.kt index a0a39f460b..cfd6a9bfd0 100644 --- a/app/src/main/java/com/itsaky/androidide/adapters/PluginListAdapter.kt +++ b/app/src/main/java/com/itsaky/androidide/adapters/PluginListAdapter.kt @@ -14,9 +14,10 @@ import com.bumptech.glide.Glide import com.bumptech.glide.signature.ObjectKey import com.itsaky.androidide.R import com.itsaky.androidide.databinding.ItemPluginBinding -import com.itsaky.androidide.idetooltips.TooltipManager +import com.itsaky.androidide.idetooltips.TooltipCategory import com.itsaky.androidide.idetooltips.TooltipTag import com.itsaky.androidide.plugins.PluginInfo +import com.itsaky.androidide.utils.displayTooltipOnLongPress import com.itsaky.androidide.utils.isSystemInDarkMode import java.io.File @@ -115,17 +116,24 @@ class PluginListAdapter( btnMenu.setOnClickListener { view -> showPopupMenu(view, plugin) } + btnMenu.displayTooltipOnLongPress( + itemView.context, + btnMenu, + TooltipCategory.CATEGORY_IDE, + TooltipTag.PLUGIN_MANAGER_ITEM_MENU, + ) // Setup item click for details root.setOnClickListener { onActionClick(plugin, Action.DETAILS) } - // Long-press for Plugin Manager tooltip - root.setOnLongClickListener { - TooltipManager.showIdeCategoryTooltip(it.context, it, TooltipTag.PLUGIN_MANAGER) - true - } + root.displayTooltipOnLongPress( + itemView.context, + root, + TooltipCategory.CATEGORY_IDE, + TooltipTag.PLUGIN_MANAGER_ITEM, + ) } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/IDEPreferencesFragment.kt b/app/src/main/java/com/itsaky/androidide/fragments/IDEPreferencesFragment.kt index f663568c15..f64e7764f2 100755 --- a/app/src/main/java/com/itsaky/androidide/fragments/IDEPreferencesFragment.kt +++ b/app/src/main/java/com/itsaky/androidide/fragments/IDEPreferencesFragment.kt @@ -18,92 +18,153 @@ package com.itsaky.androidide.fragments import android.os.Bundle +import android.view.HapticFeedbackConstants import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.preference.PreferenceCategory import androidx.preference.PreferenceGroup +import androidx.preference.PreferenceGroupAdapter +import androidx.recyclerview.widget.RecyclerView import com.google.android.material.transition.MaterialSharedAxis -import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_DEVELOPER -import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_EDITOR -import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_EDITOR_XML -import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_GENERAL -import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_GRADLE -import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_TERMUX +import com.itsaky.androidide.idetooltips.TooltipManager import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_TOP import com.itsaky.androidide.preferences.IPreference import com.itsaky.androidide.preferences.IPreferenceGroup import com.itsaky.androidide.preferences.IPreferenceScreen +import com.itsaky.androidide.utils.onLongPress class IDEPreferencesFragment : BasePreferenceFragment() { - - private var children: List = emptyList() - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) - reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) - exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) - return super.onCreateView(inflater, container, savedInstanceState) - } - - override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { - super.onCreatePreferences(savedInstanceState, rootKey) - - if (context == null) { - return - } - - @Suppress("DEPRECATION") - this.children = arguments?.getParcelableArrayList(EXTRA_CHILDREN) ?: emptyList() - - preferenceScreen.removeAll() - addChildren(this.children, preferenceScreen) - } - - private fun addChildren(children: List, pref: PreferenceGroup) { - for (child in children) { - val preference = child.onCreateView(requireContext()) - if (child is IPreferenceScreen) { - preference.fragment = IDEPreferencesFragment::class.java.name - preference.extras.putParcelableArrayList(EXTRA_CHILDREN, ArrayList(child.children)) - - - pref.addPreference(preference) - continue - } - - if (child is IPreferenceGroup) { - pref.addPreference(preference as PreferenceCategory) - addChildren(child.children, preference) - continue - } - - - pref.addPreference(preference) - } - } - - fun getCurrentScreenTooltip(): String { - val firstChildKey = children.firstOrNull()?.key - return when (firstChildKey) { - "idepref_configure" -> PREFS_TOP - "idepref_general_interface" -> PREFS_GENERAL - "idepref_editor_common" -> PREFS_EDITOR - "idepref_build_gradle" -> PREFS_GRADLE - "idepref_build_gradleCommands" -> PREFS_GRADLE - "ide.preferences.terminal.debugging" -> PREFS_TERMUX - "ide.prefs.developerOptions.debugging" -> PREFS_DEVELOPER - "idepref_xml_trimFinalNewLine" -> PREFS_EDITOR_XML - else -> PREFS_TOP - } - } - - companion object { - const val EXTRA_CHILDREN = "ide.preferences.fragment.children" - } + /** Every preference in this screen, including nested categories' children, keyed by its key. */ + internal var tooltipTagsByKey: Map = emptyMap() + + /** + * This screen's own tag - the fallback for a long-press that lands on empty RecyclerView + * space (no row under the touch point) or on a row with no tooltipTag of its own. Also read + * by [com.itsaky.androidide.activities.PreferencesActivity] to resolve the toolbar's and the + * scroll container's long-press tooltip to whichever screen is currently showing. + */ + internal var screenTooltipTag: String = PREFS_TOP + private set + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View { + enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) + reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) + exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) + return super.onCreateView(inflater, container, savedInstanceState) + } + + override fun onCreatePreferences( + savedInstanceState: Bundle?, + rootKey: String?, + ) { + super.onCreatePreferences(savedInstanceState, rootKey) + + if (context == null) { + return + } + + @Suppress("DEPRECATION") + val children: List = arguments?.getParcelableArrayList(EXTRA_CHILDREN) ?: emptyList() + this.tooltipTagsByKey = collectTooltipTags(children) + this.screenTooltipTag = arguments?.getString(EXTRA_SCREEN_TOOLTIP_TAG)?.takeIf { it.isNotEmpty() } ?: PREFS_TOP + + preferenceScreen.removeAll() + addChildren(children, preferenceScreen) + } + + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { + super.onViewCreated(view, savedInstanceState) + + // Captured once: re-reading the `listView` property from inside the callback (which can + // fire after a delay) would hit a field PreferenceFragmentCompat clears in onDestroyView. + val recyclerView = listView + + recyclerView.onLongPress { e -> + if (!isAdded) { + return@onLongPress + } + val ctx = context ?: return@onLongPress + + val row = recyclerView.findChildViewUnder(e.x, e.y) + val tag = row?.let { resolveTooltipTag(recyclerView, it) } ?: screenTooltipTag + val anchor = row ?: recyclerView + + anchor.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) + TooltipManager.showIdeCategoryTooltip(ctx, anchor, tag) + } + } + + /** The row's own tooltipTag, or null if there's no row at that position or it has none. */ + internal fun resolveTooltipTag( + recyclerView: RecyclerView, + row: View, + ): String? { + val position = recyclerView.getChildAdapterPosition(row) + if (position == RecyclerView.NO_POSITION) { + return null + } + + val key = (recyclerView.adapter as? PreferenceGroupAdapter)?.getItem(position)?.key ?: return null + return tooltipTagsByKey[key]?.takeIf { it.isNotEmpty() } + } + + /** + * Recursively walks [children], mapping each preference key to its tooltip tag. + * Nested [IPreferenceScreen] nodes are not descended into. Entries with an empty + * tooltip tag are kept as-is. Throws if two preferences in the tree share a key. + */ + internal fun collectTooltipTags(children: List): Map { + val map = mutableMapOf() + + fun visit(items: List) { + for (item in items) { + check(item.key !in map) { "Duplicate preference key in this screen's tree: ${item.key}" } + map[item.key] = item.tooltipTag + if (item is IPreferenceGroup && item !is IPreferenceScreen) { + visit(item.children) + } + } + } + + visit(children) + return map + } + + private fun addChildren( + children: List, + pref: PreferenceGroup, + ) { + for (child in children) { + val preference = child.onCreateView(requireContext()) + if (child is IPreferenceScreen) { + preference.fragment = IDEPreferencesFragment::class.java.name + preference.extras.putParcelableArrayList(EXTRA_CHILDREN, ArrayList(child.children)) + preference.extras.putString(EXTRA_SCREEN_TOOLTIP_TAG, child.tooltipTag) + + pref.addPreference(preference) + continue + } + + if (child is IPreferenceGroup) { + pref.addPreference(preference as PreferenceCategory) + addChildren(child.children, preference) + continue + } + + pref.addPreference(preference) + } + } + + companion object { + const val EXTRA_CHILDREN = "ide.preferences.fragment.children" + const val EXTRA_SCREEN_TOOLTIP_TAG = "ide.preferences.fragment.screenTooltipTag" + } } - diff --git a/app/src/main/java/com/itsaky/androidide/preferences/aboutPrefExts.kt b/app/src/main/java/com/itsaky/androidide/preferences/aboutPrefExts.kt index 049531ca0f..5ccdd1a7ef 100644 --- a/app/src/main/java/com/itsaky/androidide/preferences/aboutPrefExts.kt +++ b/app/src/main/java/com/itsaky/androidide/preferences/aboutPrefExts.kt @@ -19,16 +19,18 @@ package com.itsaky.androidide.preferences import android.content.Intent import com.itsaky.androidide.activities.AboutActivity +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_ABOUT import com.itsaky.androidide.resources.R private const val KEY_ABOUT = "idepref_about" val about = - SimpleClickablePreference( - key = KEY_ABOUT, - title = R.string.idepref_about_title, - summary = R.string.idepref_about_summary - ) { - it.context.startActivity(Intent(it.context, AboutActivity::class.java)) - true - } \ No newline at end of file +SimpleClickablePreference( + key = KEY_ABOUT, + title = R.string.idepref_about_title, + summary = R.string.idepref_about_summary, + tooltipTag = PREFS_ABOUT, +) { + it.context.startActivity(Intent(it.context, AboutActivity::class.java)) + true +} diff --git a/app/src/main/java/com/itsaky/androidide/preferences/buildAndRunPrefExts.kt b/app/src/main/java/com/itsaky/androidide/preferences/buildAndRunPrefExts.kt index f3e1de6d40..0d2db90372 100644 --- a/app/src/main/java/com/itsaky/androidide/preferences/buildAndRunPrefExts.kt +++ b/app/src/main/java/com/itsaky/androidide/preferences/buildAndRunPrefExts.kt @@ -18,7 +18,16 @@ package com.itsaky.androidide.preferences import com.itsaky.androidide.R -import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_GRADLE +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_BUILDRUN_AUTOLAUNCH +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_BUILDRUN_FLAGS +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_BUILDRUN_FLAGS_BUILDCACHE +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_BUILDRUN_FLAGS_DEBUG +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_BUILDRUN_FLAGS_INFO +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_BUILDRUN_FLAGS_OFFLINE +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_BUILDRUN_FLAGS_SCAN +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_BUILDRUN_FLAGS_STACKTRACE +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_BUILDRUN_FLAGS_WARNINGMODEALL +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_BUILD_RUN import com.itsaky.androidide.preferences.internal.BuildPreferences.GRADLE_COMMANDS import com.itsaky.androidide.preferences.internal.BuildPreferences.LAUNCH_APP_AFTER_INSTALL import com.itsaky.androidide.preferences.internal.BuildPreferences.isBuildCacheEnabled @@ -33,76 +42,77 @@ import com.itsaky.androidide.resources.R.drawable import com.itsaky.androidide.resources.R.string import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize -import kotlin.reflect.KMutableProperty0 @Parcelize class BuildAndRunPreferences( - override val key: String = "idepref_build_n_run", - override val title: Int = string.idepref_build_title, - override val summary: Int? = string.idepref_buildnrun_summary, - override val children: List = mutableListOf(), + override val key: String = "idepref_build_n_run", + override val title: Int = string.idepref_build_title, + override val summary: Int? = string.idepref_buildnrun_summary, + override val children: List = mutableListOf(), + override val tooltipTag: String = PREFS_BUILD_RUN, ) : IPreferenceScreen() { - init { - addPreference(GradleOptions()) - addPreference(RunOptions()) - } + init { + addPreference(GradleOptions()) + addPreference(RunOptions()) + } } @Parcelize private class GradleOptions( - override val key: String = "idepref_build_gradle", - override val title: Int = string.gradle, - override val children: List = mutableListOf(), + override val key: String = "idepref_build_gradle", + override val title: Int = string.gradle, + override val children: List = mutableListOf(), ) : IPreferenceGroup() { - init { - addPreference(GradleCommands()) - } +init { + addPreference(GradleCommands()) +} } @Parcelize private class GradleCommands( - override val key: String = GRADLE_COMMANDS, - override val title: Int = string.idepref_build_customgradlecommands_title, - override val summary: Int? = string.idepref_build_customgradlecommands_summary, - override val icon: Int? = drawable.ic_bash_commands, + override val key: String = GRADLE_COMMANDS, + override val title: Int = string.idepref_build_customgradlecommands_title, + override val summary: Int? = string.idepref_build_customgradlecommands_summary, + override val icon: Int? = drawable.ic_bash_commands, ) : PropertyBasedMultiChoicePreference() { - @IgnoredOnParcel - override val tooltipTag: String = PREFS_GRADLE + @IgnoredOnParcel + override val tooltipTag: String = PREFS_BUILDRUN_FLAGS - override fun getProperties(): Map> { - return linkedMapOf( - "--stacktrace" to ::isStacktraceEnabled, - "--info" to ::isInfoEnabled, - "--debug" to ::isDebugEnabled, - "--scan" to ::isScanEnabled, - "--warning-mode all" to ::isWarningModeAllEnabled, - "--build-cache" to ::isBuildCacheEnabled, - "--offline" to ::isOfflineEnabled, - ) - } + override fun getProperties(): List { + return listOf( + PropertyEntry("--stacktrace", ::isStacktraceEnabled, PREFS_BUILDRUN_FLAGS_STACKTRACE), + PropertyEntry("--info", ::isInfoEnabled, PREFS_BUILDRUN_FLAGS_INFO), + PropertyEntry("--debug", ::isDebugEnabled, PREFS_BUILDRUN_FLAGS_DEBUG), + PropertyEntry("--scan", ::isScanEnabled, PREFS_BUILDRUN_FLAGS_SCAN), + PropertyEntry("--warning-mode all", ::isWarningModeAllEnabled, PREFS_BUILDRUN_FLAGS_WARNINGMODEALL), + PropertyEntry("--build-cache", ::isBuildCacheEnabled, PREFS_BUILDRUN_FLAGS_BUILDCACHE), + PropertyEntry("--offline", ::isOfflineEnabled, PREFS_BUILDRUN_FLAGS_OFFLINE), + ) + } } @Parcelize private class RunOptions( - override val key: String = "ide.build.runOptions", - override val title: Int = R.string.title_run_options, - override val children: List = mutableListOf() + override val key: String = "ide.build.runOptions", + override val title: Int = R.string.title_run_options, + override val children: List = mutableListOf(), ) : IPreferenceGroup() { - init { - addPreference(LaunchAppAfterInstall()) - } + init { + addPreference(LaunchAppAfterInstall()) + } } @Parcelize private class LaunchAppAfterInstall( - override val key: String = LAUNCH_APP_AFTER_INSTALL, - override val title: Int = R.string.idepref_launchAppAfterInstall_title, - override val summary: Int? = R.string.idepref_launchAppAfterInstall_summary, - override val icon: Int? = drawable.ic_open_external + override val key: String = LAUNCH_APP_AFTER_INSTALL, + override val title: Int = R.string.idepref_launchAppAfterInstall_title, + override val summary: Int? = R.string.idepref_launchAppAfterInstall_summary, + override val icon: Int? = drawable.ic_open_external, + override val tooltipTag: String = PREFS_BUILDRUN_AUTOLAUNCH, ) : - SwitchPreference(setValue = ::launchAppAfterInstall::set, getValue = ::launchAppAfterInstall::get) +SwitchPreference(setValue = ::launchAppAfterInstall::set, getValue = ::launchAppAfterInstall::get) diff --git a/app/src/main/java/com/itsaky/androidide/preferences/commonPrefExts.kt b/app/src/main/java/com/itsaky/androidide/preferences/commonPrefExts.kt index 7bd59dd0bd..bd4dbd11f1 100644 --- a/app/src/main/java/com/itsaky/androidide/preferences/commonPrefExts.kt +++ b/app/src/main/java/com/itsaky/androidide/preferences/commonPrefExts.kt @@ -23,27 +23,30 @@ import kotlin.reflect.KMutableProperty0 internal abstract class PropertyBasedMultiChoicePreference : MultiChoicePreference() { - abstract fun getProperties(): Map> - - override fun getEntries(preference: Preference): Array { - val properties = getProperties() - val entries = Array(properties.size) { PreferenceChoices.Entry.EMPTY } - - var index = 0 - properties.forEach { (key, property) -> - entries[index] = PreferenceChoices.Entry(key, property.get(), property) - ++index - } - - return entries - } - - override fun onChoicesConfirmed( - preference: Preference, - entries: Array - ) { - entries.forEach { entry -> - uncheckedCast>(entry.data).set(entry.isChecked) - } - } -} \ No newline at end of file +/** One checkbox: its label, the property it toggles, and its own tooltip tag (if any). */ +data class PropertyEntry( + val label: String, + val property: KMutableProperty0, + val tooltipTag: String = "", +) + +/** The checkboxes for this screen, in display order; each entry's [PropertyEntry.tooltipTag] is that choice's own long-press help. */ +abstract fun getProperties(): List + +override fun getEntries(preference: Preference): Array { + val properties = getProperties() + return Array(properties.size) { i -> + val entry = properties[i] + PreferenceChoices.Entry(entry.label, entry.property.get(), entry.property, entry.tooltipTag) + } +} + +override fun onChoicesConfirmed( + preference: Preference, + entries: Array +) { + entries.forEach { entry -> + uncheckedCast>(entry.data).set(entry.isChecked) + } +} +} diff --git a/app/src/main/java/com/itsaky/androidide/preferences/developerOptionsPrefExts.kt b/app/src/main/java/com/itsaky/androidide/preferences/developerOptionsPrefExts.kt index 0f37c26dc3..bae79edf12 100644 --- a/app/src/main/java/com/itsaky/androidide/preferences/developerOptionsPrefExts.kt +++ b/app/src/main/java/com/itsaky/androidide/preferences/developerOptionsPrefExts.kt @@ -18,46 +18,53 @@ package com.itsaky.androidide.preferences import com.itsaky.androidide.R +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_DEVOPTIONS +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_DEVOPTIONS_DUMPLOGS +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_DEVOPTIONS_LOGSENDER import com.itsaky.androidide.preferences.internal.DevOpsPreferences import kotlinx.parcelize.Parcelize @Parcelize internal class DeveloperOptionsScreen( - override val key: String = DevOpsPreferences.KEY_DEVOPTS, - override val title: Int = R.string.title_developer_options, - override val summary: Int? = R.string.idepref_devOptions_summary, - override val children: List = mutableListOf()) : IPreferenceScreen() { - - init { - addPreference(DumpLogsPreference()) - addPreference(EnableLogSenderPreference()) - } +override val key: String = DevOpsPreferences.KEY_DEVOPTS, +override val title: Int = R.string.title_developer_options, +override val summary: Int? = R.string.idepref_devOptions_summary, +override val children: List = mutableListOf(), +override val tooltipTag: String = PREFS_DEVOPTIONS, +) : IPreferenceScreen() { + +init { + addPreference(DumpLogsPreference()) + addPreference(EnableLogSenderPreference()) +} } @Parcelize internal class DebuggingPreferences( - override val key: String = DevOpsPreferences.KEY_DEVOPTS_DEBUGGING, - override val title: Int = R.string.idepref_group_debugging, - override val children: List = mutableListOf()) : IPreferenceGroup() { - - init { - addPreference(DumpLogsPreference()) - addPreference(EnableLogSenderPreference()) - } +override val key: String = DevOpsPreferences.KEY_DEVOPTS_DEBUGGING, +override val title: Int = R.string.idepref_group_debugging, +override val children: List = mutableListOf()) : IPreferenceGroup() { + +init { + addPreference(DumpLogsPreference()) + addPreference(EnableLogSenderPreference()) +} } @Parcelize internal class DumpLogsPreference( - override val key: String = DevOpsPreferences.KEY_DEVOPTS_DEBUGGING_DUMPLOGS, - override val title: Int = R.string.idepref_devOptions_dumpLogs_title, - override val summary: Int? = R.string.idepref_devOptions_dumpLogs_summary) : - SwitchPreference(setValue = DevOpsPreferences::dumpLogs::set, - getValue = DevOpsPreferences::dumpLogs::get) +override val key: String = DevOpsPreferences.KEY_DEVOPTS_DEBUGGING_DUMPLOGS, +override val title: Int = R.string.idepref_devOptions_dumpLogs_title, +override val summary: Int? = R.string.idepref_devOptions_dumpLogs_summary, +override val tooltipTag: String = PREFS_DEVOPTIONS_DUMPLOGS) : +SwitchPreference(setValue = DevOpsPreferences::dumpLogs::set, + getValue = DevOpsPreferences::dumpLogs::get) @Parcelize internal class EnableLogSenderPreference( - override val key: String = DevOpsPreferences.KEY_DEVOPTS_DEBUGGING_ENABLE_LOGSENDER, - override val title: Int = R.string.idepref_devOptions_enableLogsender_title, - override val summary: Int? = R.string.idepref_devOptions_enableLogsender_summary) : - SwitchPreference(setValue = DevOpsPreferences::logsenderEnabled::set, - getValue = DevOpsPreferences::logsenderEnabled::get) \ No newline at end of file +override val key: String = DevOpsPreferences.KEY_DEVOPTS_DEBUGGING_ENABLE_LOGSENDER, +override val title: Int = R.string.idepref_devOptions_enableLogsender_title, +override val summary: Int? = R.string.idepref_devOptions_enableLogsender_summary, +override val tooltipTag: String = PREFS_DEVOPTIONS_LOGSENDER) : +SwitchPreference(setValue = DevOpsPreferences::logsenderEnabled::set, + getValue = DevOpsPreferences::logsenderEnabled::get) diff --git a/app/src/main/java/com/itsaky/androidide/preferences/editorPrefExts.kt b/app/src/main/java/com/itsaky/androidide/preferences/editorPrefExts.kt index d31daeb9d0..1d6c3d157c 100644 --- a/app/src/main/java/com/itsaky/androidide/preferences/editorPrefExts.kt +++ b/app/src/main/java/com/itsaky/androidide/preferences/editorPrefExts.kt @@ -40,211 +40,221 @@ import com.itsaky.androidide.resources.R.drawable import com.itsaky.androidide.resources.R.string import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize -import kotlin.reflect.KMutableProperty0 @Parcelize class EditorPreferencesScreen( - override val key: String = "idepref_editor", - override val title: Int = string.idepref_editor_title, - override val summary: Int? = string.idepref_editor_summary, - override val children: List = mutableListOf(), +override val key: String = "idepref_editor", +override val title: Int = string.idepref_editor_title, +override val summary: Int? = string.idepref_editor_summary, +override val children: List = mutableListOf(), +override val tooltipTag: String = TooltipTag.PREFS_EDITOR, ) : IPreferenceScreen() { - init { - addPreference(CommonConfigurations()) - addPreference(JavaCodeConfigurations()) - addPreference(XMLPreferencesScreen()) - } +init { + addPreference(CommonConfigurations()) + addPreference(JavaCodeConfigurations()) + addPreference(XMLPreferencesScreen()) +} } @Parcelize private class CommonConfigurations( - override val key: String = "idepref_editor_common", - override val title: Int = string.idepref_editor_category_common, - override val children: List = mutableListOf(), +override val key: String = "idepref_editor_common", +override val title: Int = string.idepref_editor_category_common, +override val children: List = mutableListOf(), ) : IPreferenceGroup() { - init { - addPreference(TextSize()) - addPreference(TabSize()) - addPreference(NonPrintablePaintingFlags()) - addPreference(UseSoftTab()) - addPreference(WordWrap()) - addPreference(UseMagnifier()) - addPreference(UseICU()) - addPreference(DeleteEmptyLines()) - addPreference(DeleteTabs()) - addPreference(StickyScrollEnabled()) - addPreference(PinLineNumbersEnabled()) - addPreference(CompletionsMatchLower()) - } +init { + addPreference(TextSize()) + addPreference(TabSize()) + addPreference(NonPrintablePaintingFlags()) + addPreference(UseSoftTab()) + addPreference(WordWrap()) + addPreference(UseMagnifier()) + addPreference(UseICU()) + addPreference(DeleteEmptyLines()) + addPreference(DeleteTabs()) + addPreference(StickyScrollEnabled()) + addPreference(PinLineNumbersEnabled()) + addPreference(CompletionsMatchLower()) +} } @Parcelize private class TextSize( - override val key: String = FONT_SIZE, - override val title: Int = string.idepref_editor_fontsize_title, - override val summary: Int? = string.idepref_editor_fontsize_summary, - override val icon: Int? = drawable.ic_text_size, - override val dialogTitle: Int = string.title_change_text_size, - override val dialogMessage: Int? = string.msg_editor_font_size, - override val tooltipTag: String = TooltipTag.PREFS_EDITOR, +override val key: String = FONT_SIZE, +override val title: Int = string.idepref_editor_fontsize_title, +override val summary: Int? = string.idepref_editor_fontsize_summary, +override val icon: Int? = drawable.ic_text_size, +override val dialogTitle: Int = string.title_change_text_size, +override val dialogMessage: Int? = string.msg_editor_font_size, +override val tooltipTag: String = TooltipTag.PREFS_EDITOR_FONTSIZE, ) : DialogPreference() { - override fun onConfigureDialog(preference: Preference, dialog: MaterialAlertDialogBuilder) { - val binding = LayoutTextSizeSliderBinding.inflate(LayoutInflater.from(preference.context)) - var size = EditorPreferences.fontSize - if (size !in 6f..32f) { - size = 14f - } - binding.slider.value = kotlin.math.round(size) - binding.slider.setLabelFormatter { it.toString() } - - dialog.setView(binding.root) - dialog.setPositiveButton(android.R.string.ok) { iface, _ -> - iface.dismiss() - EditorPreferences.fontSize = binding.slider.value - } - dialog.setNegativeButton(android.R.string.cancel, null) - dialog.setNeutralButton(string.reset) { iface, _ -> - iface.dismiss() - EditorPreferences.fontSize = 14f - } - } +override fun onConfigureDialog(preference: Preference, dialog: MaterialAlertDialogBuilder) { + val binding = LayoutTextSizeSliderBinding.inflate(LayoutInflater.from(preference.context)) + var size = EditorPreferences.fontSize + if (size !in 6f..32f) { + size = 14f + } + binding.slider.value = kotlin.math.round(size) + binding.slider.setLabelFormatter { it.toString() } + + dialog.setView(binding.root) + dialog.setPositiveButton(android.R.string.ok) { iface, _ -> + iface.dismiss() + EditorPreferences.fontSize = binding.slider.value + } + dialog.setNegativeButton(android.R.string.cancel, null) + dialog.setNeutralButton(string.reset) { iface, _ -> + iface.dismiss() + EditorPreferences.fontSize = 14f + } +} } @Parcelize private class UseSoftTab( - override val key: String = USE_SOFT_TAB, - override val title: Int = string.idepref_editor_useSoftTabs_title, - override val summary: Int? = string.idepref_editor_useSoftTabs_summary, - override val icon: Int? = drawable.ic_space, +override val key: String = USE_SOFT_TAB, +override val title: Int = string.idepref_editor_useSoftTabs_title, +override val summary: Int? = string.idepref_editor_useSoftTabs_summary, +override val icon: Int? = drawable.ic_space, +override val tooltipTag: String = TooltipTag.PREFS_EDITOR_SOFTTAB, ) : SwitchPreference(setValue = EditorPreferences::useSoftTab::set, - getValue = EditorPreferences::useSoftTab::get) +getValue = EditorPreferences::useSoftTab::get) @Parcelize private class TabSize( - override val key: String = TAB_SIZE, - override val title: Int = string.title_tab_size, - override val summary: Int? = string.msg_tab_size, - override val icon: Int? = drawable.ic_tab, - override val tooltipTag: String = TooltipTag.PREFS_EDITOR, +override val key: String = TAB_SIZE, +override val title: Int = string.title_tab_size, +override val summary: Int? = string.msg_tab_size, +override val icon: Int? = drawable.ic_tab, +override val tooltipTag: String = TooltipTag.PREFS_EDITOR_TABSIZE, ) : SingleChoicePreference() { - @IgnoredOnParcel - private val tabSizes = intArrayOf(2, 4, 6, 8) - - override fun getEntries(preference: Preference): Array { - val currentTabSize = EditorPreferences.tabSize - return Array(tabSizes.size) { index -> - PreferenceChoices.Entry( - label = tabSizes[index].toString(), - _isChecked = currentTabSize == tabSizes[index], - data = tabSizes[index] - ) - } - } - - override fun onChoiceConfirmed( - preference: Preference, - entry: PreferenceChoices.Entry?, - position: Int - ) { - EditorPreferences.tabSize = (entry?.data as? Int?) ?: 4 - } +@IgnoredOnParcel +private val tabSizes = intArrayOf(2, 4, 6, 8) + +override fun getEntries(preference: Preference): Array { + val currentTabSize = EditorPreferences.tabSize + return Array(tabSizes.size) { index -> + PreferenceChoices.Entry( + label = tabSizes[index].toString(), + _isChecked = currentTabSize == tabSizes[index], + data = tabSizes[index] + ) + } +} + +override fun onChoiceConfirmed( + preference: Preference, + entry: PreferenceChoices.Entry?, + position: Int +) { + EditorPreferences.tabSize = (entry?.data as? Int?) ?: 4 +} } @Parcelize private class NonPrintablePaintingFlags( - override val key: String = PRINTABLE_CHARS, - override val title: Int = string.idepref_editor_paintingflags_title, - override val summary: Int? = string.idepref_editor_paintingflags_summary, - override val icon: Int? = drawable.ic_drawing, +override val key: String = PRINTABLE_CHARS, +override val title: Int = string.idepref_editor_paintingflags_title, +override val summary: Int? = string.idepref_editor_paintingflags_summary, +override val icon: Int? = drawable.ic_drawing, +override val tooltipTag: String = TooltipTag.PREFS_EDITOR_NONPRINTING, ) : PropertyBasedMultiChoicePreference() { - override fun getProperties(): Map> { - return linkedMapOf( - "Leading" to EditorPreferences::drawLeadingWs, - "Trailing" to EditorPreferences::drawTrailingWs, - "Inner" to EditorPreferences::drawInnerWs, - "Empty lines" to EditorPreferences::drawEmptyLineWs, - "Line breaks" to EditorPreferences::drawLineBreak - ) - } +override fun getProperties(): List { + return listOf( + PropertyEntry("Leading", EditorPreferences::drawLeadingWs, TooltipTag.PREFS_EDITOR_NONPRINTING_LEADING), + PropertyEntry("Trailing", EditorPreferences::drawTrailingWs, TooltipTag.PREFS_EDITOR_NONPRINTING_TRAILING), + PropertyEntry("Inner", EditorPreferences::drawInnerWs, TooltipTag.PREFS_EDITOR_NONPRINTING_INNER), + PropertyEntry("Empty lines", EditorPreferences::drawEmptyLineWs, TooltipTag.PREFS_EDITOR_NONPRINTING_EMPTYLINES), + PropertyEntry("Line breaks", EditorPreferences::drawLineBreak, TooltipTag.PREFS_EDITOR_NONPRINTING_LINEBREAKS), + ) +} } @Parcelize private class WordWrap( - override val key: String = WORD_WRAP, - override val title: Int = string.idepref_editor_word_wrap_title, - override val summary: Int? = string.idepref_editor_word_wrap_summary, - override val icon: Int? = drawable.ic_wrap_text, +override val key: String = WORD_WRAP, +override val title: Int = string.idepref_editor_word_wrap_title, +override val summary: Int? = string.idepref_editor_word_wrap_summary, +override val icon: Int? = drawable.ic_wrap_text, +override val tooltipTag: String = TooltipTag.PREFS_EDITOR_WORDWRAP, ) : SwitchPreference(setValue = EditorPreferences::wordwrap::set, - getValue = EditorPreferences::wordwrap::get) +getValue = EditorPreferences::wordwrap::get) @Parcelize private class UseMagnifier( - override val key: String = USE_MAGNIFER, - override val title: Int = string.idepref_editor_use_magnifier_title, - override val summary: Int? = string.idepref_editor_use_magnifier_summary, - override val icon: Int? = drawable.ic_loupe, +override val key: String = USE_MAGNIFER, +override val title: Int = string.idepref_editor_use_magnifier_title, +override val summary: Int? = string.idepref_editor_use_magnifier_summary, +override val icon: Int? = drawable.ic_loupe, +override val tooltipTag: String = TooltipTag.PREFS_EDITOR_MAGNIFIER, ) : SwitchPreference(setValue = EditorPreferences::useMagnifier::set, - getValue = EditorPreferences::useMagnifier::get) +getValue = EditorPreferences::useMagnifier::get) @Parcelize private class CompletionsMatchLower( - override val key: String = COMPLETIONS_MATCH_LOWER, - override val title: Int = string.idepref_java_matchLower_title, - override val summary: Int? = string.idepref_java_matchLower_summary, - override val icon: Int? = drawable.ic_text_lower, +override val key: String = COMPLETIONS_MATCH_LOWER, +override val title: Int = string.idepref_java_matchLower_title, +override val summary: Int? = string.idepref_java_matchLower_summary, +override val icon: Int? = drawable.ic_text_lower, +override val tooltipTag: String = TooltipTag.PREFS_EDITOR_MATCHCASE, ) : SwitchPreference( - setValue = EditorPreferences::completionsMatchLower::set, - getValue = EditorPreferences::completionsMatchLower::get +setValue = EditorPreferences::completionsMatchLower::set, +getValue = EditorPreferences::completionsMatchLower::get ) @Parcelize private class UseICU( - override val key: String = USE_ICU, - override val title: Int = string.idepref_useIcu_title, - override val summary: Int? = string.idepref_useIcu_summary, - override val icon: Int? = drawable.ic_expand_selection, +override val key: String = USE_ICU, +override val title: Int = string.idepref_useIcu_title, +override val summary: Int? = string.idepref_useIcu_summary, +override val icon: Int? = drawable.ic_expand_selection, +override val tooltipTag: String = TooltipTag.PREFS_EDITOR_WORDBOUNDARIES, ) : SwitchPreference(setValue = EditorPreferences::useIcu::set, - getValue = EditorPreferences::useIcu::get) +getValue = EditorPreferences::useIcu::get) @Parcelize private class DeleteEmptyLines( - override val key: String = DELETE_EMPTY_LINES, - override val title: Int = R.string.idepref_deleteEmptyLines_title, - override val summary: Int? = R.string.idepref_deleteEmptyLines_summary, - override val icon: Int? = drawable.ic_backspace +override val key: String = DELETE_EMPTY_LINES, +override val title: Int = R.string.idepref_deleteEmptyLines_title, +override val summary: Int? = R.string.idepref_deleteEmptyLines_summary, +override val icon: Int? = drawable.ic_backspace, +override val tooltipTag: String = TooltipTag.PREFS_EDITOR_DELETELINES, ) : SwitchPreference(setValue = EditorPreferences::deleteEmptyLines::set, - getValue = EditorPreferences::deleteEmptyLines::get) +getValue = EditorPreferences::deleteEmptyLines::get) @Parcelize private class DeleteTabs( - override val key: String = DELETE_TABS_ON_BACKSPACE, - override val title: Int = R.string.idepref_deleteTabs_title, - override val summary: Int? = R.string.idepref_deleteTabs_summary, - override val icon: Int? = drawable.ic_backspace +override val key: String = DELETE_TABS_ON_BACKSPACE, +override val title: Int = R.string.idepref_deleteTabs_title, +override val summary: Int? = R.string.idepref_deleteTabs_summary, +override val icon: Int? = drawable.ic_backspace, +override val tooltipTag: String = TooltipTag.PREFS_EDITOR_SMARTBACKSPACE, ) : - SwitchPreference(setValue = EditorPreferences::deleteTabsOnBackspace::set, - getValue = EditorPreferences::deleteTabsOnBackspace::get) +SwitchPreference(setValue = EditorPreferences::deleteTabsOnBackspace::set, + getValue = EditorPreferences::deleteTabsOnBackspace::get) @Parcelize private class StickyScrollEnabled( - override val key: String = STICKY_SCROLL_ENABLED, - override val title: Int = R.string.idepref_editor_stickScroll_title, - override val summary: Int? = R.string.idepref_editor_stickyScroll_summary, - override val icon: Int? = drawable.ic_sticky_scroll +override val key: String = STICKY_SCROLL_ENABLED, +override val title: Int = R.string.idepref_editor_stickScroll_title, +override val summary: Int? = R.string.idepref_editor_stickyScroll_summary, +override val icon: Int? = drawable.ic_sticky_scroll, +override val tooltipTag: String = TooltipTag.PREFS_EDITOR_STICKYSCROLL, ) : SwitchPreference(setValue = EditorPreferences::stickyScrollEnabled::set, - getValue = EditorPreferences::stickyScrollEnabled::get) +getValue = EditorPreferences::stickyScrollEnabled::get) @Parcelize private class PinLineNumbersEnabled( - override val key: String = PIN_LINE_NUMBERS, - override val title: Int = R.string.idepref_editor_pinLineNumbers_title, - override val summary: Int? = R.string.idepref_editor_pinLineNumbers_summary, - override val icon: Int? = drawable.ic_pin +override val key: String = PIN_LINE_NUMBERS, +override val title: Int = R.string.idepref_editor_pinLineNumbers_title, +override val summary: Int? = R.string.idepref_editor_pinLineNumbers_summary, +override val icon: Int? = drawable.ic_pin, +override val tooltipTag: String = TooltipTag.PREFS_EDITOR_PINLINES, ) : SwitchPreference(setValue = EditorPreferences::pinLineNumbers::set, - getValue = EditorPreferences::pinLineNumbers::get) +getValue = EditorPreferences::pinLineNumbers::get) diff --git a/app/src/main/java/com/itsaky/androidide/preferences/generalPrefExts.kt b/app/src/main/java/com/itsaky/androidide/preferences/generalPrefExts.kt index 478d90edae..83a30fadb4 100644 --- a/app/src/main/java/com/itsaky/androidide/preferences/generalPrefExts.kt +++ b/app/src/main/java/com/itsaky/androidide/preferences/generalPrefExts.kt @@ -23,6 +23,10 @@ import androidx.core.content.ContextCompat import androidx.preference.Preference import com.itsaky.androidide.R import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_GENERAL +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_GENERAL_CONFIRMOPEN +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_GENERAL_LANGUAGE +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_GENERAL_OPENLAST +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_GENERAL_UIMODE import com.itsaky.androidide.preferences.internal.GeneralPreferences import com.itsaky.androidide.resources.R.drawable import com.itsaky.androidide.resources.R.string @@ -35,7 +39,8 @@ class GeneralPreferencesScreen( override val key: String = "idepref_general", override val title: Int = string.title_general, override val summary: Int? = string.idepref_general_summary, -override val children: List = mutableListOf() +override val children: List = mutableListOf(), +override val tooltipTag: String = PREFS_GENERAL, ) : IPreferenceScreen() { init { @@ -79,7 +84,7 @@ override val icon: Int? = R.drawable.ic_ui_mode ) : SingleChoicePreference() { @IgnoredOnParcel -override val tooltipTag: String = PREFS_GENERAL +override val tooltipTag: String = PREFS_GENERAL_UIMODE override fun getEntries(preference: Preference): Array { val context = preference.context @@ -115,7 +120,7 @@ override val icon: Int? = R.drawable.ic_translate ) : SingleChoicePreference() { @IgnoredOnParcel -override val tooltipTag: String = PREFS_GENERAL +override val tooltipTag: String = PREFS_GENERAL_LANGUAGE override fun getEntries(preference: Preference): Array { val context = preference.context @@ -156,7 +161,8 @@ class OpenLastProject( override val key: String = GeneralPreferences.OPEN_PROJECTS, override val title: Int = string.title_open_projects, override val summary: Int? = string.msg_open_projects, -override val icon: Int? = drawable.ic_open_project +override val icon: Int? = drawable.ic_open_project, +override val tooltipTag: String = PREFS_GENERAL_OPENLAST, ) : SwitchPreference() { override fun onCreatePreference(context: Context): Preference { @@ -177,7 +183,8 @@ class ConfirmProjectOpen( override val key: String = GeneralPreferences.CONFIRM_PROJECT_OPEN, override val title: Int = string.title_confirm_project_open, override val summary: Int? = string.msg_confirm_project_open, -override val icon: Int? = drawable.ic_open_project +override val icon: Int? = drawable.ic_open_project, +override val tooltipTag: String = PREFS_GENERAL_CONFIRMOPEN, ) : SwitchPreference() { override fun onCreatePreference(context: Context): Preference { diff --git a/app/src/main/java/com/itsaky/androidide/preferences/gitPrefExts.kt b/app/src/main/java/com/itsaky/androidide/preferences/gitPrefExts.kt index dc8b50d9ee..9e0d102823 100644 --- a/app/src/main/java/com/itsaky/androidide/preferences/gitPrefExts.kt +++ b/app/src/main/java/com/itsaky/androidide/preferences/gitPrefExts.kt @@ -2,105 +2,111 @@ package com.itsaky.androidide.preferences import androidx.preference.Preference import com.google.android.material.textfield.TextInputLayout +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_GIT +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_GIT_USEREMAIL +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_GIT_USERNAME import com.itsaky.androidide.preferences.internal.GitPreferences import com.itsaky.androidide.R import kotlinx.parcelize.Parcelize @Parcelize class GitPreferencesScreen( - override val key: String = "idepref_git", - override val title: Int = R.string.git_title, - override val summary: Int? = R.string.idepref_git_summary, - override val children: List = mutableListOf() + override val key: String = "idepref_git", + override val title: Int = R.string.git_title, + override val summary: Int? = R.string.idepref_git_summary, + override val children: List = mutableListOf(), + override val tooltipTag: String = PREFS_GIT, ) : IPreferenceScreen() { - init { - addPreference(GitAuthorConfig()) - } + init { + addPreference(GitAuthorConfig()) + } } @Parcelize class GitAuthorConfig( - override val key: String = "idepref_git_author", - override val title: Int = R.string.idepref_git_author_title, - override val summary: Int? = R.string.idepref_git_author_summary, - override val children: List = mutableListOf() + override val key: String = "idepref_git_author", + override val title: Int = R.string.idepref_git_author_title, + override val summary: Int? = R.string.idepref_git_author_summary, + override val children: List = mutableListOf(), ) : IPreferenceGroup() { - init { - addPreference(GitUserName()) - addPreference(GitUserEmail()) - } + init { + addPreference(GitUserName()) + addPreference(GitUserEmail()) + } } @Parcelize class GitUserName( - override val key: String = GitPreferences.GIT_USER_NAME, - override val title: Int = R.string.idepref_git_user_name_title, - override val summary: Int? = null, - override val icon: Int? = R.drawable.ic_account + override val key: String = GitPreferences.GIT_USER_NAME, + override val title: Int = R.string.idepref_git_user_name_title, + override val summary: Int? = null, + override val icon: Int? = R.drawable.ic_account, + override val tooltipTag: String = PREFS_GIT_USERNAME, ) : EditTextPreference() { - override fun onCreateView(context: android.content.Context): Preference { - val pref = super.onCreateView(context) - val currentName = GitPreferences.userName - if (!currentName.isNullOrBlank()) { - pref.summary = currentName - } else { - pref.summary = context.getString(R.string.idepref_git_user_name_summary) - } - return pref - } + override fun onCreateView(context: android.content.Context): Preference { + val pref = super.onCreateView(context) + val currentName = GitPreferences.userName + if (!currentName.isNullOrBlank()) { + pref.summary = currentName + } else { + pref.summary = context.getString(R.string.idepref_git_user_name_summary) + } + return pref + } - override fun onConfigureTextInput(input: TextInputLayout) { - input.editText?.setText(GitPreferences.userName) - input.hint = input.context.getString(R.string.idepref_git_user_name_title) - } + override fun onConfigureTextInput(input: TextInputLayout) { + input.editText?.setText(GitPreferences.userName) + input.hint = input.context.getString(R.string.idepref_git_user_name_title) + } - override fun onPreferenceChanged(preference: Preference, newValue: Any?): Boolean { - val name = newValue as? String - GitPreferences.userName = name - if (!name.isNullOrBlank()) { - preference.summary = name - } else { - preference.summary = preference.context.getString(R.string.idepref_git_user_name_summary) - } - return true - } + override fun onPreferenceChanged(preference: Preference, newValue: Any?): Boolean { + val name = newValue as? String + GitPreferences.userName = name + if (!name.isNullOrBlank()) { + preference.summary = name + } else { + preference.summary = preference.context.getString(R.string.idepref_git_user_name_summary) + } + return true + } } @Parcelize class GitUserEmail( - override val key: String = GitPreferences.GIT_USER_EMAIL, - override val title: Int = R.string.idepref_git_user_email_title, - override val summary: Int? = null, - override val icon: Int? = R.drawable.ic_email + override val key: String = GitPreferences.GIT_USER_EMAIL, + override val title: Int = R.string.idepref_git_user_email_title, + override val summary: Int? = null, + override val icon: Int? = R.drawable.ic_email, + override val tooltipTag: String = PREFS_GIT_USEREMAIL, ) : EditTextPreference() { - override fun onCreateView(context: android.content.Context): Preference { - val pref = super.onCreateView(context) - val currentEmail = GitPreferences.userEmail - if (!currentEmail.isNullOrBlank()) { - pref.summary = currentEmail - } else { - pref.summary = context.getString(R.string.idepref_git_user_email_summary) - } - return pref - } + override fun onCreateView(context: android.content.Context): Preference { + val pref = super.onCreateView(context) + val currentEmail = GitPreferences.userEmail + if (!currentEmail.isNullOrBlank()) { + pref.summary = currentEmail + } else { + pref.summary = context.getString(R.string.idepref_git_user_email_summary) + } + return pref + } - override fun onConfigureTextInput(input: TextInputLayout) { - input.editText?.setText(GitPreferences.userEmail) - input.hint = input.context.getString(R.string.idepref_git_user_email_title) - } + override fun onConfigureTextInput(input: TextInputLayout) { + input.editText?.setText(GitPreferences.userEmail) + input.hint = input.context.getString(R.string.idepref_git_user_email_title) + } - override fun onPreferenceChanged(preference: Preference, newValue: Any?): Boolean { - val email = newValue as? String - GitPreferences.userEmail = email - if (!email.isNullOrBlank()) { - preference.summary = email - } else { - preference.summary = preference.context.getString(R.string.idepref_git_user_email_summary) - } - return true - } + override fun onPreferenceChanged(preference: Preference, newValue: Any?): Boolean { + val email = newValue as? String + GitPreferences.userEmail = email + if (!email.isNullOrBlank()) { + preference.summary = email + } else { + preference.summary = preference.context.getString(R.string.idepref_git_user_email_summary) + } + return true + } } diff --git a/app/src/main/java/com/itsaky/androidide/preferences/javaPrefExts.kt b/app/src/main/java/com/itsaky/androidide/preferences/javaPrefExts.kt index 91fe9cc959..44197b1985 100644 --- a/app/src/main/java/com/itsaky/androidide/preferences/javaPrefExts.kt +++ b/app/src/main/java/com/itsaky/androidide/preferences/javaPrefExts.kt @@ -18,6 +18,7 @@ package com.itsaky.androidide.preferences import com.itsaky.androidide.R +import com.itsaky.androidide.idetooltips.TooltipTag import com.itsaky.androidide.preferences.internal.JavaPreferences import com.itsaky.androidide.resources.R.drawable import com.itsaky.androidide.resources.R.string @@ -25,22 +26,23 @@ import kotlinx.parcelize.Parcelize @Parcelize internal class JavaCodeConfigurations( - override val key: String = "idepref_editor_java", - override val title: Int = string.idepref_editor_category_java, - override val children: List = mutableListOf(), +override val key: String = "idepref_editor_java", +override val title: Int = string.idepref_editor_category_java, +override val children: List = mutableListOf(), ) : IPreferenceGroup() { - init { - addPreference(GoogleCodeStyle()) - } +init { + addPreference(GoogleCodeStyle()) +} } /** @author Akash Yadav */ @Parcelize private class GoogleCodeStyle( - override val key: String = JavaPreferences.GOOGLE_CODE_STYLE, - override val title: Int = string.idepref_java_useGoogleStyle_title, - override val summary: Int? = string.idepref_java_useGoogleStyle_summary, - override val icon: Int? = drawable.ic_format_code, +override val key: String = JavaPreferences.GOOGLE_CODE_STYLE, +override val title: Int = string.idepref_java_useGoogleStyle_title, +override val summary: Int? = string.idepref_java_useGoogleStyle_summary, +override val icon: Int? = drawable.ic_format_code, +override val tooltipTag: String = TooltipTag.PREFS_EDITOR_GOOGLESTYLE, ) : SwitchPreference(getValue = JavaPreferences::googleCodeStyle::get, - setValue = JavaPreferences::googleCodeStyle::set) +setValue = JavaPreferences::googleCodeStyle::set) diff --git a/app/src/main/java/com/itsaky/androidide/preferences/pluginPrefExts.kt b/app/src/main/java/com/itsaky/androidide/preferences/pluginPrefExts.kt index ae8098d04a..c800eb70fc 100644 --- a/app/src/main/java/com/itsaky/androidide/preferences/pluginPrefExts.kt +++ b/app/src/main/java/com/itsaky/androidide/preferences/pluginPrefExts.kt @@ -7,6 +7,7 @@ import androidx.preference.Preference import com.itsaky.androidide.activities.PluginManagerActivity import com.itsaky.androidide.activities.PluginScreenNavigator import com.itsaky.androidide.app.IDEApplication +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_PLUGIN_MANAGER import com.itsaky.androidide.plugins.manager.core.PluginManager import com.itsaky.androidide.resources.R.drawable import com.itsaky.androidide.resources.R.string @@ -18,6 +19,7 @@ class PluginManagerEntry( override val key: String = "idepref_plugin_manager", override val title: Int = string.plugin_manager_title, override val summary: Int? = string.plugin_manager_summary, + override val tooltipTag: String = PREFS_PLUGIN_MANAGER, ) : BasePreference() { override fun onCreatePreference(context: Context): Preference { return Preference(context) diff --git a/app/src/main/java/com/itsaky/androidide/preferences/termuxPrefsExt.kt b/app/src/main/java/com/itsaky/androidide/preferences/termuxPrefsExt.kt index 303eacca5c..8aba76f2c8 100644 --- a/app/src/main/java/com/itsaky/androidide/preferences/termuxPrefsExt.kt +++ b/app/src/main/java/com/itsaky/androidide/preferences/termuxPrefsExt.kt @@ -24,6 +24,12 @@ import androidx.preference.Preference import com.itsaky.androidide.R import com.itsaky.androidide.app.IDEApplication import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_TERMUX +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_TERMUX_CRASHREPORTS +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_TERMUX_KEYLOGGING +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_TERMUX_LOGLEVEL +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_TERMUX_MARGIN +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_TERMUX_NOHARDKEYBOARD +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_TERMUX_SOFTKEYBOARD import com.termux.shared.logger.Logger import com.termux.shared.termux.settings.preferences.TermuxAppSharedPreferences import kotlinx.parcelize.IgnoredOnParcel @@ -42,221 +48,228 @@ private const val KEY_TERMUX_VIEW_PREFERENCES = "${KEY_TERMUX_PREFERENCES}.view" private const val KEY_TERMUX_VIEW_MARGIN_ADJUSTMENT_ENABLED_PREFERENCE = "${KEY_TERMUX_VIEW_PREFERENCES}.marginAdjustment" abstract class TermuxSwitchPreference( - @StringRes private val summaryOn: Int, - @StringRes private val summaryOff: Int, - property: KMutableProperty0 +@StringRes private val summaryOn: Int, +@StringRes private val summaryOff: Int, +property: KMutableProperty0, +override val tooltipTag: String = "", ) : SwitchPreference(property) { - override fun onCreatePreference(context: Context): Preference { - return super.onCreatePreference(context).also { preference -> - updateSummary(preference) - } - } +override fun onCreatePreference(context: Context): Preference { + return super.onCreatePreference(context).also { preference -> + updateSummary(preference) + } +} - override fun onPreferenceChanged(preference: Preference, newValue: Any?): Boolean { - return super.onPreferenceChanged(preference, newValue).also { - updateSummary(preference) - } - } +override fun onPreferenceChanged(preference: Preference, newValue: Any?): Boolean { + return super.onPreferenceChanged(preference, newValue).also { + updateSummary(preference) + } +} - private fun updateSummary(preference: Preference) { - preference.summary = (if (getValue?.invoke() == true) { - summaryOn - } else { - summaryOff - }).let { summary -> - ContextCompat.getString(preference.context, summary) - } - } +private fun updateSummary(preference: Preference) { + preference.summary = (if (getValue?.invoke() == true) { + summaryOn + } else { + summaryOff + }).let { summary -> + ContextCompat.getString(preference.context, summary) + } +} } @Parcelize class TermuxPreferences( - override val key: String = KEY_TERMUX_PREFERENCES, - override val title: Int = R.string.termux_preferences_title, - override val summary: Int? = R.string.termux_preferences_summary, - override val children: List = mutableListOf() +override val key: String = KEY_TERMUX_PREFERENCES, +override val title: Int = R.string.termux_preferences_title, +override val summary: Int? = R.string.termux_preferences_summary, +override val children: List = mutableListOf(), +override val tooltipTag: String = PREFS_TERMUX, ) : IPreferenceScreen() { - init { - addPreference(TermuxDebuggingPreferences()) - addPreference(TermuxKeyboardPreferences()) - addPreference(TermuxViewPreferences()) - } +init { + addPreference(TermuxDebuggingPreferences()) + addPreference(TermuxKeyboardPreferences()) + addPreference(TermuxViewPreferences()) +} } @Parcelize class TermuxDebuggingPreferences( - override val key: String = KEY_TERMUX_DEBUGGING_PREFERENCES, - override val title: Int = R.string.termux_debugging_preferences_title, - override val children: List = mutableListOf() +override val key: String = KEY_TERMUX_DEBUGGING_PREFERENCES, +override val title: Int = R.string.termux_debugging_preferences_title, +override val children: List = mutableListOf(), ) : IPreferenceGroup() { - init { - addPreference(TermuxDebuggingLogLevelPreference()) - addPreference(TermuxDebuggingTerminalViewKeyLoggingPreference()) - addPreference(TermuxDebuggingCrashReportNotificationsPreference()) - } +init { + addPreference(TermuxDebuggingLogLevelPreference()) + addPreference(TermuxDebuggingTerminalViewKeyLoggingPreference()) + addPreference(TermuxDebuggingCrashReportNotificationsPreference()) +} } @Parcelize class TermuxDebuggingLogLevelPreference( - override val key: String = KEY_TERMUX_DEBUGGING_LOG_LEVEL_PREFERENCE, - override val title: Int = R.string.log_level_title, - override val icon: Int? = R.drawable.ic_bug +override val key: String = KEY_TERMUX_DEBUGGING_LOG_LEVEL_PREFERENCE, +override val title: Int = R.string.log_level_title, +override val icon: Int? = R.drawable.ic_bug ) : SingleChoicePreference() { - @IgnoredOnParcel - override val tooltipTag: String = PREFS_TERMUX +@IgnoredOnParcel +override val tooltipTag: String = PREFS_TERMUX_LOGLEVEL - override fun getEntries(preference: Preference): Array { - val logLevels = Logger.getLogLevelsArray() - val logLevelLabels = Logger.getLogLevelLabelsArray(preference.context, logLevels, true) - val currentLogLevel = TermuxAppSharedPreferences.build(preference.context, false)?.logLevel - ?: Logger.DEFAULT_LOG_LEVEL - return Array(logLevels.size) { - PreferenceChoices.Entry(logLevelLabels[it], currentLogLevel == logLevels[it], - logLevels[it]) - } - } +override fun getEntries(preference: Preference): Array { + val logLevels = Logger.getLogLevelsArray() + val logLevelLabels = Logger.getLogLevelLabelsArray(preference.context, logLevels, true) + val currentLogLevel = TermuxAppSharedPreferences.build(preference.context, false)?.logLevel + ?: Logger.DEFAULT_LOG_LEVEL + return Array(logLevels.size) { + PreferenceChoices.Entry(logLevelLabels[it], currentLogLevel == logLevels[it], + logLevels[it]) + } +} - override fun onChoiceConfirmed( - preference: Preference, - entry: PreferenceChoices.Entry?, - position: Int - ) { - val newLevel = (entry?.data as? Int?) ?: Logger.DEFAULT_LOG_LEVEL - TermuxAppSharedPreferences.build(preference.context, false) - ?.setLogLevel(preference.context, newLevel) +override fun onChoiceConfirmed( + preference: Preference, + entry: PreferenceChoices.Entry?, + position: Int +) { + val newLevel = (entry?.data as? Int?) ?: Logger.DEFAULT_LOG_LEVEL + TermuxAppSharedPreferences.build(preference.context, false) + ?.setLogLevel(preference.context, newLevel) - preference.summary = Logger.getLogLevelLabel(preference.context, newLevel, true) - } + preference.summary = Logger.getLogLevelLabel(preference.context, newLevel, true) +} - override fun onCreatePreference(context: Context): Preference { - return super.onCreatePreference(context).also { preference -> - val currentLogLevel = TermuxAppSharedPreferences.build(preference.context, false)?.logLevel - ?: Logger.DEFAULT_LOG_LEVEL - preference.summary = Logger.getLogLevelLabel(context, currentLogLevel, true) - } - } +override fun onCreatePreference(context: Context): Preference { + return super.onCreatePreference(context).also { preference -> + val currentLogLevel = TermuxAppSharedPreferences.build(preference.context, false)?.logLevel + ?: Logger.DEFAULT_LOG_LEVEL + preference.summary = Logger.getLogLevelLabel(context, currentLogLevel, true) + } +} } private var isTerminalViewKeyLoggingEnabled: Boolean - get() = TermuxAppSharedPreferences.build(IDEApplication.instance, - true).isTerminalViewKeyLoggingEnabled - set(value) { - TermuxAppSharedPreferences.build(IDEApplication.instance, - true).isTerminalViewKeyLoggingEnabled = value - } +get() = TermuxAppSharedPreferences.build(IDEApplication.instance, + true).isTerminalViewKeyLoggingEnabled +set(value) { + TermuxAppSharedPreferences.build(IDEApplication.instance, + true).isTerminalViewKeyLoggingEnabled = value +} @Parcelize class TermuxDebuggingTerminalViewKeyLoggingPreference( - override val key: String = KEY_TERMUX_DEBUGGING_TERMINAL_VIEW_KEY_LOGGING_PREFERENCE, - override val title: Int = R.string.termux_terminal_view_key_logging_enabled_title, - override val icon: Int? = R.drawable.ic_keyboard, +override val key: String = KEY_TERMUX_DEBUGGING_TERMINAL_VIEW_KEY_LOGGING_PREFERENCE, +override val title: Int = R.string.termux_terminal_view_key_logging_enabled_title, +override val icon: Int? = R.drawable.ic_keyboard, ) : TermuxSwitchPreference( - summaryOn = R.string.termux_terminal_view_key_logging_enabled_on, - summaryOff = R.string.termux_terminal_view_key_logging_enabled_off, - property = ::isTerminalViewKeyLoggingEnabled +summaryOn = R.string.termux_terminal_view_key_logging_enabled_on, +summaryOff = R.string.termux_terminal_view_key_logging_enabled_off, +property = ::isTerminalViewKeyLoggingEnabled, +tooltipTag = PREFS_TERMUX_KEYLOGGING, ) private var isTerminalCrashReportNotificationsEnabled: Boolean - get() = TermuxAppSharedPreferences.build(IDEApplication.instance, - true).areCrashReportNotificationsEnabled(false) - set(value) { - TermuxAppSharedPreferences.build(IDEApplication.instance, - true).setCrashReportNotificationsEnabled(value) - } +get() = TermuxAppSharedPreferences.build(IDEApplication.instance, + true).areCrashReportNotificationsEnabled(false) +set(value) { + TermuxAppSharedPreferences.build(IDEApplication.instance, + true).setCrashReportNotificationsEnabled(value) +} @Parcelize class TermuxDebuggingCrashReportNotificationsPreference( - override val key: String = KEY_TERMUX_DEBUGGING_CRASH_REPORT_NOTIFICATIONS_PREFERENCE, - override val title: Int = R.string.termux_crash_report_notifications_enabled_title, - override val icon: Int? = R.drawable.ic_bell, +override val key: String = KEY_TERMUX_DEBUGGING_CRASH_REPORT_NOTIFICATIONS_PREFERENCE, +override val title: Int = R.string.termux_crash_report_notifications_enabled_title, +override val icon: Int? = R.drawable.ic_bell, ) : TermuxSwitchPreference( - summaryOn = R.string.termux_crash_report_notifications_enabled_on, - summaryOff = R.string.termux_crash_report_notifications_enabled_off, - property = ::isTerminalCrashReportNotificationsEnabled +summaryOn = R.string.termux_crash_report_notifications_enabled_on, +summaryOff = R.string.termux_crash_report_notifications_enabled_off, +property = ::isTerminalCrashReportNotificationsEnabled, +tooltipTag = PREFS_TERMUX_CRASHREPORTS, ) @Parcelize class TermuxKeyboardPreferences( - override val key: String = KEY_TERMUX_KBD_PREFERENCES, - override val title: Int = R.string.termux_keyboard_header, - override val children: List = mutableListOf() +override val key: String = KEY_TERMUX_KBD_PREFERENCES, +override val title: Int = R.string.termux_keyboard_header, +override val children: List = mutableListOf(), ) : IPreferenceGroup() { - init { - addPreference(TermuxKbdSoftKbdEnabledPreference()) - addPreference(TermuxKbdSoftKbdOnlyIfNoHardKbdEnabledPreference()) - } +init { + addPreference(TermuxKbdSoftKbdEnabledPreference()) + addPreference(TermuxKbdSoftKbdOnlyIfNoHardKbdEnabledPreference()) +} } private var isSoftKbdEnabled: Boolean - get() = TermuxAppSharedPreferences.build(IDEApplication.instance, - true).isSoftKeyboardEnabled - set(value) { - TermuxAppSharedPreferences.build(IDEApplication.instance, - true).isSoftKeyboardEnabled = value - } +get() = TermuxAppSharedPreferences.build(IDEApplication.instance, + true).isSoftKeyboardEnabled +set(value) { + TermuxAppSharedPreferences.build(IDEApplication.instance, + true).isSoftKeyboardEnabled = value +} @Parcelize class TermuxKbdSoftKbdEnabledPreference( - override val key: String = KEY_TERMUX_KBD_SOFT_KDB_ENABLED_PREFERENCE, - override val title: Int = R.string.termux_soft_keyboard_enabled_title, - override val icon: Int? = R.drawable.ic_keyboard_soft, +override val key: String = KEY_TERMUX_KBD_SOFT_KDB_ENABLED_PREFERENCE, +override val title: Int = R.string.termux_soft_keyboard_enabled_title, +override val icon: Int? = R.drawable.ic_keyboard_soft, ) : TermuxSwitchPreference( - summaryOn = R.string.termux_soft_keyboard_enabled_on, - summaryOff = R.string.termux_soft_keyboard_enabled_off, - property = ::isSoftKbdEnabled +summaryOn = R.string.termux_soft_keyboard_enabled_on, +summaryOff = R.string.termux_soft_keyboard_enabled_off, +property = ::isSoftKbdEnabled, +tooltipTag = PREFS_TERMUX_SOFTKEYBOARD, ) private var isSoftKbdOnlyIfNoHardKbdEnabled: Boolean - get() = TermuxAppSharedPreferences.build(IDEApplication.instance, - true).isSoftKeyboardEnabledOnlyIfNoHardware - set(value) { - TermuxAppSharedPreferences.build(IDEApplication.instance, - true).isSoftKeyboardEnabledOnlyIfNoHardware = value - } +get() = TermuxAppSharedPreferences.build(IDEApplication.instance, + true).isSoftKeyboardEnabledOnlyIfNoHardware +set(value) { + TermuxAppSharedPreferences.build(IDEApplication.instance, + true).isSoftKeyboardEnabledOnlyIfNoHardware = value +} @Parcelize class TermuxKbdSoftKbdOnlyIfNoHardKbdEnabledPreference( - override val key: String = KEY_TERMUX_KBD_SOFT_KDB_ONLY_IF_NO_HARD_KBD_PREFERENCE, - override val title: Int = R.string.termux_soft_keyboard_enabled_only_if_no_hardware_title, - override val icon: Int? = R.drawable.ic_keyboard, +override val key: String = KEY_TERMUX_KBD_SOFT_KDB_ONLY_IF_NO_HARD_KBD_PREFERENCE, +override val title: Int = R.string.termux_soft_keyboard_enabled_only_if_no_hardware_title, +override val icon: Int? = R.drawable.ic_keyboard, ) : TermuxSwitchPreference( - summaryOn = R.string.termux_soft_keyboard_enabled_only_if_no_hardware_on, - summaryOff = R.string.termux_soft_keyboard_enabled_only_if_no_hardware_off, - property = ::isSoftKbdOnlyIfNoHardKbdEnabled +summaryOn = R.string.termux_soft_keyboard_enabled_only_if_no_hardware_on, +summaryOff = R.string.termux_soft_keyboard_enabled_only_if_no_hardware_off, +property = ::isSoftKbdOnlyIfNoHardKbdEnabled, +tooltipTag = PREFS_TERMUX_NOHARDKEYBOARD, ) @Parcelize class TermuxViewPreferences( - override val key: String = KEY_TERMUX_VIEW_PREFERENCES, - override val title: Int = R.string.termux_terminal_view_view_header, - override val children: List = mutableListOf() +override val key: String = KEY_TERMUX_VIEW_PREFERENCES, +override val title: Int = R.string.termux_terminal_view_view_header, +override val children: List = mutableListOf(), ) : IPreferenceGroup() { - init { - addPreference(TermuxViewMarginAdjustmentEnabledPreference()) - } +init { + addPreference(TermuxViewMarginAdjustmentEnabledPreference()) +} } private var isViewMarginAdjustmentEnabled: Boolean - get() = TermuxAppSharedPreferences.build(IDEApplication.instance, - true).isTerminalMarginAdjustmentEnabled - set(value) { - TermuxAppSharedPreferences.build(IDEApplication.instance, - true).setTerminalMarginAdjustment(value) - } +get() = TermuxAppSharedPreferences.build(IDEApplication.instance, + true).isTerminalMarginAdjustmentEnabled +set(value) { + TermuxAppSharedPreferences.build(IDEApplication.instance, + true).setTerminalMarginAdjustment(value) +} @Parcelize class TermuxViewMarginAdjustmentEnabledPreference( - override val key: String = KEY_TERMUX_VIEW_MARGIN_ADJUSTMENT_ENABLED_PREFERENCE, - override val title: Int = R.string.termux_terminal_view_terminal_margin_adjustment_title, - override val icon: Int? = R.drawable.ic_space, +override val key: String = KEY_TERMUX_VIEW_MARGIN_ADJUSTMENT_ENABLED_PREFERENCE, +override val title: Int = R.string.termux_terminal_view_terminal_margin_adjustment_title, +override val icon: Int? = R.drawable.ic_space, ) : TermuxSwitchPreference( - summaryOn = R.string.termux_terminal_view_terminal_margin_adjustment_on, - summaryOff = R.string.termux_terminal_view_terminal_margin_adjustment_off, - property = ::isViewMarginAdjustmentEnabled -) \ No newline at end of file +summaryOn = R.string.termux_terminal_view_terminal_margin_adjustment_on, +summaryOff = R.string.termux_terminal_view_terminal_margin_adjustment_off, +property = ::isViewMarginAdjustmentEnabled, +tooltipTag = PREFS_TERMUX_MARGIN, +) diff --git a/app/src/main/java/com/itsaky/androidide/preferences/xmlPrefExts.kt b/app/src/main/java/com/itsaky/androidide/preferences/xmlPrefExts.kt index 8e975a0993..8f202b229f 100644 --- a/app/src/main/java/com/itsaky/androidide/preferences/xmlPrefExts.kt +++ b/app/src/main/java/com/itsaky/androidide/preferences/xmlPrefExts.kt @@ -26,210 +26,223 @@ import org.eclipse.lemminx.dom.builder.EmptyElements @Parcelize class XMLPreferencesScreen( - override val key: String = "idepref_editor_xml", - override val title: Int = string.xml, - override val children: List = mutableListOf() +override val key: String = "idepref_editor_xml", +override val title: Int = string.xml, +override val children: List = mutableListOf(), +override val tooltipTag: String = TooltipTag.PREFS_EDITOR_XML, ) : IPreferenceGroup() { - init { - addPreference(XMLFormattingOptions()) - } +init { + addPreference(XMLFormattingOptions()) +} } @Parcelize private class XMLFormattingOptions( - override val key: String = "idepref_xml_formattingOptions", - override val title: Int = string.xml_formatting_options, - override val summary: Int? = string.xml_formatting_options_summary, - override val children: List = mutableListOf() +override val key: String = "idepref_xml_formattingOptions", +override val title: Int = string.xml_formatting_options, +override val summary: Int? = string.xml_formatting_options_summary, +override val children: List = mutableListOf(), +override val tooltipTag: String = TooltipTag.PREFS_EDITOR_XML, ) : IPreferenceScreen() { - init { - addPreference(TrimFinalNewLines()) - addPreference(InsertFinalNewLine()) - addPreference(SplitAttributes()) - addPreference(JoinCDataLines()) - addPreference(JoinCommentLines()) - addPreference(JoinContentLines()) - addPreference(SpaceBeforeEmptyCloseTag()) - addPreference(PreserveEmptyContent()) - addPreference(PreserveAttributeLineBreaks()) - addPreference(ClosingBracketNewLine()) - addPreference(TrimTrailingWhitespace()) - addPreference(MaxLineWidth()) - addPreference(PreservedNewLines()) - addPreference(SplitAttributesIndentSize()) - addPreference(EmptyElementsBehavior()) - } +init { + addPreference(TrimFinalNewLines()) + addPreference(InsertFinalNewLine()) + addPreference(SplitAttributes()) + addPreference(JoinCDataLines()) + addPreference(JoinCommentLines()) + addPreference(JoinContentLines()) + addPreference(SpaceBeforeEmptyCloseTag()) + addPreference(PreserveEmptyContent()) + addPreference(PreserveAttributeLineBreaks()) + addPreference(ClosingBracketNewLine()) + addPreference(TrimTrailingWhitespace()) + addPreference(MaxLineWidth()) + addPreference(PreservedNewLines()) + addPreference(SplitAttributesIndentSize()) + addPreference(EmptyElementsBehavior()) +} } @Parcelize private class TrimFinalNewLines( - override val key: String = XmlPreferences.TRIM_FINAL_NEW_LINE, - override val title: Int = string.idepref_xml_trimFinalNewLine_title, - override val summary: Int? = string.idepref_xml_trimFinalNewLine_summary +override val key: String = XmlPreferences.TRIM_FINAL_NEW_LINE, +override val title: Int = string.idepref_xml_trimFinalNewLine_title, +override val summary: Int? = string.idepref_xml_trimFinalNewLine_summary, +override val tooltipTag: String = TooltipTag.PREFS_XML_TRIMFINALNEWLINE, ) : SwitchPreference(setValue = XmlPreferences::trimFinalNewLine::set, - getValue = XmlPreferences::trimFinalNewLine::get) +getValue = XmlPreferences::trimFinalNewLine::get) @Parcelize private class InsertFinalNewLine( - override val key: String = XmlPreferences.INSERT_FINAL_NEW_LINE, - override val title: Int = string.idepref_xml_insertFinalNewLine_title, - override val summary: Int? = string.idepref_xml_insertFinalNewLine_summary +override val key: String = XmlPreferences.INSERT_FINAL_NEW_LINE, +override val title: Int = string.idepref_xml_insertFinalNewLine_title, +override val summary: Int? = string.idepref_xml_insertFinalNewLine_summary, +override val tooltipTag: String = TooltipTag.PREFS_XML_INSERTFINALNEWLINE, ) : SwitchPreference(setValue = XmlPreferences::insertFinalNewLine::set, - getValue = XmlPreferences::insertFinalNewLine::get) +getValue = XmlPreferences::insertFinalNewLine::get) @Parcelize private class SplitAttributes( - override val key: String = XmlPreferences.SPLIT_ATTRIBUTES, - override val title: Int = string.idepref_xml_splitAttributes_title, - override val summary: Int? = string.idepref_xml_splitAttributes_summary +override val key: String = XmlPreferences.SPLIT_ATTRIBUTES, +override val title: Int = string.idepref_xml_splitAttributes_title, +override val summary: Int? = string.idepref_xml_splitAttributes_summary, +override val tooltipTag: String = TooltipTag.PREFS_XML_SPLITATTRIBUTES, ) : SwitchPreference(setValue = XmlPreferences::splitAttributes::set, - getValue = XmlPreferences::splitAttributes::get) +getValue = XmlPreferences::splitAttributes::get) @Parcelize private class JoinCDataLines( - override val key: String = XmlPreferences.JOIN_CDATA_LINES, - override val title: Int = string.idepref_xml_joinCDataLines_title, - override val summary: Int? = string.idepref_xml_joinCDataLines_summary +override val key: String = XmlPreferences.JOIN_CDATA_LINES, +override val title: Int = string.idepref_xml_joinCDataLines_title, +override val summary: Int? = string.idepref_xml_joinCDataLines_summary, +override val tooltipTag: String = TooltipTag.PREFS_XML_JOINCDATALINES, ) : SwitchPreference(setValue = XmlPreferences::joinCDataLines::set, - getValue = XmlPreferences::joinCDataLines::get) +getValue = XmlPreferences::joinCDataLines::get) @Parcelize private class JoinCommentLines( - override val key: String = XmlPreferences.JOIN_COMMENT_LINES, - override val title: Int = string.idepref_xml_joinComment_title, - override val summary: Int? = string.idepref_xml_joinComment_summary +override val key: String = XmlPreferences.JOIN_COMMENT_LINES, +override val title: Int = string.idepref_xml_joinComment_title, +override val summary: Int? = string.idepref_xml_joinComment_summary, +override val tooltipTag: String = TooltipTag.PREFS_XML_JOINCOMMENTLINES, ) : SwitchPreference(setValue = XmlPreferences::joinCommentLines::set, - getValue = XmlPreferences::joinCommentLines::get) +getValue = XmlPreferences::joinCommentLines::get) @Parcelize private class JoinContentLines( - override val key: String = XmlPreferences.JOIN_CONTENT_LINES, - override val title: Int = string.idepref_xml_joinContent_title, - override val summary: Int? = string.idepref_xml_joinContent_summary +override val key: String = XmlPreferences.JOIN_CONTENT_LINES, +override val title: Int = string.idepref_xml_joinContent_title, +override val summary: Int? = string.idepref_xml_joinContent_summary, +override val tooltipTag: String = TooltipTag.PREFS_XML_JOINCONTENTLINES, ) : SwitchPreference(setValue = XmlPreferences::joinContentLines::set, - getValue = XmlPreferences::joinContentLines::get) +getValue = XmlPreferences::joinContentLines::get) @Parcelize private class SpaceBeforeEmptyCloseTag( - override val key: String = XmlPreferences.SPACE_BEFORE_EMPTY_CLOSE_TAG, - override val title: Int = string.idepref_xml_spaceBeforeEmptyClose_title, - override val summary: Int? = string.idepref_xml_spaceBeforeEmptyClose_summary +override val key: String = XmlPreferences.SPACE_BEFORE_EMPTY_CLOSE_TAG, +override val title: Int = string.idepref_xml_spaceBeforeEmptyClose_title, +override val summary: Int? = string.idepref_xml_spaceBeforeEmptyClose_summary, +override val tooltipTag: String = TooltipTag.PREFS_XML_SPACEBEFORECLOSE, ) : - SwitchPreference( - setValue = XmlPreferences::spaceBeforeEmptyCloseTag::set, - getValue = XmlPreferences::spaceBeforeEmptyCloseTag::get - ) +SwitchPreference( + setValue = XmlPreferences::spaceBeforeEmptyCloseTag::set, + getValue = XmlPreferences::spaceBeforeEmptyCloseTag::get +) @Parcelize private class PreserveEmptyContent( - override val key: String = XmlPreferences.PRESERVE_EMPTY_CONTENT, - override val title: Int = string.idepref_xml_preserveEmptyContent_title, - override val summary: Int? = string.idepref_xml_preserveEmptyContent_summary +override val key: String = XmlPreferences.PRESERVE_EMPTY_CONTENT, +override val title: Int = string.idepref_xml_preserveEmptyContent_title, +override val summary: Int? = string.idepref_xml_preserveEmptyContent_summary, +override val tooltipTag: String = TooltipTag.PREFS_XML_PRESERVEEMPTYCONTENT, ) : - SwitchPreference(setValue = XmlPreferences::preserveEmptyContent::set, - getValue = XmlPreferences::preserveEmptyContent::get) +SwitchPreference(setValue = XmlPreferences::preserveEmptyContent::set, + getValue = XmlPreferences::preserveEmptyContent::get) @Parcelize private class PreserveAttributeLineBreaks( - override val key: String = XmlPreferences.PRESERVE_ATTRIBUTE_LINE_BREAKS, - override val title: Int = string.idepref_xml_preserveAttrLineBreaks_title, - override val summary: Int? = string.idepref_xml_preserveAttrLineBreaks_summary +override val key: String = XmlPreferences.PRESERVE_ATTRIBUTE_LINE_BREAKS, +override val title: Int = string.idepref_xml_preserveAttrLineBreaks_title, +override val summary: Int? = string.idepref_xml_preserveAttrLineBreaks_summary, +override val tooltipTag: String = TooltipTag.PREFS_XML_PRESERVEATTRIBUTES, ) : - SwitchPreference( - setValue = XmlPreferences::preserveAttributeLineBreaks::set, - getValue = XmlPreferences::preserveAttributeLineBreaks::get - ) +SwitchPreference( + setValue = XmlPreferences::preserveAttributeLineBreaks::set, + getValue = XmlPreferences::preserveAttributeLineBreaks::get +) @Parcelize private class ClosingBracketNewLine( - override val key: String = XmlPreferences.CLOSING_BRACKET_NEW_LINE, - override val title: Int = string.idepref_xml_closingBrackNewLine_title, - override val summary: Int? = string.idepref_xml_closingBrackNewLine_summary +override val key: String = XmlPreferences.CLOSING_BRACKET_NEW_LINE, +override val title: Int = string.idepref_xml_closingBrackNewLine_title, +override val summary: Int? = string.idepref_xml_closingBrackNewLine_summary, +override val tooltipTag: String = TooltipTag.PREFS_XML_CLOSEBRACKET, ) : - SwitchPreference( - setValue = XmlPreferences::closingBracketNewLine::set, - getValue = XmlPreferences::closingBracketNewLine::get - ) +SwitchPreference( + setValue = XmlPreferences::closingBracketNewLine::set, + getValue = XmlPreferences::closingBracketNewLine::get +) @Parcelize private class TrimTrailingWhitespace( - override val key: String = XmlPreferences.TRIM_TRAILING_WHITESPACE, - override val title: Int = string.idepref_xml_trimTrailingWs_title, - override val summary: Int? = string.idepref_xml_trimTrailingWs_summary +override val key: String = XmlPreferences.TRIM_TRAILING_WHITESPACE, +override val title: Int = string.idepref_xml_trimTrailingWs_title, +override val summary: Int? = string.idepref_xml_trimTrailingWs_summary, +override val tooltipTag: String = TooltipTag.PREFS_XML_TRIMWHITESPACE, ) : - SwitchPreference( - setValue = XmlPreferences::trimTrailingWhitespace::set, - getValue = XmlPreferences::trimTrailingWhitespace::get - ) +SwitchPreference( + setValue = XmlPreferences::trimTrailingWhitespace::set, + getValue = XmlPreferences::trimTrailingWhitespace::get +) @Parcelize private class MaxLineWidth( - override val key: String = XmlPreferences.MAX_LINE_WIDTH, - override val title: Int = string.idepref_maxLineWidth_title, - override val summary: Int? = string.idepref_maxLineWidth_summary, - override val tooltipTag: String = TooltipTag.PREFS_EDITOR_XML, +override val key: String = XmlPreferences.MAX_LINE_WIDTH, +override val title: Int = string.idepref_maxLineWidth_title, +override val summary: Int? = string.idepref_maxLineWidth_summary, +override val tooltipTag: String = TooltipTag.PREFS_XML_MAXLINEWIDTH, ) : - NumberInputEditTextPreference( - hint = string.idepref_maxLineWidth_title, - setValue = XmlPreferences::maxLineWidth::set, - getValue = XmlPreferences::maxLineWidth::get - ) +NumberInputEditTextPreference( + hint = string.idepref_maxLineWidth_title, + setValue = XmlPreferences::maxLineWidth::set, + getValue = XmlPreferences::maxLineWidth::get +) @Parcelize private class PreservedNewLines( - override val key: String = XmlPreferences.PRESERVED_NEW_LINES, - override val title: Int = string.idepref_preservedNewLines_title, - override val summary: Int? = string.idepref_preservedNewLines_summary, - override val tooltipTag: String = TooltipTag.PREFS_EDITOR_XML, +override val key: String = XmlPreferences.PRESERVED_NEW_LINES, +override val title: Int = string.idepref_preservedNewLines_title, +override val summary: Int? = string.idepref_preservedNewLines_summary, +override val tooltipTag: String = TooltipTag.PREFS_XML_PRESERVENEWLINES, ) : - NumberInputEditTextPreference( - hint = string.idepref_preservedNewLines_title, - setValue = XmlPreferences::preservedNewLines::set, - getValue = XmlPreferences::preservedNewLines::get - ) +NumberInputEditTextPreference( + hint = string.idepref_preservedNewLines_title, + setValue = XmlPreferences::preservedNewLines::set, + getValue = XmlPreferences::preservedNewLines::get +) @Parcelize private class SplitAttributesIndentSize( - override val key: String = XmlPreferences.SPLIT_ATTRIBUTES_INDENT_SIZE, - override val title: Int = string.idepref_splitAttrIndentSize_title, - override val summary: Int? = string.idepref_splitAttrIndentSize_summary, - override val tooltipTag: String = TooltipTag.PREFS_EDITOR_XML, +override val key: String = XmlPreferences.SPLIT_ATTRIBUTES_INDENT_SIZE, +override val title: Int = string.idepref_splitAttrIndentSize_title, +override val summary: Int? = string.idepref_splitAttrIndentSize_summary, +override val tooltipTag: String = TooltipTag.PREFS_XML_SPLITATTRIBINDENT, ) : - NumberInputEditTextPreference( - hint = string.idepref_splitAttrIndentSize_title, - setValue = XmlPreferences::splitAttributesIndentSize::set, - getValue = XmlPreferences::splitAttributesIndentSize::get - ) +NumberInputEditTextPreference( + hint = string.idepref_splitAttrIndentSize_title, + setValue = XmlPreferences::splitAttributesIndentSize::set, + getValue = XmlPreferences::splitAttributesIndentSize::get +) @Parcelize private class EmptyElementsBehavior( - override val key: String = XmlPreferences.EMPTY_ELEMENTS_BEHAVIOR, - override val title: Int = string.idepref_emptyElements_title, - override val summary: Int? = string.idepref_emptyElements_summary, - override val tooltipTag: String = TooltipTag.PREFS_EDITOR_XML, +override val key: String = XmlPreferences.EMPTY_ELEMENTS_BEHAVIOR, +override val title: Int = string.idepref_emptyElements_title, +override val summary: Int? = string.idepref_emptyElements_summary, +override val tooltipTag: String = TooltipTag.PREFS_XML_EMPTYELEMENTS, ) : SingleChoicePreference() { - override fun getEntries(preference: Preference): Array { - val entries = EmptyElements.entries - val currentBehavior = EmptyElements.valueOf(XmlPreferences.emptyElementsBehavior) - - return Array(entries.size) { index -> - PreferenceChoices.Entry( - label = entries[index].toString(), - _isChecked = currentBehavior == entries[index], - data = entries[index] - ) - } - } - - override fun onChoiceConfirmed( - preference: Preference, - entry: PreferenceChoices.Entry?, - position: Int - ) { - XmlPreferences.emptyElementsBehavior = (entry?.data as? EmptyElements?)?.toString() - ?: "Collapse" - } +override fun getEntries(preference: Preference): Array { + val entries = EmptyElements.entries + val currentBehavior = EmptyElements.valueOf(XmlPreferences.emptyElementsBehavior) + + return Array(entries.size) { index -> + PreferenceChoices.Entry( + label = entries[index].toString(), + _isChecked = currentBehavior == entries[index], + data = entries[index] + ) + } +} + +override fun onChoiceConfirmed( + preference: Preference, + entry: PreferenceChoices.Entry?, + position: Int +) { + XmlPreferences.emptyElementsBehavior = (entry?.data as? EmptyElements?)?.toString() + ?: "Collapse" +} } diff --git a/app/src/test/java/com/itsaky/androidide/fragments/IDEPreferencesFragmentTest.kt b/app/src/test/java/com/itsaky/androidide/fragments/IDEPreferencesFragmentTest.kt new file mode 100644 index 0000000000..e4d2afe922 --- /dev/null +++ b/app/src/test/java/com/itsaky/androidide/fragments/IDEPreferencesFragmentTest.kt @@ -0,0 +1,162 @@ +package com.itsaky.androidide.fragments + +import android.content.Context +import android.view.View +import androidx.preference.Preference +import androidx.preference.PreferenceGroupAdapter +import androidx.preference.PreferenceManager +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import androidx.test.core.app.ApplicationProvider +import com.google.common.truth.Truth.assertThat +import com.itsaky.androidide.preferences.IPreference +import com.itsaky.androidide.preferences.IPreferenceGroup +import com.itsaky.androidide.preferences.IPreferenceScreen +import kotlinx.parcelize.IgnoredOnParcel +import kotlinx.parcelize.Parcelize +import org.junit.Assert.assertThrows +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner + +@RunWith(RobolectricTestRunner::class) +class IDEPreferencesFragmentTest { + @Test + fun `collectTooltipTags maps every leaf key to its tooltipTag`() { + val children = + listOf( + FakeItem(key = "a", tooltipTag = "tag.a"), + FakeItem(key = "b", tooltipTag = "tag.b"), + ) + + val tags = IDEPreferencesFragment().collectTooltipTags(children) + + assertThat(tags).containsExactly("a", "tag.a", "b", "tag.b") + } + + @Test + fun `collectTooltipTags recurses into nested categories but not into nested screens`() { + val children = + listOf( + FakeCategory( + key = "category", + tooltipTag = "tag.category", + children = listOf(FakeItem(key = "nested", tooltipTag = "tag.nested")), + ), + FakeScreen( + key = "screen", + tooltipTag = "tag.screen", + // A screen's own children belong to a different fragment instance and must not + // be pulled into this screen's map. + children = listOf(FakeItem(key = "hidden", tooltipTag = "tag.hidden")), + ), + ) + + val tags = IDEPreferencesFragment().collectTooltipTags(children) + + assertThat(tags).containsExactly( + "category", + "tag.category", + "nested", + "tag.nested", + "screen", + "tag.screen", + ) + } + + @Test + fun `collectTooltipTags preserves an empty tooltipTag rather than omitting the key`() { + val children = listOf(FakeItem(key = "untagged", tooltipTag = "")) + + val tags = IDEPreferencesFragment().collectTooltipTags(children) + + assertThat(tags).containsExactly("untagged", "") + } + + @Test + fun `collectTooltipTags rejects a duplicate key instead of silently overwriting its tag`() { + val children = + listOf( + FakeItem(key = "dup", tooltipTag = "tag.first"), + FakeItem(key = "dup", tooltipTag = "tag.second"), + ) + + assertThrows(IllegalStateException::class.java) { + IDEPreferencesFragment().collectTooltipTags(children) + } + } + + @Test + fun `resolveTooltipTag looks up the real adapter position's own tag`() { + val recyclerView = buildRecyclerView(keys = listOf("a", "b")) + val fragment = + IDEPreferencesFragment().apply { + tooltipTagsByKey = mapOf("a" to "tag.a", "b" to "tag.b") + } + + val rowB = recyclerView.getChildAt(1) + + assertThat(fragment.resolveTooltipTag(recyclerView, rowB)).isEqualTo("tag.b") + } + + @Test + fun `resolveTooltipTag returns null for a row whose own tag is empty`() { + val recyclerView = buildRecyclerView(keys = listOf("a")) + val fragment = + IDEPreferencesFragment().apply { + tooltipTagsByKey = mapOf("a" to "") + } + + val rowA = recyclerView.getChildAt(0) + + assertThat(fragment.resolveTooltipTag(recyclerView, rowA)).isNull() + } + + /** A real, laid-out RecyclerView backed by a real PreferenceGroupAdapter - not a fake. */ + private fun buildRecyclerView(keys: List): RecyclerView { + val context = ApplicationProvider.getApplicationContext() + val screen = PreferenceManager(context).createPreferenceScreen(context) + keys.forEach { screen.addPreference(Preference(context).apply { key = it }) } + + return RecyclerView(context).apply { + layoutManager = LinearLayoutManager(context) + adapter = PreferenceGroupAdapter(screen) + measure( + View.MeasureSpec.makeMeasureSpec(1000, View.MeasureSpec.EXACTLY), + View.MeasureSpec.makeMeasureSpec(1000, View.MeasureSpec.EXACTLY), + ) + layout(0, 0, 1000, 1000) + } + } +} + +@Parcelize +private class FakeItem( + override val key: String, + override val tooltipTag: String = "", +) : IPreference() { + @IgnoredOnParcel + override val title: Int = 0 + + override fun onCreateView(context: Context): Preference = throw UnsupportedOperationException() +} + +@Parcelize +private class FakeCategory( + override val key: String, + override val tooltipTag: String = "", + override val children: List = mutableListOf(), +) : IPreferenceGroup() { + @IgnoredOnParcel + override val title: Int = 0 +} + +@Parcelize +private class FakeScreen( + override val key: String, + override val tooltipTag: String = "", + override val children: List = mutableListOf(), +) : IPreferenceScreen() { + @IgnoredOnParcel + override val title: Int = 0 +} diff --git a/docs/docdb/ADFA-5088-plugin-manager-tooltips.sql b/docs/docdb/ADFA-5088-plugin-manager-tooltips.sql new file mode 100644 index 0000000000..f59b60a089 --- /dev/null +++ b/docs/docdb/ADFA-5088-plugin-manager-tooltips.sql @@ -0,0 +1,187 @@ +-- ADFA-5088: Tooltips + Content rows for the Plugin Manager screen. +-- +-- The Plugin Manager screen previously showed one shared tooltip +-- ("plugin.manager") for its toolbar, download icon, FAB, empty state, +-- and every plugin row. The corresponding code change replaces that +-- with a distinct TooltipTag per widget; this script adds the +-- documentation database rows those new tags look up. +-- +-- None of the new tags below existed before. The old shared "plugin.manager" +-- tag (a different string from every tag below) is now dead - no code +-- references it any more - so this script deletes it and its one +-- TooltipButtons row, but leaves the Content page that button linked to +-- (i/plugin-install.html) in place, since Content isn't clearly unreachable +-- the way the Tooltips/TooltipButtons rows are. +-- +-- Both Tooltips (UNIQUE(categoryId, tag)) and Content (UNIQUE(path)) are +-- idempotent `INSERT ... ON CONFLICT ... DO UPDATE` upserts below, so the +-- whole script is safe to re-run. +-- +-- All rows use categoryId = 1 ("ide"), languageId = 1 ("EN-us"), +-- contentTypeId = 12 ("text/html", brotli-compressed). +-- +-- Apply against the real documentation.db: +-- sqlite3 documentation.db < ADFA-5088-plugin-manager-tooltips.sql +-- The whole script runs inside one transaction (BEGIN/COMMIT below) with +-- `.bail on`, so any failure - a bad SQL statement, or a Brotli payload +-- caught by the guard below - stops the script and leaves the database +-- untouched (the open transaction rolls back when the connection closes) +-- rather than half-applied. +-- +-- Every Brotli payload is written under /tmp/adfa5088-pm-workdir, an +-- owner-only (mode 700) directory this script creates fresh and removes +-- again at the end of a successful run - not bare /tmp filenames, which +-- are guessable and world-writable, so another local user could pre-plant +-- a symlink or race the write/read pair (CWE-377). `mkdir -m` sets the +-- mode atomically at creation, and the preceding `rm -rf` means each run +-- starts from a directory it fully owns rather than trusting one left +-- over from an earlier run - including one left behind by a `.bail` +-- abort part-way through a previous run, since that skips the cleanup at +-- the end. Don't run two copies of this script at once against the same +-- database: both would share this one fixed workdir path. +-- +-- For each Content row: `.system rm -f /x.br` clears any stale +-- file, `.system echo "" | brotli -Z > /x.br` writes the +-- compressed payload (the uncompressed HTML is visible right there in +-- the command), then `INSERT INTO _content_guard SELECT +-- READFILE('/x.br')` is a deliberate assertion: `.system` +-- failures aren't SQL errors and `.bail` can't see them directly, but a +-- failed or empty Brotli run leaves the file missing or empty, and +-- _content_guard's `NOT NULL` + `CHECK (length(content) > 0)` turn that +-- into a real SQL error `.bail` does catch - before the real `INSERT INTO +-- Content` below it can run with bad data. _content_guard is a TEMP +-- table: connection-local, dropped automatically, never touches the real +-- schema. +-- +-- `.system`, `.bail`, and `READFILE()` require the sqlite3 CLI (not a +-- library binding). If `.system` is disabled in your sqlite3 build, +-- create the mode-700 working directory and run each `rm -f`/`echo ... | +-- brotli -Z > file` pair yourself via a shell first, then run just the +-- INSERT statements (the _content_guard assertion becomes redundant at +-- that point - the file either exists and is non-empty by the time you +-- run the script, or you'd have already seen the shell command fail). + +.bail on +BEGIN; + +-- A temp table (connection-local, never touches the real schema) whose +-- CHECK constraint turns a silently-empty or failed Brotli payload into a +-- real SQL error .bail can catch, before it ever reaches the real Content +-- table. +CREATE TEMP TABLE _content_guard (content BLOB NOT NULL CHECK (length(content) > 0)); + +-- Route every Brotli payload through an owner-only (mode 700) working +-- directory instead of bare /tmp filenames: a fixed name under world- +-- writable /tmp is guessable, so another local user could pre-plant a +-- symlink or race the write/read pair. mkdir -m sets the mode atomically +-- at creation (no separate chmod, no window with a wider mode); the prior +-- rm -rf makes each run start from a clean directory it fully owns, +-- rather than trusting one left over from an earlier run. mkdir itself +-- can fail without .bail seeing it (e.g. another process recreates the +-- path between the rm -rf and the mkdir), so assert the mode really is +-- 700 before trusting the directory with anything - the same guard-table +-- trick _content_guard uses for the Brotli payloads below. +.system rm -rf /tmp/adfa5088-pm-workdir +.system mkdir -m 700 /tmp/adfa5088-pm-workdir +.system stat --printf='%a' /tmp/adfa5088-pm-workdir > /tmp/adfa5088-pm-workdir/.mode +CREATE TEMP TABLE _workdir_guard (mode TEXT NOT NULL CHECK (mode = '700')); +INSERT INTO _workdir_guard SELECT CAST(READFILE('/tmp/adfa5088-pm-workdir/.mode') AS TEXT); + +-- Remove the dead "plugin.manager" tag: no code path can reach it any +-- more now that every widget has its own tag. Delete the TooltipButtons +-- row first (it references Tooltips.id via a foreign key). + +DELETE FROM TooltipButtons +WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'plugin.manager' AND categoryId = 1); + +DELETE FROM Tooltips WHERE tag = 'plugin.manager' AND categoryId = 1; + +-- Tooltips: idempotent upserts (all new tags) + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'plugin.manager.toolbar', 'Plugin Manager lists your installed plugins.', 'Use this screen to install a new plugin, open a plugin''s details, or find more plugins. The back button returns to Preferences.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'plugin.manager.download', 'Find more plugins to install.', 'Opens a webpage where you can find plugins to add to Code on the Go.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'plugin.manager.fab.install', 'Install a plugin from a file.', 'Opens a file picker so you can choose a plugin package (a .cgp file) to install.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'plugin.manager.emptystate', 'No plugins installed yet.', 'This message appears when you have no plugins installed. Use the download icon to find plugins, or the + button to install one from a file.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'plugin.manager.list', 'Your installed plugins.', 'Each row below shows one installed plugin. Tap a row to see its details, or use its menu button for more actions.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'plugin.manager.item', 'Tap for this plugin''s details.', 'Shows this plugin''s name, status, and version. Tap the row to see full details, including its description and version history.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'plugin.manager.item.menu', 'More actions for this plugin.', 'Opens a menu with actions for this plugin, such as enable, disable, uninstall, or view details. Which actions appear depends on the plugin''s current state.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +-- Content: Tier 3 HTML pages, one INSERT per Tooltips row above. + +.system rm -f /tmp/adfa5088-pm-workdir/adfa5088-pm-toolbar.br +.system echo "

Plugin Manager lists every plugin currently installed in Code on the Go.

From here you can install a new plugin from a file, find more plugins online, and open any installed plugin's details or actions.

" | brotli -Z > /tmp/adfa5088-pm-workdir/adfa5088-pm-toolbar.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-pm-workdir/adfa5088-pm-toolbar.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/plugin/manager/toolbar', 1, 12, READFILE('/tmp/adfa5088-pm-workdir/adfa5088-pm-toolbar.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-pm-workdir/adfa5088-pm-download.br +.system echo "

The download icon opens a webpage where you can find plugins to add to Code on the Go.

This opens in your browser or an in-app web view, outside Code on the Go itself.

" | brotli -Z > /tmp/adfa5088-pm-workdir/adfa5088-pm-download.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-pm-workdir/adfa5088-pm-download.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/plugin/manager/download', 1, 12, READFILE('/tmp/adfa5088-pm-workdir/adfa5088-pm-download.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-pm-workdir/adfa5088-pm-fab-install.br +.system echo "

The + button installs a plugin you already have as a file.

Tap it to open a file picker, choose a plugin package (a .cgp file), and confirm the install. This does not download anything - use the download icon first if you need to find a plugin file.

" | brotli -Z > /tmp/adfa5088-pm-workdir/adfa5088-pm-fab-install.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-pm-workdir/adfa5088-pm-fab-install.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/plugin/manager/fab/install', 1, 12, READFILE('/tmp/adfa5088-pm-workdir/adfa5088-pm-fab-install.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-pm-workdir/adfa5088-pm-emptystate.br +.system echo "

This message appears when Plugin Manager has no plugins to show.

Use the download icon at the top of the screen to find plugins, or the + button to install a plugin file you already have.

" | brotli -Z > /tmp/adfa5088-pm-workdir/adfa5088-pm-emptystate.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-pm-workdir/adfa5088-pm-emptystate.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/plugin/manager/emptystate', 1, 12, READFILE('/tmp/adfa5088-pm-workdir/adfa5088-pm-emptystate.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-pm-workdir/adfa5088-pm-list.br +.system echo "

This list shows every plugin installed in Code on the Go, one row per plugin.

Each row shows the plugin's name, version, and current status. Tap a row for its details, or use its menu button for more actions.

" | brotli -Z > /tmp/adfa5088-pm-workdir/adfa5088-pm-list.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-pm-workdir/adfa5088-pm-list.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/plugin/manager/list', 1, 12, READFILE('/tmp/adfa5088-pm-workdir/adfa5088-pm-list.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-pm-workdir/adfa5088-pm-item.br +.system echo "

This row represents one installed plugin.

It shows the plugin's name, version, and whether it is enabled, disabled, or failed to load. Tap the row to open its full details.

" | brotli -Z > /tmp/adfa5088-pm-workdir/adfa5088-pm-item.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-pm-workdir/adfa5088-pm-item.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/plugin/manager/item', 1, 12, READFILE('/tmp/adfa5088-pm-workdir/adfa5088-pm-item.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-pm-workdir/adfa5088-pm-item-menu.br +.system echo "

This button opens a menu of actions for this plugin.

Depending on the plugin's current state, the menu can include enabling it, disabling it, uninstalling it, or viewing its details.

" | brotli -Z > /tmp/adfa5088-pm-workdir/adfa5088-pm-item-menu.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-pm-workdir/adfa5088-pm-item-menu.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/plugin/manager/item/menu', 1, 12, READFILE('/tmp/adfa5088-pm-workdir/adfa5088-pm-item-menu.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +-- Tier 3 links: without a TooltipButtons row, a tooltip's popup has no way to +-- surface its Content page - Tier 1 (summary) and Tier 2 (detail) still work +-- from the Tooltips row alone, but the richer Content page above is otherwise +-- unreachable. buttonNumberId 1 matches the existing single-button convention +-- (see e.g. the debugger-panel tooltip). Idempotent: delete then insert, since +-- TooltipButtons has no unique constraint to upsert against. + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'plugin.manager.toolbar' AND categoryId = 1) AND uri = 'i/plugin/manager/toolbar'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'plugin.manager.toolbar' AND categoryId = 1), 1, 'Learn more', 'i/plugin/manager/toolbar'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'plugin.manager.download' AND categoryId = 1) AND uri = 'i/plugin/manager/download'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'plugin.manager.download' AND categoryId = 1), 1, 'Learn more', 'i/plugin/manager/download'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'plugin.manager.fab.install' AND categoryId = 1) AND uri = 'i/plugin/manager/fab/install'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'plugin.manager.fab.install' AND categoryId = 1), 1, 'Learn more', 'i/plugin/manager/fab/install'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'plugin.manager.emptystate' AND categoryId = 1) AND uri = 'i/plugin/manager/emptystate'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'plugin.manager.emptystate' AND categoryId = 1), 1, 'Learn more', 'i/plugin/manager/emptystate'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'plugin.manager.list' AND categoryId = 1) AND uri = 'i/plugin/manager/list'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'plugin.manager.list' AND categoryId = 1), 1, 'Learn more', 'i/plugin/manager/list'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'plugin.manager.item' AND categoryId = 1) AND uri = 'i/plugin/manager/item'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'plugin.manager.item' AND categoryId = 1), 1, 'Learn more', 'i/plugin/manager/item'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'plugin.manager.item.menu' AND categoryId = 1) AND uri = 'i/plugin/manager/item/menu'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'plugin.manager.item.menu' AND categoryId = 1), 1, 'Learn more', 'i/plugin/manager/item/menu'); +.system rm -rf /tmp/adfa5088-pm-workdir +COMMIT; diff --git a/docs/docdb/ADFA-5088-preference-tooltips.sql b/docs/docdb/ADFA-5088-preference-tooltips.sql new file mode 100644 index 0000000000..721d7ad4a5 --- /dev/null +++ b/docs/docdb/ADFA-5088-preference-tooltips.sql @@ -0,0 +1,804 @@ +-- ADFA-5088: Tooltips + Content rows for every Preferences menu item. +-- +-- Deliverable 1 (already merged) tags every Preferences row with its own +-- idetooltips TooltipTag.PREFS_* constant. This script fills in the +-- documentation database rows those tags look up: a Tooltips row per tag +-- (Tier 1 `summary` + Tier 2 `detail`) and a matching Content row (Tier 3 +-- HTML page) at a new, item-granular `i/prefs/...` path. +-- +-- Deliberately NOT included: prefs.top, prefs.general, prefs.editor, +-- prefs.editor.xml, prefs.termux, and prefs.git already exist in the real +-- documentation.db with good, real (non-empty) summary/detail text -- these +-- tags are reused as-is for the corresponding screen/toolbar row, so this +-- script leaves them alone rather than overwriting curated production +-- content with a draft. +-- +-- Both Tooltips (UNIQUE(categoryId, tag)) and Content (UNIQUE(path)) are +-- idempotent `INSERT ... ON CONFLICT ... DO UPDATE` upserts below, so the +-- whole script is safe to re-run. +-- +-- All rows use categoryId = 1 ("ide"), languageId = 1 ("EN-us"), +-- contentTypeId = 12 ("text/html", brotli-compressed). +-- +-- Apply against the real documentation.db: +-- sqlite3 documentation.db < ADFA-5088-preference-tooltips.sql +-- The whole script runs inside one transaction (BEGIN/COMMIT below) with +-- `.bail on`, so any failure - a bad SQL statement, or a Brotli payload +-- caught by the guard below - stops the script and leaves the database +-- untouched (the open transaction rolls back when the connection closes) +-- rather than half-applied. +-- +-- Every Brotli payload is written under /tmp/adfa5088-prefs-workdir, an +-- owner-only (mode 700) directory this script creates fresh and removes +-- again at the end of a successful run - not bare /tmp filenames, which +-- are guessable and world-writable, so another local user could pre-plant +-- a symlink or race the write/read pair (CWE-377). `mkdir -m` sets the +-- mode atomically at creation, and the preceding `rm -rf` means each run +-- starts from a directory it fully owns rather than trusting one left +-- over from an earlier run - including one left behind by a `.bail` +-- abort part-way through a previous run, since that skips the cleanup at +-- the end. Don't run two copies of this script at once against the same +-- database: both would share this one fixed workdir path. +-- +-- For each Content row: `.system rm -f /x.br` clears any stale +-- file, `.system echo "" | brotli -Z > /x.br` writes the +-- compressed payload (the uncompressed HTML is visible right there in +-- the command), then `INSERT INTO _content_guard SELECT +-- READFILE('/x.br')` is a deliberate assertion: `.system` +-- failures aren't SQL errors and `.bail` can't see them directly, but a +-- failed or empty Brotli run leaves the file missing or empty, and +-- _content_guard's `NOT NULL` + `CHECK (length(content) > 0)` turn that +-- into a real SQL error `.bail` does catch - before the real `INSERT INTO +-- Content` below it can run with bad data. _content_guard is a TEMP +-- table: connection-local, dropped automatically, never touches the real +-- schema. +-- +-- `.system`, `.bail`, and `READFILE()` require the sqlite3 CLI (not a +-- library binding). If `.system` is disabled in your sqlite3 build, +-- create the mode-700 working directory and run each `rm -f`/`echo ... | +-- brotli -Z > file` pair yourself via a shell first, then run just the +-- INSERT statements (the _content_guard assertion becomes redundant at +-- that point - the file either exists and is non-empty by the time you +-- run the script, or you'd have already seen the shell command fail). + +.bail on +BEGIN; + +-- A temp table (connection-local, never touches the real schema) whose +-- CHECK constraint turns a silently-empty or failed Brotli payload into a +-- real SQL error .bail can catch, before it ever reaches the real Content +-- table. +CREATE TEMP TABLE _content_guard (content BLOB NOT NULL CHECK (length(content) > 0)); + +-- Route every Brotli payload through an owner-only (mode 700) working +-- directory instead of bare /tmp filenames: a fixed name under world- +-- writable /tmp is guessable, so another local user could pre-plant a +-- symlink or race the write/read pair. mkdir -m sets the mode atomically +-- at creation (no separate chmod, no window with a wider mode); the prior +-- rm -rf makes each run start from a clean directory it fully owns, +-- rather than trusting one left over from an earlier run. mkdir itself +-- can fail without .bail seeing it (e.g. another process recreates the +-- path between the rm -rf and the mkdir), so assert the mode really is +-- 700 before trusting the directory with anything - the same guard-table +-- trick _content_guard uses for the Brotli payloads below. +.system rm -rf /tmp/adfa5088-prefs-workdir +.system mkdir -m 700 /tmp/adfa5088-prefs-workdir +.system stat --printf='%a' /tmp/adfa5088-prefs-workdir > /tmp/adfa5088-prefs-workdir/.mode +CREATE TEMP TABLE _workdir_guard (mode TEXT NOT NULL CHECK (mode = '700')); +INSERT INTO _workdir_guard SELECT CAST(READFILE('/tmp/adfa5088-prefs-workdir/.mode') AS TEXT); + +-- Tooltips: idempotent upserts (existing empty stub rows + brand new tags, +-- all in one form so the script can be re-run safely) + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.general.uimode', 'Choose light mode, dark mode, or match your device''s system setting.', 'This setting controls the color theme of the app. Pick Light, Dark, or Follow system. Follow system switches automatically when your device''s theme changes.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.general.language', 'Choose the display language for Code on the Go.', 'This setting changes the language of the app menus and text. Pick System Default to use your device language, or choose a specific language from the list.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.general.openlast', 'Reopen your last project automatically when the app starts.', 'When on, Code on the Go opens the last project you worked on as soon as it starts. When off, the app shows the project list instead.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.general.confirmopen', 'Ask for confirmation before opening your last project.', 'When on, Code on the Go shows a confirmation prompt before it reopens your last project. This gives you a chance to go to the project list instead.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.editor.fontsize', 'Set the editor text size.', 'This changes the size of code text in the editor, measured in sp (scale-independent pixels). Use a slider to pick a value between 6 and 32.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.editor.tabsize', 'Set how many spaces one tab indents.', 'This sets the number of spaces the editor uses for each indent level. Choose from 2, 4, 6, or 8 spaces.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.editor.nonprinting', 'Choose which whitespace and line-break marks the editor shows.', 'This opens a dialog with checkboxes for leading, trailing, inner, and empty-line whitespace, plus line-break marks. Turn on any you want the editor to display.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.editor.softtab', 'Insert spaces instead of a tab character when you press Tab.', 'When on, pressing the Tab key inserts spaces. When off, it inserts a tab character.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.editor.wordwrap', 'Break long lines so they fit on the screen.', 'When on, the editor wraps long lines onto multiple visual lines instead of scrolling sideways. This does not change the file content.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.editor.magnifier', 'Show a magnified view of text while you select it.', 'When on, pressing and holding on text shows a zoomed-in view of the area around your finger. This makes it easier to place the cursor precisely.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.editor.wordboundaries', 'Use spaces to define word boundaries when you double-tap to select.', 'When on, double-tapping a word selects text up to the nearest space. When off, the editor also uses punctuation to decide where a word ends.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.editor.matchcase', 'Match code suggestions even when letter case differs.', 'When on, autocomplete suggestions match class and member names regardless of whether you type upper or lower case letters.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.editor.deletelines', 'Delete a whole blank line with one backspace.', 'When on, pressing backspace on a line with no visible text removes the entire line at once.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.editor.smartbackspace', 'Delete a full indent level with one backspace.', 'When on, pressing backspace at the start of an indented line removes the whole indent level at once, instead of one character at a time.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.editor.stickyscroll', 'Keep the current code block header visible while scrolling.', 'When on, the editor pins the header line of the current class or method at the top of the screen while you scroll through its body.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.editor.pinlines', 'Keep line numbers visible when scrolling sideways.', 'When on, line numbers stay in place on the left side of the screen even when you scroll a long line horizontally.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.editor.googlestyle', 'Format Java code using Google''s style rules.', 'When on, the code formatter applies Google''s Java style conventions, such as its indentation and spacing rules, instead of the default style.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.buildrun', 'Change settings for building and running your app.', 'Set additional Gradle flags and control whether the app launches automatically after a run installs it.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.buildrun.autolaunch', 'Launch the app automatically after a successful run installs it.', 'When on, Code on the Go opens your app right after installing it, with no extra confirmation step.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.buildrun.flags', 'Choose extra Gradle flags to add to every build.', 'This opens a dialog with checkboxes for common Gradle command-line flags, such as --info or --offline. Any flag you turn on is added to every Gradle task the IDE runs.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.termux.loglevel', 'Set how much detail the terminal writes to its internal log.', 'This controls the terminal''s own internal logging level, used for troubleshooting the terminal itself. Higher levels record more detail.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.termux.keylogging', 'Log every key you press in the terminal, for debugging.', 'When on, the terminal records each key press to the system log. This is very verbose and can slow the app down, so leave it off unless you are debugging a keyboard issue.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.termux.margin', 'Adjust the terminal margin so the on-screen keyboard does not cover it.', 'When on, the terminal adjusts its margin to avoid being covered by the on-screen keyboard. If you notice screen flickering, turn this off.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.termux.nohardkeyboard', 'Show the on-screen keyboard only when no physical keyboard is connected.', 'When on, the terminal hides its on-screen keyboard while a hardware keyboard is connected, and shows it again once the hardware keyboard is disconnected.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.termux.softkeyboard', 'Show the on-screen keyboard in the terminal.', 'When on, the terminal shows its own on-screen keyboard for typing commands. This is on by default.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.xml.closebracket', 'Put a tag closing bracket on its own new line.', 'When on, the formatter places the final ">" or "/>" on a new line after the last attribute. This is off by default.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.xml.emptyelements', 'Choose how the formatter writes empty XML elements.', 'Pick Expand to write empty elements as an open and a close tag, Collapse to write them as a single self-closing tag, or Ignore to leave them as they are.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.xml.maxlinewidth', 'Set the maximum characters allowed on one line.', 'This sets the line-width limit, in characters, before the formatter wraps a line onto more than one line. The default is 80.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.xml.preserveattributes', 'Keep existing line breaks between attributes.', 'When on, the formatter leaves attributes on the lines you already put them on, instead of joining them onto one line. This is on by default.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.xml.preservenewlines', 'Set how many blank lines to keep between elements.', 'This sets the maximum number of blank lines the formatter keeps between elements when it reformats a file. Extra blank lines beyond this number are removed. The default is 2.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.xml.spacebeforeclose', 'Add a space before a tag self-closing slash.', 'When on, the formatter writes a space before "/>", producing "" instead of "". This is on by default.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.xml.splitattribindent', 'Set the indent size for attributes on split lines.', 'This sets how many spaces the formatter uses to indent an attribute placed on its own line. By default it is half the editor tab size.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.xml.trimwhitespace', 'Remove extra spaces at the end of lines.', 'When on, the formatter deletes any whitespace left at the end of each line. This is on by default.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.devoptions', 'Change experimental and debugging settings.', 'These settings are for troubleshooting Code on the Go itself, not for normal project configuration. Only change them if you are diagnosing a problem.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.devoptions.dumplogs', 'Save the IDE internal logs to a file.', 'When on, Code on the Go writes its internal logs to a file at $HOME/.cg/logs, for troubleshooting.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.devoptions.logsender', 'Show logs from your running app inside Code on the Go.', 'When on, Code on the Go displays log output from apps you run. Turn this off to stop showing those logs.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.editor.nonprinting.leading', 'Show whitespace at the start of each line.', 'When on, the editor marks spaces and tabs that come before the first visible character on a line.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.editor.nonprinting.trailing', 'Show whitespace at the end of each line.', 'When on, the editor marks spaces and tabs that come after the last visible character on a line.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.editor.nonprinting.inner', 'Show whitespace between words.', 'When on, the editor marks spaces and tabs that appear between two visible characters on a line.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.editor.nonprinting.emptylines', 'Show whitespace on lines with no visible text.', 'When on, the editor marks spaces and tabs on lines that contain only whitespace.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.editor.nonprinting.linebreaks', 'Show a mark at the end of each line.', 'When on, the editor draws a small symbol where each line ends, so line breaks are visible.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.xml.trimfinalnewline', 'Remove blank lines at the end of an XML file.', 'When on, the formatter deletes empty lines at the end of the file, leaving no trailing blank lines. This is off by default.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.xml.insertfinalnewline', 'Add one newline at the end of an XML file.', 'When on, the formatter makes sure the file ends with exactly one newline character. This is on by default.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.xml.splitattributes', 'Put each XML attribute on its own line.', 'When on, the formatter places every attribute of a tag on a separate line instead of one line. This is on by default.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.xml.joincdatalines', 'Combine multiple CDATA lines into one.', 'When on, the formatter merges the lines of a multi-line CDATA section into a single line. This is off by default.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.xml.joincommentlines', 'Combine multiple single-line comments into one.', 'When on, the formatter merges adjacent single-line XML comments into a single line. This is on by default.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.xml.joincontentlines', 'Combine multiple lines of element text into one.', 'When on, the formatter merges multiple lines of text inside an XML element into a single line. This is off by default.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.xml.preserveemptycontent', 'Keep tags that stand alone on an empty line as they are.', 'When on, the formatter leaves an element alone on its own blank line unchanged, instead of collapsing it. This is on by default.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.buildrun.flags.stacktrace', 'Show a full stack trace when a build fails.', 'Sets the Gradle --stacktrace flag. When a build fails, Gradle prints the full stack trace of the failure instead of a short summary.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.buildrun.flags.info', 'Show detailed log messages for each build task.', 'Sets the Gradle --info flag. Gradle prints detailed log messages as each task runs, instead of just a summary line for each task.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.buildrun.flags.debug', 'Show maximum detail in build logs.', 'Sets the Gradle --debug flag. Gradle prints very detailed, low-level log messages for every task. This produces a large amount of output and can slow the build down.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.buildrun.flags.scan', 'Publish a Gradle Build Scan report link.', 'Sets the Gradle --scan flag. After the build, Gradle uploads build data and gives you a link to a Build Scan report with detailed information.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.buildrun.flags.warningmodeall', 'Show every deprecation warning during the build.', 'Sets the Gradle --warning-mode all flag. Gradle lists every individual deprecation warning instead of just a count at the end of the build.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.buildrun.flags.buildcache', 'Reuse outputs from a previous build to speed up this one.', 'Sets the Gradle --build-cache flag. Gradle reuses task outputs from an earlier build when the inputs have not changed, which can make the build faster.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.buildrun.flags.offline', 'Build without any network access.', 'Sets the Gradle --offline flag. Gradle uses only dependencies already downloaded to your device and does not try to reach the network.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.termux.crashreports', 'Show a notification if the terminal crashes.', 'When on, Code on the Go shows a notification with a crash report after the terminal process crashes. This is on by default.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.git.username', 'Set the name used as the author of your commits.', 'This name is recorded on every Git commit you make from Code on the Go.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.git.useremail', 'Set the email address used as the author of your commits.', 'This email address is recorded on every Git commit you make from Code on the Go.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.pluginmanager', 'Manage IDE plugins and extensions.', 'This opens Plugin Manager, where you can install, remove, and configure plugins that add features to Code on the Go.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.about', 'See app version and other information about Code on the Go.', 'This opens the About screen, with details such as the app version and credits.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +-- Content: Tier 3 HTML pages, one INSERT per Tooltips row above. +-- Each pair shows the uncompressed HTML via `.system echo ... | brotli -Z` +-- then inserts the resulting compressed file with READFILE(). + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-general.br +.system echo "

The General screen holds settings that are not specific to the editor or to a single project. Use it to set the app theme, choose a display language, and control how Code on the Go opens the last project on launch.

Changes here apply immediately and affect the whole app.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-general.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-general.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/general', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-general.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-general-uimode.br +.system echo "

UI mode sets the color theme for Code on the Go.

  • Light: always use light colors.
  • Dark: always use dark colors.
  • Follow system: match your device's current theme, and switch automatically when it changes.
" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-general-uimode.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-general-uimode.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/general/uimode', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-general-uimode.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-general-language.br +.system echo "

Language sets the display language for the Code on the Go interface.

Choose System Default to use your device language setting, or pick one of the supported languages directly. The app restarts the affected screens to apply the change.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-general-language.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-general-language.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/general/language', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-general-language.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-general-openlast.br +.system echo "

Open last project controls what happens when you start Code on the Go.

Turn it on to skip the project list and go straight into your most recent project. Turn it off to see the project list every time.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-general-openlast.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-general-openlast.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/general/openlast', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-general-openlast.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-general-confirmopen.br +.system echo "

Confirm project opening adds a confirmation step before Code on the Go reopens your last project automatically.

Use this if you often want to switch to a different project instead of continuing the last one.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-general-confirmopen.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-general-confirmopen.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/general/confirmopen', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-general-confirmopen.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor.br +.system echo "

The Editor screen holds settings for the code editor: font size, tab size, word wrap, whitespace display, and other editing behavior.

Formatting options specific to XML files are on a separate sub-screen.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-fontsize.br +.system echo "

Font size sets the text size used in the code editor, in sp units.

Choose a larger value to make code easier to read, or a smaller value to fit more code on the screen. The allowed range is 6 to 32.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-fontsize.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-fontsize.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/fontsize', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-fontsize.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-tabsize.br +.system echo "

Tab size sets the number of spaces that one tab character represents in the editor.

Pick a value that matches your project code style: 2, 4, 6, or 8 spaces.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-tabsize.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-tabsize.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/tabsize', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-tabsize.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-nonprinting.br +.system echo "

Show non-printing characters controls which invisible characters, such as spaces, tabs, and line breaks, the editor marks visibly.

Open this setting to choose which kinds to show: leading whitespace, trailing whitespace, whitespace between words, whitespace on empty lines, and line-break marks.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-nonprinting.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-nonprinting.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/nonprinting', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-nonprinting.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-nonprinting-leading.br +.system echo "

Leading marks whitespace characters, such as spaces and tabs, that appear before the first visible character on a line.

Turn this on to see indentation whitespace clearly.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-nonprinting-leading.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-nonprinting-leading.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/nonprinting/leading', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-nonprinting-leading.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-nonprinting-trailing.br +.system echo "

Trailing marks whitespace characters that appear after the last visible character on a line.

Turn this on to spot stray trailing spaces or tabs.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-nonprinting-trailing.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-nonprinting-trailing.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/nonprinting/trailing', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-nonprinting-trailing.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-nonprinting-inner.br +.system echo "

Inner marks whitespace characters that appear between words or tokens within a line, rather than at the start or end.

Turn this on to check for irregular spacing inside a line.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-nonprinting-inner.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-nonprinting-inner.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/nonprinting/inner', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-nonprinting-inner.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-nonprinting-emptylines.br +.system echo "

Empty lines marks whitespace characters on lines that have no visible text, only spaces or tabs.

Turn this on to spot stray whitespace on otherwise blank lines.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-nonprinting-emptylines.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-nonprinting-emptylines.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/nonprinting/emptylines', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-nonprinting-emptylines.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-nonprinting-linebreaks.br +.system echo "

Line breaks draws a small marker at the end of each line to show where the line break occurs.

Turn this on to see line endings clearly, for example when comparing files with different line-ending styles.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-nonprinting-linebreaks.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-nonprinting-linebreaks.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/nonprinting/linebreaks', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-nonprinting-linebreaks.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-softtab.br +.system echo "

Use soft tab controls what the editor inserts when you press the Tab key.

Turn this on to insert spaces instead of a tab character. This can help keep code consistent across editors that render tabs differently.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-softtab.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-softtab.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/softtab', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-softtab.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-wordwrap.br +.system echo "

Word wrap breaks long lines of code into multiple visual lines so they fit within the visible width of the editor.

The underlying file is not changed; only the way it is displayed changes.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-wordwrap.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-wordwrap.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/wordwrap', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-wordwrap.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-magnifier.br +.system echo "

Enable magnifier shows a zoomed-in view of the text around your finger when you press and hold to select text.

This makes it easier to position the cursor precisely on a small screen.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-magnifier.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-magnifier.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/magnifier', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-magnifier.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-wordboundaries.br +.system echo "

Use spaces as word boundaries changes how the editor decides what counts as one word when you double-tap to select text.

When on, only blank space marks the edge of a word. When off, the editor also treats punctuation, such as periods and underscores, as word boundaries.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-wordboundaries.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-wordboundaries.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/wordboundaries', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-wordboundaries.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-matchcase.br +.system echo "

Match completions in lower case controls whether the code editor autocomplete feature is case-sensitive.

When on, typing in lower case still matches suggestions that use upper case letters, such as class names.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-matchcase.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-matchcase.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/matchcase', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-matchcase.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-deletelines.br +.system echo "

Delete empty lines on backspace changes what happens when you press backspace on a line with no visible text.

When on, the whole empty line is removed in one step, instead of removing one whitespace character at a time.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-deletelines.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-deletelines.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/deletelines', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-deletelines.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-smartbackspace.br +.system echo "

Smart backspace indent changes what one backspace press removes when the cursor is inside leading indentation.

When on, it removes a full indent level at once. When off, it removes one character at a time.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-smartbackspace.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-smartbackspace.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/smartbackspace', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-smartbackspace.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-stickyscroll.br +.system echo "

Sticky scroll keeps the header line of the code block you are inside, such as a class or method declaration, visible at the top of the editor while you scroll down through its contents.

This helps you keep track of context in long files.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-stickyscroll.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-stickyscroll.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/stickyscroll', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-stickyscroll.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-pinlines.br +.system echo "

Pin line numbers keeps the line-number column fixed on the left side of the editor.

Without this, scrolling a long line horizontally can move the line numbers out of view along with the code.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-pinlines.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-pinlines.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/pinlines', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-pinlines.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-googlestyle.br +.system echo "

Use Google Java Style code formatting applies Google's published conventions for Java source code, such as indentation, spacing, and line-wrapping rules, when you format code.

Turn this on if your project follows Google's Java style guide.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-googlestyle.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-googlestyle.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/googlestyle', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-googlestyle.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-xml.br +.system echo "

XML formatting options opens a sub-screen of settings that control how Code on the Go formats XML files, separate from the general editor settings.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-xml.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-xml.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/xml', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-xml.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-trimfinalnewline.br +.system echo "

Trim final new line removes empty lines at the very end of an XML file when the formatter runs.

This is off by default.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-trimfinalnewline.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-trimfinalnewline.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/trimfinalnewline', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-trimfinalnewline.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-insertfinalnewline.br +.system echo "

Insert final new line adds a single newline character at the end of an XML file if one is not already there.

Many tools expect files to end this way.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-insertfinalnewline.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-insertfinalnewline.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/insertfinalnewline', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-insertfinalnewline.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-splitattributes.br +.system echo "

Split attributes places each attribute of an XML tag on its own line when the formatter runs.

This makes tags with many attributes easier to read and to compare in version control.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-splitattributes.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-splitattributes.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/splitattributes', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-splitattributes.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-joincdatalines.br +.system echo "

Join CDATA lines combines the lines of a CDATA section into a single line when the formatter runs.

CDATA sections hold raw, unescaped text or markup inside an XML file.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-joincdatalines.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-joincdatalines.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/joincdatalines', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-joincdatalines.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-joincommentlines.br +.system echo "

Join comment lines combines adjacent single-line XML comments into one line when the formatter runs.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-joincommentlines.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-joincommentlines.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/joincommentlines', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-joincommentlines.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-joincontentlines.br +.system echo "

Join content lines combines multiple lines of text inside a single XML element into one line when the formatter runs.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-joincontentlines.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-joincontentlines.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/joincontentlines', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-joincontentlines.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-spacebeforeclose.br +.system echo "

Space before empty close tag adds one space before the closing /> of a self-closing XML tag, so the formatter produces <foo /> instead of <foo/>.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-spacebeforeclose.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-spacebeforeclose.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/spacebeforeclose', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-spacebeforeclose.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-preserveemptycontent.br +.system echo "

Preserve empty content keeps an XML element that appears alone on an otherwise empty line as it is, instead of collapsing or moving it during formatting.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-preserveemptycontent.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-preserveemptycontent.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/preserveemptycontent', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-preserveemptycontent.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-preserveattributes.br +.system echo "

Preserve attribute line breaks keeps attributes on the separate lines you already placed them on, instead of collapsing them onto a single line during formatting.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-preserveattributes.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-preserveattributes.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/preserveattributes', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-preserveattributes.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-closebracket.br +.system echo "

Closing bracket on new line places the final closing bracket of a tag, including self-closing tags, on its own new line after the last attribute.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-closebracket.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-closebracket.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/closebracket', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-closebracket.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-trimwhitespace.br +.system echo "

Trim trailing whitespace removes any spaces or tabs left at the end of each line when the formatter runs.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-trimwhitespace.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-trimwhitespace.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/trimwhitespace', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-trimwhitespace.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-maxlinewidth.br +.system echo "

Maximum line width sets the number of characters allowed on a single line before the formatter wraps it onto additional lines.

The default value is 80 characters.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-maxlinewidth.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-maxlinewidth.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/maxlinewidth', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-maxlinewidth.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-preservenewlines.br +.system echo "

Preserve new lines sets the maximum number of consecutive blank lines the formatter keeps between XML elements.

Any blank lines beyond this limit are removed. The default is 2.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-preservenewlines.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-preservenewlines.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/preservenewlines', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-preservenewlines.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-splitattribindent.br +.system echo "

Split attributes indent size sets the number of spaces used to indent an attribute placed on its own line by the formatter.

By default, this is half the editor's tab size.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-splitattribindent.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-splitattribindent.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/splitattribindent', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-splitattribindent.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-emptyelements.br +.system echo "

Empty elements behavior controls how the formatter writes an XML element that has no content.

  • Expand: write a separate open and close tag, for example <foo></foo>.
  • Collapse: write a single self-closing tag, for example <foo/>.
  • Ignore: leave the element as it is in the source file.

The default is Collapse.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-emptyelements.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-emptyelements.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/emptyelements', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-emptyelements.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-build.br +.system echo "

The Build & Run screen holds settings for the Gradle build and for running your app: additional Gradle command-line flags, and whether to launch the app automatically after installation.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-build.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-build.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-build.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-autolaunch.br +.system echo "

Launch app after installation controls whether Code on the Go opens your app automatically once a run finishes installing it on the device.

When off, you need to launch the app yourself.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-autolaunch.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-autolaunch.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/autolaunch', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-autolaunch.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags.br +.system echo "

Additional Gradle flags lets you turn on extra command-line flags that Code on the Go adds to every Gradle task it runs, such as build and sync.

Use this to get more detailed logs, use a build cache, or work offline, without typing the flags yourself each time.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-stacktrace.br +.system echo "

Sets the Gradle --stacktrace flag. When a build fails, Gradle prints the full stack trace of the failure, which can help diagnose the exact cause.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-stacktrace.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-stacktrace.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags/--stacktrace', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-stacktrace.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-info.br +.system echo "

Sets the Gradle --info flag to produce more detailed log messages as each task runs, rather than just a summary line for each task.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-info.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-info.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags/--info', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-info.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-debug.br +.system echo "

Sets the Gradle --debug flag. Gradle prints its most detailed log level for every task.

This produces a lot of output and can make the build noticeably slower. Use it only when you need to diagnose a specific problem.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-debug.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-debug.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags/--debug', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-debug.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-scan.br +.system echo "

Sets the Gradle --scan flag. After the build finishes, Gradle uploads data about the build and gives you a link to a Build Scan report.

The report shows detailed information about tasks, dependencies, and timing. This requires network access.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-scan.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-scan.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags/--scan', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-scan.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-warningmodeall.br +.system echo "

Sets the Gradle --warning-mode all flag. Gradle prints every individual deprecation warning during the build, instead of only a summary count at the end.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-warningmodeall.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-warningmodeall.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags/--warning-mode-all', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-warningmodeall.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-buildcache.br +.system echo "

Sets the Gradle --build-cache flag. Gradle reuses outputs from a previous build for any task whose inputs have not changed, instead of running that task again.

This can noticeably speed up repeated builds.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-buildcache.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-buildcache.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags/--build-cache', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-buildcache.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-offline.br +.system echo "

Sets the Gradle --offline flag. Gradle uses only the dependencies already downloaded to your device and does not attempt any network access.

The build fails if a required dependency has not been downloaded yet.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-offline.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-offline.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags/--offline', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-offline.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux.br +.system echo "

The Terminal screen holds settings for the built-in terminal emulator: internal logging, keyboard behavior, crash notifications, and screen margin.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/termux', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux-loglevel.br +.system echo "

Log Level sets how much detail the terminal emulator writes to its own internal log, separate from your app logs.

Use a higher level only when troubleshooting a terminal problem, since it produces more log output.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux-loglevel.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux-loglevel.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/termux/loglevel', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux-loglevel.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux-keylogging.br +.system echo "

Terminal View Key Logging records each key you press in the terminal to the system log.

This produces a large amount of log output and can cause performance problems, so it is off by default. Turn it on only to debug a keyboard-related issue.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux-keylogging.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux-keylogging.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/termux/keylogging', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux-keylogging.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux-crashreports.br +.system echo "

Crash Report Notifications controls whether Code on the Go shows a notification after the terminal process crashes.

The notification lets you view or share a report about the crash. This is on by default.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux-crashreports.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux-crashreports.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/termux/crashreports', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux-crashreports.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux-softkeyboard.br +.system echo "

Soft Keyboard Enabled turns on the terminal's own on-screen keyboard, which includes keys not found on a standard keyboard, such as Ctrl and Esc.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux-softkeyboard.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux-softkeyboard.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/termux/softkeyboard', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux-softkeyboard.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux-nohardkeyboard.br +.system echo "

Soft Keyboard Only If No Hardware shows the on-screen keyboard only when no physical keyboard is connected to your device.

This is off by default, meaning the on-screen keyboard can show even with a hardware keyboard connected.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux-nohardkeyboard.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux-nohardkeyboard.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/termux/nohardkeyboard', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux-nohardkeyboard.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux-margin.br +.system echo "

Terminal Margin Adjustment changes the terminal view margin to try to prevent the on-screen keyboard from covering part of the terminal or its extra keys row.

This is on by default. If it causes screen flickering on your device, turn it off.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux-margin.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux-margin.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/termux/margin', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux-margin.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-git.br +.system echo "

The Git screen sets your author identity for Git: the name and email address recorded on every commit you make from Code on the Go.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-git.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-git.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/git', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-git.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-git-username.br +.system echo "

User name sets the name recorded as the author on every Git commit you make from Code on the Go.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-git-username.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-git-username.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/git/username', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-git-username.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-git-useremail.br +.system echo "

User email sets the email address recorded as the author on every Git commit you make from Code on the Go.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-git-useremail.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-git-useremail.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/git/useremail', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-git-useremail.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-pluginmanager.br +.system echo "

Plugin Manager opens a screen where you can browse, install, remove, and configure plugins that extend Code on the Go with extra features.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-pluginmanager.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-pluginmanager.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/pluginmanager', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-pluginmanager.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-about.br +.system echo "

About Code on the Go opens a screen with details about the app, such as its version number and credits.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-about.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-about.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/about', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-about.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-devoptions.br +.system echo "

Developer Options holds experimental and debugging settings for Code on the Go, such as log dumping and log sending controls.

These settings are meant for troubleshooting, not everyday use.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-devoptions.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-devoptions.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/devoptions', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-devoptions.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-devoptions-dumplogs.br +.system echo "

Dump logs writes the Code on the Go internal logs to a file at ~/.cg/logs, inside your home directory.

Turn this on when you need to collect logs to diagnose a problem.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-devoptions-dumplogs.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-devoptions-dumplogs.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/devoptions/dumplogs', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-devoptions-dumplogs.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-devoptions-logsender.br +.system echo "

Enable LogSender controls whether Code on the Go shows log output from the apps you run, inside its own log viewer.

This is on by default. Turn it off if you do not want to see app logs inside the IDE.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-devoptions-logsender.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-devoptions-logsender.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/devoptions/logsender', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-devoptions-logsender.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +-- Tier 3 links: without a TooltipButtons row, a tooltip's popup has no way to +-- surface its Content page - Tier 1 (summary) and Tier 2 (detail) still work +-- from the Tooltips row alone, but the richer Content page above is otherwise +-- unreachable. buttonNumberId 1 matches the existing single-button convention +-- (see e.g. the debugger-panel tooltip). Idempotent: delete then insert, since +-- TooltipButtons has no unique constraint to upsert against. + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.general.uimode' AND categoryId = 1) AND uri = 'i/prefs/general/uimode'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.general.uimode' AND categoryId = 1), 1, 'Learn more', 'i/prefs/general/uimode'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.general.language' AND categoryId = 1) AND uri = 'i/prefs/general/language'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.general.language' AND categoryId = 1), 1, 'Learn more', 'i/prefs/general/language'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.general.openlast' AND categoryId = 1) AND uri = 'i/prefs/general/openlast'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.general.openlast' AND categoryId = 1), 1, 'Learn more', 'i/prefs/general/openlast'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.general.confirmopen' AND categoryId = 1) AND uri = 'i/prefs/general/confirmopen'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.general.confirmopen' AND categoryId = 1), 1, 'Learn more', 'i/prefs/general/confirmopen'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.editor.fontsize' AND categoryId = 1) AND uri = 'i/prefs/editor/fontsize'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.editor.fontsize' AND categoryId = 1), 1, 'Learn more', 'i/prefs/editor/fontsize'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.editor.tabsize' AND categoryId = 1) AND uri = 'i/prefs/editor/tabsize'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.editor.tabsize' AND categoryId = 1), 1, 'Learn more', 'i/prefs/editor/tabsize'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.editor.nonprinting' AND categoryId = 1) AND uri = 'i/prefs/editor/nonprinting'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.editor.nonprinting' AND categoryId = 1), 1, 'Learn more', 'i/prefs/editor/nonprinting'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.editor.softtab' AND categoryId = 1) AND uri = 'i/prefs/editor/softtab'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.editor.softtab' AND categoryId = 1), 1, 'Learn more', 'i/prefs/editor/softtab'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.editor.wordwrap' AND categoryId = 1) AND uri = 'i/prefs/editor/wordwrap'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.editor.wordwrap' AND categoryId = 1), 1, 'Learn more', 'i/prefs/editor/wordwrap'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.editor.magnifier' AND categoryId = 1) AND uri = 'i/prefs/editor/magnifier'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.editor.magnifier' AND categoryId = 1), 1, 'Learn more', 'i/prefs/editor/magnifier'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.editor.wordboundaries' AND categoryId = 1) AND uri = 'i/prefs/editor/wordboundaries'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.editor.wordboundaries' AND categoryId = 1), 1, 'Learn more', 'i/prefs/editor/wordboundaries'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.editor.matchcase' AND categoryId = 1) AND uri = 'i/prefs/editor/matchcase'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.editor.matchcase' AND categoryId = 1), 1, 'Learn more', 'i/prefs/editor/matchcase'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.editor.deletelines' AND categoryId = 1) AND uri = 'i/prefs/editor/deletelines'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.editor.deletelines' AND categoryId = 1), 1, 'Learn more', 'i/prefs/editor/deletelines'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.editor.smartbackspace' AND categoryId = 1) AND uri = 'i/prefs/editor/smartbackspace'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.editor.smartbackspace' AND categoryId = 1), 1, 'Learn more', 'i/prefs/editor/smartbackspace'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.editor.stickyscroll' AND categoryId = 1) AND uri = 'i/prefs/editor/stickyscroll'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.editor.stickyscroll' AND categoryId = 1), 1, 'Learn more', 'i/prefs/editor/stickyscroll'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.editor.pinlines' AND categoryId = 1) AND uri = 'i/prefs/editor/pinlines'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.editor.pinlines' AND categoryId = 1), 1, 'Learn more', 'i/prefs/editor/pinlines'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.editor.googlestyle' AND categoryId = 1) AND uri = 'i/prefs/editor/googlestyle'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.editor.googlestyle' AND categoryId = 1), 1, 'Learn more', 'i/prefs/editor/googlestyle'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.buildrun' AND categoryId = 1) AND uri = 'i/prefs/build'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.buildrun' AND categoryId = 1), 1, 'Learn more', 'i/prefs/build'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.buildrun.autolaunch' AND categoryId = 1) AND uri = 'i/prefs/build/autolaunch'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.buildrun.autolaunch' AND categoryId = 1), 1, 'Learn more', 'i/prefs/build/autolaunch'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.buildrun.flags' AND categoryId = 1) AND uri = 'i/prefs/build/flags'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.buildrun.flags' AND categoryId = 1), 1, 'Learn more', 'i/prefs/build/flags'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.termux.loglevel' AND categoryId = 1) AND uri = 'i/prefs/termux/loglevel'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.termux.loglevel' AND categoryId = 1), 1, 'Learn more', 'i/prefs/termux/loglevel'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.termux.keylogging' AND categoryId = 1) AND uri = 'i/prefs/termux/keylogging'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.termux.keylogging' AND categoryId = 1), 1, 'Learn more', 'i/prefs/termux/keylogging'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.termux.margin' AND categoryId = 1) AND uri = 'i/prefs/termux/margin'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.termux.margin' AND categoryId = 1), 1, 'Learn more', 'i/prefs/termux/margin'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.termux.nohardkeyboard' AND categoryId = 1) AND uri = 'i/prefs/termux/nohardkeyboard'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.termux.nohardkeyboard' AND categoryId = 1), 1, 'Learn more', 'i/prefs/termux/nohardkeyboard'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.termux.softkeyboard' AND categoryId = 1) AND uri = 'i/prefs/termux/softkeyboard'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.termux.softkeyboard' AND categoryId = 1), 1, 'Learn more', 'i/prefs/termux/softkeyboard'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.xml.closebracket' AND categoryId = 1) AND uri = 'i/prefs/xml/closebracket'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.xml.closebracket' AND categoryId = 1), 1, 'Learn more', 'i/prefs/xml/closebracket'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.xml.emptyelements' AND categoryId = 1) AND uri = 'i/prefs/xml/emptyelements'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.xml.emptyelements' AND categoryId = 1), 1, 'Learn more', 'i/prefs/xml/emptyelements'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.xml.maxlinewidth' AND categoryId = 1) AND uri = 'i/prefs/xml/maxlinewidth'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.xml.maxlinewidth' AND categoryId = 1), 1, 'Learn more', 'i/prefs/xml/maxlinewidth'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.xml.preserveattributes' AND categoryId = 1) AND uri = 'i/prefs/xml/preserveattributes'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.xml.preserveattributes' AND categoryId = 1), 1, 'Learn more', 'i/prefs/xml/preserveattributes'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.xml.preservenewlines' AND categoryId = 1) AND uri = 'i/prefs/xml/preservenewlines'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.xml.preservenewlines' AND categoryId = 1), 1, 'Learn more', 'i/prefs/xml/preservenewlines'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.xml.spacebeforeclose' AND categoryId = 1) AND uri = 'i/prefs/xml/spacebeforeclose'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.xml.spacebeforeclose' AND categoryId = 1), 1, 'Learn more', 'i/prefs/xml/spacebeforeclose'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.xml.splitattribindent' AND categoryId = 1) AND uri = 'i/prefs/xml/splitattribindent'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.xml.splitattribindent' AND categoryId = 1), 1, 'Learn more', 'i/prefs/xml/splitattribindent'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.xml.trimwhitespace' AND categoryId = 1) AND uri = 'i/prefs/xml/trimwhitespace'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.xml.trimwhitespace' AND categoryId = 1), 1, 'Learn more', 'i/prefs/xml/trimwhitespace'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.devoptions' AND categoryId = 1) AND uri = 'i/prefs/devoptions'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.devoptions' AND categoryId = 1), 1, 'Learn more', 'i/prefs/devoptions'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.devoptions.dumplogs' AND categoryId = 1) AND uri = 'i/prefs/devoptions/dumplogs'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.devoptions.dumplogs' AND categoryId = 1), 1, 'Learn more', 'i/prefs/devoptions/dumplogs'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.devoptions.logsender' AND categoryId = 1) AND uri = 'i/prefs/devoptions/logsender'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.devoptions.logsender' AND categoryId = 1), 1, 'Learn more', 'i/prefs/devoptions/logsender'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.editor.nonprinting.leading' AND categoryId = 1) AND uri = 'i/prefs/editor/nonprinting/leading'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.editor.nonprinting.leading' AND categoryId = 1), 1, 'Learn more', 'i/prefs/editor/nonprinting/leading'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.editor.nonprinting.trailing' AND categoryId = 1) AND uri = 'i/prefs/editor/nonprinting/trailing'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.editor.nonprinting.trailing' AND categoryId = 1), 1, 'Learn more', 'i/prefs/editor/nonprinting/trailing'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.editor.nonprinting.inner' AND categoryId = 1) AND uri = 'i/prefs/editor/nonprinting/inner'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.editor.nonprinting.inner' AND categoryId = 1), 1, 'Learn more', 'i/prefs/editor/nonprinting/inner'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.editor.nonprinting.emptylines' AND categoryId = 1) AND uri = 'i/prefs/editor/nonprinting/emptylines'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.editor.nonprinting.emptylines' AND categoryId = 1), 1, 'Learn more', 'i/prefs/editor/nonprinting/emptylines'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.editor.nonprinting.linebreaks' AND categoryId = 1) AND uri = 'i/prefs/editor/nonprinting/linebreaks'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.editor.nonprinting.linebreaks' AND categoryId = 1), 1, 'Learn more', 'i/prefs/editor/nonprinting/linebreaks'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.xml.trimfinalnewline' AND categoryId = 1) AND uri = 'i/prefs/xml/trimfinalnewline'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.xml.trimfinalnewline' AND categoryId = 1), 1, 'Learn more', 'i/prefs/xml/trimfinalnewline'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.xml.insertfinalnewline' AND categoryId = 1) AND uri = 'i/prefs/xml/insertfinalnewline'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.xml.insertfinalnewline' AND categoryId = 1), 1, 'Learn more', 'i/prefs/xml/insertfinalnewline'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.xml.splitattributes' AND categoryId = 1) AND uri = 'i/prefs/xml/splitattributes'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.xml.splitattributes' AND categoryId = 1), 1, 'Learn more', 'i/prefs/xml/splitattributes'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.xml.joincdatalines' AND categoryId = 1) AND uri = 'i/prefs/xml/joincdatalines'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.xml.joincdatalines' AND categoryId = 1), 1, 'Learn more', 'i/prefs/xml/joincdatalines'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.xml.joincommentlines' AND categoryId = 1) AND uri = 'i/prefs/xml/joincommentlines'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.xml.joincommentlines' AND categoryId = 1), 1, 'Learn more', 'i/prefs/xml/joincommentlines'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.xml.joincontentlines' AND categoryId = 1) AND uri = 'i/prefs/xml/joincontentlines'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.xml.joincontentlines' AND categoryId = 1), 1, 'Learn more', 'i/prefs/xml/joincontentlines'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.xml.preserveemptycontent' AND categoryId = 1) AND uri = 'i/prefs/xml/preserveemptycontent'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.xml.preserveemptycontent' AND categoryId = 1), 1, 'Learn more', 'i/prefs/xml/preserveemptycontent'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.buildrun.flags.stacktrace' AND categoryId = 1) AND uri = 'i/prefs/build/flags/--stacktrace'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.buildrun.flags.stacktrace' AND categoryId = 1), 1, 'Learn more', 'i/prefs/build/flags/--stacktrace'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.buildrun.flags.info' AND categoryId = 1) AND uri = 'i/prefs/build/flags/--info'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.buildrun.flags.info' AND categoryId = 1), 1, 'Learn more', 'i/prefs/build/flags/--info'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.buildrun.flags.debug' AND categoryId = 1) AND uri = 'i/prefs/build/flags/--debug'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.buildrun.flags.debug' AND categoryId = 1), 1, 'Learn more', 'i/prefs/build/flags/--debug'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.buildrun.flags.scan' AND categoryId = 1) AND uri = 'i/prefs/build/flags/--scan'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.buildrun.flags.scan' AND categoryId = 1), 1, 'Learn more', 'i/prefs/build/flags/--scan'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.buildrun.flags.warningmodeall' AND categoryId = 1) AND uri = 'i/prefs/build/flags/--warning-mode-all'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.buildrun.flags.warningmodeall' AND categoryId = 1), 1, 'Learn more', 'i/prefs/build/flags/--warning-mode-all'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.buildrun.flags.buildcache' AND categoryId = 1) AND uri = 'i/prefs/build/flags/--build-cache'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.buildrun.flags.buildcache' AND categoryId = 1), 1, 'Learn more', 'i/prefs/build/flags/--build-cache'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.buildrun.flags.offline' AND categoryId = 1) AND uri = 'i/prefs/build/flags/--offline'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.buildrun.flags.offline' AND categoryId = 1), 1, 'Learn more', 'i/prefs/build/flags/--offline'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.termux.crashreports' AND categoryId = 1) AND uri = 'i/prefs/termux/crashreports'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.termux.crashreports' AND categoryId = 1), 1, 'Learn more', 'i/prefs/termux/crashreports'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.git.username' AND categoryId = 1) AND uri = 'i/prefs/git/username'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.git.username' AND categoryId = 1), 1, 'Learn more', 'i/prefs/git/username'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.git.useremail' AND categoryId = 1) AND uri = 'i/prefs/git/useremail'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.git.useremail' AND categoryId = 1), 1, 'Learn more', 'i/prefs/git/useremail'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.pluginmanager' AND categoryId = 1) AND uri = 'i/prefs/pluginmanager'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.pluginmanager' AND categoryId = 1), 1, 'Learn more', 'i/prefs/pluginmanager'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.about' AND categoryId = 1) AND uri = 'i/prefs/about'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.about' AND categoryId = 1), 1, 'Learn more', 'i/prefs/about'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.general' AND categoryId = 1) AND uri = 'i/prefs/general'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.general' AND categoryId = 1), 1, 'Learn more', 'i/prefs/general'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.editor' AND categoryId = 1) AND uri = 'i/prefs/editor'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.editor' AND categoryId = 1), 1, 'Learn more', 'i/prefs/editor'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.editor.xml' AND categoryId = 1) AND uri = 'i/prefs/editor/xml'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.editor.xml' AND categoryId = 1), 1, 'Learn more', 'i/prefs/editor/xml'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.termux' AND categoryId = 1) AND uri = 'i/prefs/termux'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.termux' AND categoryId = 1), 1, 'Learn more', 'i/prefs/termux'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.git' AND categoryId = 1) AND uri = 'i/prefs/git'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.git' AND categoryId = 1), 1, 'Learn more', 'i/prefs/git'); +.system rm -rf /tmp/adfa5088-prefs-workdir +COMMIT; diff --git a/idetooltips/src/main/java/com/itsaky/androidide/idetooltips/TooltipTag.kt b/idetooltips/src/main/java/com/itsaky/androidide/idetooltips/TooltipTag.kt index 02a0571d1f..5a6e7438fd 100644 --- a/idetooltips/src/main/java/com/itsaky/androidide/idetooltips/TooltipTag.kt +++ b/idetooltips/src/main/java/com/itsaky/androidide/idetooltips/TooltipTag.kt @@ -40,16 +40,97 @@ object TooltipTag { */ fun gradleTaskTooltipTag(taskPath: String): String = "gradle." + taskPath.removePrefix(":") - // General Preferences + // Preferences - top level rows (Configure category and its screens) const val PREFS_TOP = "prefs.top" - const val PREFS_EDITOR = "prefs.editor" const val PREFS_GENERAL = "prefs.general" + const val PREFS_EDITOR = "prefs.editor" const val PREFS_BUILD_RUN = "prefs.buildrun" - const val PREFS_GRADLE = "prefs.gradle" const val PREFS_TERMUX = "prefs.termux" + const val PREFS_GIT = "prefs.git" + const val PREFS_PLUGIN_MANAGER = "prefs.pluginmanager" + const val PREFS_ABOUT = "prefs.about" + const val PREFS_DEVOPTIONS = "prefs.devoptions" + + // Preferences - General screen + const val PREFS_GENERAL_UIMODE = "prefs.general.uimode" + const val PREFS_GENERAL_LANGUAGE = "prefs.general.language" + const val PREFS_GENERAL_OPENLAST = "prefs.general.openlast" + const val PREFS_GENERAL_CONFIRMOPEN = "prefs.general.confirmopen" + + // Preferences - Editor screen (Common + Java categories) + const val PREFS_EDITOR_FONTSIZE = "prefs.editor.fontsize" + const val PREFS_EDITOR_TABSIZE = "prefs.editor.tabsize" + const val PREFS_EDITOR_NONPRINTING = "prefs.editor.nonprinting" + const val PREFS_EDITOR_NONPRINTING_LEADING = "prefs.editor.nonprinting.leading" + const val PREFS_EDITOR_NONPRINTING_TRAILING = "prefs.editor.nonprinting.trailing" + const val PREFS_EDITOR_NONPRINTING_INNER = "prefs.editor.nonprinting.inner" + const val PREFS_EDITOR_NONPRINTING_EMPTYLINES = "prefs.editor.nonprinting.emptylines" + const val PREFS_EDITOR_NONPRINTING_LINEBREAKS = "prefs.editor.nonprinting.linebreaks" + const val PREFS_EDITOR_SOFTTAB = "prefs.editor.softtab" + const val PREFS_EDITOR_WORDWRAP = "prefs.editor.wordwrap" + const val PREFS_EDITOR_MAGNIFIER = "prefs.editor.magnifier" + const val PREFS_EDITOR_WORDBOUNDARIES = "prefs.editor.wordboundaries" + const val PREFS_EDITOR_MATCHCASE = "prefs.editor.matchcase" + const val PREFS_EDITOR_DELETELINES = "prefs.editor.deletelines" + const val PREFS_EDITOR_SMARTBACKSPACE = "prefs.editor.smartbackspace" + const val PREFS_EDITOR_STICKYSCROLL = "prefs.editor.stickyscroll" + const val PREFS_EDITOR_PINLINES = "prefs.editor.pinlines" + const val PREFS_EDITOR_GOOGLESTYLE = "prefs.editor.googlestyle" + + // Preferences - Editor > XML formatting options screen const val PREFS_EDITOR_XML = "prefs.editor.xml" - const val PREFS_DEVELOPER = "prefs.developer" - const val PLUGIN_MANAGER = "plugin.manager" + const val PREFS_XML_TRIMFINALNEWLINE = "prefs.xml.trimfinalnewline" + const val PREFS_XML_INSERTFINALNEWLINE = "prefs.xml.insertfinalnewline" + const val PREFS_XML_SPLITATTRIBUTES = "prefs.xml.splitattributes" + const val PREFS_XML_JOINCDATALINES = "prefs.xml.joincdatalines" + const val PREFS_XML_JOINCOMMENTLINES = "prefs.xml.joincommentlines" + const val PREFS_XML_JOINCONTENTLINES = "prefs.xml.joincontentlines" + const val PREFS_XML_SPACEBEFORECLOSE = "prefs.xml.spacebeforeclose" + const val PREFS_XML_PRESERVEEMPTYCONTENT = "prefs.xml.preserveemptycontent" + const val PREFS_XML_PRESERVEATTRIBUTES = "prefs.xml.preserveattributes" + const val PREFS_XML_CLOSEBRACKET = "prefs.xml.closebracket" + const val PREFS_XML_TRIMWHITESPACE = "prefs.xml.trimwhitespace" + const val PREFS_XML_MAXLINEWIDTH = "prefs.xml.maxlinewidth" + const val PREFS_XML_PRESERVENEWLINES = "prefs.xml.preservenewlines" + const val PREFS_XML_SPLITATTRIBINDENT = "prefs.xml.splitattribindent" + const val PREFS_XML_EMPTYELEMENTS = "prefs.xml.emptyelements" + + // Preferences - Build & Run screen + const val PREFS_BUILDRUN_AUTOLAUNCH = "prefs.buildrun.autolaunch" + const val PREFS_BUILDRUN_FLAGS = "prefs.buildrun.flags" + const val PREFS_BUILDRUN_FLAGS_STACKTRACE = "prefs.buildrun.flags.stacktrace" + const val PREFS_BUILDRUN_FLAGS_INFO = "prefs.buildrun.flags.info" + const val PREFS_BUILDRUN_FLAGS_DEBUG = "prefs.buildrun.flags.debug" + const val PREFS_BUILDRUN_FLAGS_SCAN = "prefs.buildrun.flags.scan" + const val PREFS_BUILDRUN_FLAGS_WARNINGMODEALL = "prefs.buildrun.flags.warningmodeall" + const val PREFS_BUILDRUN_FLAGS_BUILDCACHE = "prefs.buildrun.flags.buildcache" + const val PREFS_BUILDRUN_FLAGS_OFFLINE = "prefs.buildrun.flags.offline" + + // Preferences - Terminal screen + const val PREFS_TERMUX_LOGLEVEL = "prefs.termux.loglevel" + const val PREFS_TERMUX_KEYLOGGING = "prefs.termux.keylogging" + const val PREFS_TERMUX_CRASHREPORTS = "prefs.termux.crashreports" + const val PREFS_TERMUX_SOFTKEYBOARD = "prefs.termux.softkeyboard" + const val PREFS_TERMUX_NOHARDKEYBOARD = "prefs.termux.nohardkeyboard" + const val PREFS_TERMUX_MARGIN = "prefs.termux.margin" + + // Preferences - Git screen + const val PREFS_GIT_USERNAME = "prefs.git.username" + const val PREFS_GIT_USEREMAIL = "prefs.git.useremail" + + // Preferences - Developer options screen + const val PREFS_DEVOPTIONS_DUMPLOGS = "prefs.devoptions.dumplogs" + const val PREFS_DEVOPTIONS_LOGSENDER = "prefs.devoptions.logsender" + + // Plugin Manager screen + const val PLUGIN_MANAGER_TOOLBAR = "plugin.manager.toolbar" + const val PLUGIN_MANAGER_DOWNLOAD = "plugin.manager.download" + const val PLUGIN_MANAGER_FAB_INSTALL = "plugin.manager.fab.install" + const val PLUGIN_MANAGER_EMPTY_STATE = "plugin.manager.emptystate" + const val PLUGIN_MANAGER_LIST = "plugin.manager.list" + const val PLUGIN_MANAGER_ITEM = "plugin.manager.item" + const val PLUGIN_MANAGER_ITEM_MENU = "plugin.manager.item.menu" + const val TEMPLATE_TABBED_ACTIVITY = "template.tabbed.activity" const val TEMPLATE_LEGACY_PROJECT = "template.legacy.project" const val TEMPLATE_EMPTY_ACTIVITY = "template.empty.activity" diff --git a/preferences/src/main/java/com/itsaky/androidide/preferences/ChoiceBasedDialogPreference.kt b/preferences/src/main/java/com/itsaky/androidide/preferences/ChoiceBasedDialogPreference.kt index 949502f147..8774c3d538 100644 --- a/preferences/src/main/java/com/itsaky/androidide/preferences/ChoiceBasedDialogPreference.kt +++ b/preferences/src/main/java/com/itsaky/androidide/preferences/ChoiceBasedDialogPreference.kt @@ -18,8 +18,10 @@ package com.itsaky.androidide.preferences import androidx.annotation.CallSuper +import androidx.appcompat.app.AlertDialog import androidx.preference.Preference import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.itsaky.androidide.idetooltips.TooltipManager /** * Base class for dialog preferences which allows users to choose from multiple items. Subclasses @@ -27,53 +29,76 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder * * @author Akash Yadav */ -abstract class ChoiceBasedDialogPreference : DialogPreference(), PreferenceChoices { +abstract class ChoiceBasedDialogPreference : + DialogPreference(), + PreferenceChoices { + private var choices = emptyArray() - private var choices = emptyArray() + final override fun onConfigureDialog( + preference: Preference, + dialog: MaterialAlertDialogBuilder, + ) { + choices = getEntries(preference) - final override fun onConfigureDialog(preference: Preference, dialog: MaterialAlertDialogBuilder) { - choices = getEntries(preference) + val selections = BooleanArray(choices.size) { choices[it].isChecked } + onConfigureDialogChoices(preference, dialog, choices, selections) - val selections = BooleanArray(choices.size) { choices[it].isChecked } - onConfigureDialogChoices(preference, dialog, choices, selections) + dialog.setPositiveButton(android.R.string.ok) { dialogInterface, _ -> + dialogInterface.dismiss() + onChoicesConfirmed(preference, choices) + } - dialog.setPositiveButton(android.R.string.ok) { dialogInterface, _ -> - dialogInterface.dismiss() - onChoicesConfirmed(preference, choices) - } + dialog.setNegativeButton(android.R.string.cancel) { dialogInterface, _ -> + dialogInterface.dismiss() + onChoicesCancelled(preference) + } + } - dialog.setNegativeButton(android.R.string.cancel) { dialogInterface, _ -> - dialogInterface.dismiss() - onChoicesCancelled(preference) - } - } + // The choice list is a ListView, which applyLongPressRecursively() deliberately skips (its rows + // are recycled, so a static recursive walk can't reach them) - wire per-row long-press here + // instead, falling back to the dialog's own tag for entries with none of their own. + final override fun onDialogShown( + preference: Preference, + dialog: AlertDialog, + ) { + dialog.listView?.setOnItemLongClickListener { _, view, position, _ -> + val tag = choices.getOrNull(position)?.tooltipTag?.takeIf { it.isNotEmpty() } ?: tooltipTag + if (tag.isNotEmpty()) { + TooltipManager.showIdeCategoryTooltip(preference.context, view, tag) + } + true + } + } - @CallSuper - override fun onSelectionChanged( - preference: Preference, - entry: PreferenceChoices.Entry, - position: Int, - isSelected: Boolean - ) { - entry._isChecked = isSelected - } + @CallSuper + override fun onSelectionChanged( + preference: Preference, + entry: PreferenceChoices.Entry, + position: Int, + isSelected: Boolean, + ) { + entry._isChecked = isSelected + } - /** - * Configure the dialog choices. - */ - protected abstract fun onConfigureDialogChoices( - preference: Preference, - dialog: MaterialAlertDialogBuilder, - entries: Array, - selections: BooleanArray - ) + /** + * Configure the dialog choices. + */ + protected abstract fun onConfigureDialogChoices( + preference: Preference, + dialog: MaterialAlertDialogBuilder, + entries: Array, + selections: BooleanArray, + ) - override fun onChoicesConfirmed(preference: Preference, entries: Array) { - } + override fun onChoicesConfirmed( + preference: Preference, + entries: Array, + ) { + } - override fun onChoicesCancelled(preference: Preference) {} + override fun onChoicesCancelled(preference: Preference) {} - final override fun onDialogCancelled(preference: Preference) { - onChoicesCancelled(preference) - } -} \ No newline at end of file + final override fun onDialogCancelled(preference: Preference) { + onChoicesCancelled(preference) + } +} diff --git a/preferences/src/main/java/com/itsaky/androidide/preferences/DialogPreference.kt b/preferences/src/main/java/com/itsaky/androidide/preferences/DialogPreference.kt index 4aedd03ab2..b1b7aceb3e 100644 --- a/preferences/src/main/java/com/itsaky/androidide/preferences/DialogPreference.kt +++ b/preferences/src/main/java/com/itsaky/androidide/preferences/DialogPreference.kt @@ -17,6 +17,7 @@ package com.itsaky.androidide.preferences +import androidx.appcompat.app.AlertDialog import androidx.preference.Preference import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.itsaky.androidide.idetooltips.TooltipManager @@ -29,41 +30,55 @@ import com.itsaky.androidide.utils.applyLongPressRecursively * @author Akash Yadav */ abstract class DialogPreference : SimplePreference() { + open val dialogTitle: Int + get() = this.title - open val dialogTitle: Int - get() = this.title + open val dialogMessage: Int? = null + open val dialogCancellable: Boolean = true - open val dialogMessage: Int? = null - open val dialogCancellable: Boolean = true - open val tooltipTag: String = "" + override fun onPreferenceClick(preference: Preference): Boolean { + val dialog = DialogUtils.newMaterialDialogBuilder(preference.context) + dialog.setTitle(this.dialogTitle) + dialogMessage?.let { dialog.setMessage(it) } + dialog.setCancelable(this.dialogCancellable) + dialog.setOnCancelListener { onDialogCancelled(preference) } + onConfigureDialog(preference, dialog) + val alertDialog = dialog.create() + alertDialog.show() - override fun onPreferenceClick(preference: Preference): Boolean { - val dialog = DialogUtils.newMaterialDialogBuilder(preference.context) - dialog.setTitle(this.dialogTitle) - dialogMessage?.let { dialog.setMessage(it) } - dialog.setCancelable(this.dialogCancellable) - dialog.setOnCancelListener { onDialogCancelled(preference) } - onConfigureDialog(preference, dialog) - val alertDialog = dialog.create() - alertDialog.show() + alertDialog.window?.decorView?.applyLongPressRecursively { + if (tooltipTag.isNotEmpty()) { + TooltipManager.showIdeCategoryTooltip(preference.context, it, tooltipTag) + } + true + } - alertDialog.window?.decorView?.applyLongPressRecursively { - TooltipManager.showIdeCategoryTooltip(preference.context, it, tooltipTag) - true - } + onDialogShown(preference, alertDialog) - return true - } + return true + } - protected open fun onConfigureDialog(preference: Preference, - dialog: MaterialAlertDialogBuilder) { - } + /** + * Called after the dialog is shown. Subclasses whose dialog content isn't covered by + * [applyLongPressRecursively] (e.g. a [android.widget.ListView] of choices, which it skips + * because its rows are recycled) can use this to wire up their own tooltip triggers. + */ + protected open fun onDialogShown( + preference: Preference, + dialog: AlertDialog, + ) {} - /** - * Called when the dialog is cancelled by the user (e.g. the system back button), as opposed to - * being dismissed by an action button. The default behaviour discards any unconfirmed changes, - * matching the dialog's cancel button. - */ - protected open fun onDialogCancelled(preference: Preference) { - } + protected open fun onConfigureDialog( + preference: Preference, + dialog: MaterialAlertDialogBuilder, + ) { + } + + /** + * Called when the dialog is cancelled by the user (e.g. the system back button), as opposed to + * being dismissed by an action button. The default behaviour discards any unconfirmed changes, + * matching the dialog's cancel button. + */ + protected open fun onDialogCancelled(preference: Preference) { + } } diff --git a/preferences/src/main/java/com/itsaky/androidide/preferences/IPreference.kt b/preferences/src/main/java/com/itsaky/androidide/preferences/IPreference.kt index c82c385dab..9b397ee82d 100644 --- a/preferences/src/main/java/com/itsaky/androidide/preferences/IPreference.kt +++ b/preferences/src/main/java/com/itsaky/androidide/preferences/IPreference.kt @@ -28,18 +28,20 @@ import kotlinx.parcelize.Parcelize * @author Akash Yadav */ abstract class IPreference : Parcelable { + /** Icon resource for this preference. */ + open val icon: Int? = null - /** Icon resource for this preference. */ - open val icon: Int? = null + /** Key that will be used to store the value of this preference in shared preferences. */ + abstract val key: String - /** Key that will be used to store the value of this preference in shared preferences. */ - abstract val key: String + /** The title of the preference. */ + abstract val title: Int - /** The title of the preference. */ - abstract val title: Int + /** The summary of the preference. */ + open val summary: Int? = null - /** The summary of the preference. */ - open val summary: Int? = null + /** Tag used to look up this preference's tooltip in the documentation database. */ + open val tooltipTag: String = "" - abstract fun onCreateView(context: Context): Preference + abstract fun onCreateView(context: Context): Preference } diff --git a/preferences/src/main/java/com/itsaky/androidide/preferences/PreferenceChoices.kt b/preferences/src/main/java/com/itsaky/androidide/preferences/PreferenceChoices.kt index 220ba8ad7c..4f0ecbe969 100644 --- a/preferences/src/main/java/com/itsaky/androidide/preferences/PreferenceChoices.kt +++ b/preferences/src/main/java/com/itsaky/androidide/preferences/PreferenceChoices.kt @@ -26,58 +26,66 @@ import androidx.preference.Preference * @author Akash Yadav */ interface PreferenceChoices { + /** + * Get the entries for this preference. + */ + fun getEntries(preference: Preference): Array - /** - * Get the entries for this preference. - */ - fun getEntries(preference: Preference): Array + /** + * Called when an item is selected from the single choice list. + * + * @param position The position of the selected item. + * @param isSelected Whether the item is selected. + */ + fun onSelectionChanged( + preference: Preference, + entry: Entry, + position: Int, + isSelected: Boolean, + ) - /** - * Called when an item is selected from the single choice list. - * - * @param position The position of the selected item. - * @param isSelected Whether the item is selected. - */ - fun onSelectionChanged(preference: Preference, entry: Entry, position: Int, isSelected: Boolean) + /** + * Called when the user confirms the selections. + * + * @param entries The entries. + */ + fun onChoicesConfirmed( + preference: Preference, + entries: Array, + ) - /** - * Called when the user confirms the selections. - * - * @param entries The entries. - */ - fun onChoicesConfirmed(preference: Preference, entries: Array) + /** + * Called when the user cancels the selections. + */ + fun onChoicesCancelled(preference: Preference) - /** - * Called when the user cancels the selections. - */ - fun onChoicesCancelled(preference: Preference) + /** + * Entry in [PreferenceChoices]. + * + * @property label The label for the entry. + * @property isChecked Whether the item is checked or not. + * @property data The data object for the value. + * @property tooltipTag Tag used to look up this entry's own tooltip, if it has one distinct + * from the dialog's tooltip. Empty when the entry has no tooltip of its own. + */ + data class Entry( + val label: CharSequence, + @Suppress("PropertyName") internal var _isChecked: Boolean, + val data: Any, + val tooltipTag: String = "", + ) { + val isChecked: Boolean + get() = _isChecked - /** - * Entry in [PreferenceChoices]. - * - * @property label The label for the entry. - * @property isChecked Whether the item is checked or not. - * @property data The data object for the value. - */ - data class Entry( - val label: CharSequence, - @Suppress("PropertyName") internal var _isChecked: Boolean, - val data: Any, - ) { - - val isChecked: Boolean - get() = _isChecked - - companion object { - - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) - val EMPTY = Entry("", false, 0) - } - } + companion object { + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) + val EMPTY = Entry("", false, 0) + } + } } /** * Map this [PreferenceChoices.Entry] array to entry labels. */ internal val Array.labels: Array - get() = Array(size) { this[it].label } \ No newline at end of file + get() = Array(size) { this[it].label } diff --git a/preferences/src/main/java/com/itsaky/androidide/preferences/SimpleClickablePreference.kt b/preferences/src/main/java/com/itsaky/androidide/preferences/SimpleClickablePreference.kt index 4cf78bcf54..a0f84754e3 100644 --- a/preferences/src/main/java/com/itsaky/androidide/preferences/SimpleClickablePreference.kt +++ b/preferences/src/main/java/com/itsaky/androidide/preferences/SimpleClickablePreference.kt @@ -28,17 +28,15 @@ import kotlinx.parcelize.Parcelize */ @Parcelize class SimpleClickablePreference -@JvmOverloads -constructor( - override val key: String, - override val title: Int, - override val summary: Int? = null, - override val icon: Int? = null, - @IgnoredOnParcel // The prefs do not refresh so the parcelizable object is not required, remove this and update the prefs building - private val onClick: ((Preference) -> Boolean)? = { false } -) : SimplePreference() { - - override fun onPreferenceClick(preference: Preference): Boolean { - return onClick?.let { it(preference) } ?: false - } -} + @JvmOverloads + constructor( + override val key: String, + override val title: Int, + override val summary: Int? = null, + override val icon: Int? = null, + override val tooltipTag: String = "", + @IgnoredOnParcel // The prefs do not refresh so the parcelizable object is not required, remove this and update the prefs building + private val onClick: ((Preference) -> Boolean)? = { false }, + ) : SimplePreference() { + override fun onPreferenceClick(preference: Preference): Boolean = onClick?.let { it(preference) } ?: false + }