diff --git a/app/src/main/java/com/itsaky/androidide/adapters/EditorBottomSheetTabAdapter.kt b/app/src/main/java/com/itsaky/androidide/adapters/EditorBottomSheetTabAdapter.kt index fbb6cc6af3..63551f5659 100644 --- a/app/src/main/java/com/itsaky/androidide/adapters/EditorBottomSheetTabAdapter.kt +++ b/app/src/main/java/com/itsaky/androidide/adapters/EditorBottomSheetTabAdapter.kt @@ -29,10 +29,13 @@ import com.itsaky.androidide.fragments.git.GitBottomSheetFragment import com.itsaky.androidide.fragments.output.AppLogFragment import com.itsaky.androidide.fragments.output.BuildOutputFragment import com.itsaky.androidide.fragments.output.IDELogFragment +import com.itsaky.androidide.idetooltips.TooltipCategory import com.itsaky.androidide.idetooltips.TooltipTag import com.itsaky.androidide.plugins.extensions.TabItem import com.itsaky.androidide.plugins.extensions.UIExtension import com.itsaky.androidide.plugins.manager.fragment.PluginFragmentFactory +import com.itsaky.androidide.plugins.manager.pluginCategory +import com.itsaky.androidide.plugins.manager.pluginTooltipTag import com.itsaky.androidide.resources.R import com.itsaky.androidide.utils.FeatureFlags import org.slf4j.LoggerFactory @@ -137,6 +140,7 @@ class EditorBottomSheetTabAdapter( private val tabs = MutableList(allTabs.size) { allTabs[it] } private val pluginFragmentFactories = mutableMapOf Fragment>() private val pluginExtensions = mutableMapOf() + private val pluginIdsByTabItemId = mutableMapOf() init { addPluginTabs() @@ -148,6 +152,7 @@ class EditorBottomSheetTabAdapter( tabs.clear() pluginFragmentFactories.clear() pluginExtensions.clear() + pluginIdsByTabItemId.clear() notifyDataSetChanged() } @@ -338,7 +343,24 @@ class EditorBottomSheetTabAdapter( ) : this(title, fragmentClass, itemId.toLong(), tooltipTag) } - fun getTooltipTag(position: Int): String? = allTabs[position].tooltipTag + // Position-keyed lookups must index `tabs` (the visible list, what the + // ViewPager iterates), not `allTabs` (the full master list). + // `removeFragment` / `restoreFragment` can diverge the two, and the + // long-press handler in EditorBottomSheet passes the ViewPager position. + fun getTooltipTag(position: Int): String? = tabs[position].tooltipTag + + /** + * Tooltip category for the tab at [position]. Built-in tabs live in + * [TooltipCategory.CATEGORY_IDE]; plugin-contributed tabs live in their + * own per-plugin category (`plugin_`) so each plugin can + * publish its own tooltip entries via [DocumentationExtension] without + * colliding with other plugins or the IDE's own tags. + */ + fun getTooltipCategory(position: Int): String { + val itemId = tabs[position].itemId + val pluginId = pluginIdsByTabItemId[itemId] + return if (pluginId != null) pluginCategory(pluginId) else TooltipCategory.CATEGORY_IDE + } private data class PluginTabData(val tabItem: TabItem, val plugin: UIExtension) @@ -390,17 +412,21 @@ class EditorBottomSheetTabAdapter( // Add plugin tabs to the adapter at the end val startIndex = allTabs.size for ((index, data) in pluginTabs.withIndex()) { + val pluginId = pluginManager.getPluginIdForInstance(data.plugin) val tab = Tab( title = data.tabItem.title, fragmentClass = Fragment::class.java, // Placeholder, actual fragment from factory itemId = startIndex + index + 1000L, // Offset to avoid conflicts - tooltipTag = TooltipTag.PROJECT_PLUGIN_TAB, + tooltipTag = data.tabItem.tooltipTag + ?: pluginId?.let { pluginTooltipTag(it, data.tabItem.id) } + ?: TooltipTag.PROJECT_PLUGIN_TAB, ) // Store the fragment factory and the extension for later use pluginFragmentFactories[tab.itemId] = data.tabItem.fragmentFactory pluginExtensions[tab.itemId] = data.plugin + if (pluginId != null) pluginIdsByTabItemId[tab.itemId] = pluginId allTabs.add(tab) tabs.add(tab) diff --git a/app/src/main/java/com/itsaky/androidide/ui/EditorBottomSheet.kt b/app/src/main/java/com/itsaky/androidide/ui/EditorBottomSheet.kt index 5f8ba5e3a1..bfebbfbbab 100644 --- a/app/src/main/java/com/itsaky/androidide/ui/EditorBottomSheet.kt +++ b/app/src/main/java/com/itsaky/androidide/ui/EditorBottomSheet.kt @@ -159,9 +159,10 @@ constructor( tab.view.setOnLongClickListener { view -> val tooltipTag = pagerAdapter.getTooltipTag(position) ?: return@setOnLongClickListener true - TooltipManager.showIdeCategoryTooltip( + TooltipManager.showTooltip( context = context, anchorView = view, + category = pagerAdapter.getTooltipCategory(position), tag = tooltipTag, ) true diff --git a/plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/extensions/UIExtension.kt b/plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/extensions/UIExtension.kt index 00f73ee74f..d846af186f 100644 --- a/plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/extensions/UIExtension.kt +++ b/plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/extensions/UIExtension.kt @@ -81,7 +81,17 @@ data class TabItem( val fragmentFactory: () -> Fragment, val isEnabled: Boolean = true, val isVisible: Boolean = true, - val order: Int = 0 + val order: Int = 0, + /** + * Optional tooltip tag to look up under the plugin's tooltip category + * (`plugin_`). When null, the IDE composes a tag using the + * convention `.` so plugins do not need to manually + * namespace tags to avoid collisions across plugins. If the plugin's + * id can't be resolved at registration time, the IDE falls back to a + * generic placeholder tag under the IDE tooltip category. Mirrors the + * tooltipTag fallback already on NavigationItem and MenuItem. + */ + val tooltipTag: String? = null ) data class NavigationItem(