|
| 1 | +package com.itsaky.androidide.actions |
| 2 | + |
| 3 | +import android.content.Context |
| 4 | +import android.util.Log |
| 5 | +import android.view.MenuItem |
| 6 | +import androidx.core.content.ContextCompat |
| 7 | +import com.itsaky.androidide.R |
| 8 | +import com.itsaky.androidide.plugins.extensions.ShowAsAction |
| 9 | +import com.itsaky.androidide.plugins.extensions.ToolbarAction |
| 10 | +import com.itsaky.androidide.plugins.manager.pluginCategory |
| 11 | +import com.itsaky.androidide.plugins.manager.pluginTooltipTag |
| 12 | +import com.itsaky.androidide.plugins.manager.ui.PluginDrawableResolver |
| 13 | + |
| 14 | +/** |
| 15 | + * Adapts a plugin-contributed [ToolbarAction] (from `UIExtension.getToolbarActions()`) |
| 16 | + * into an editor-toolbar [ActionItem]. |
| 17 | + * |
| 18 | + * Unlike [PluginActionItem] (which wraps `getMainMenuItems()`), this is the dedicated |
| 19 | + * path for toolbar icons: it carries the action's own [ToolbarAction.order] so a plugin |
| 20 | + * can position its icon among the built-in actions, and it opts into [honorVisibility] |
| 21 | + * so the toolbar fully removes it (instead of greying it) when not applicable. |
| 22 | + */ |
| 23 | +class PluginToolbarActionItem( |
| 24 | + context: Context, |
| 25 | + private val toolbarAction: ToolbarAction, |
| 26 | + val pluginId: String |
| 27 | +) : EditorActivityAction() { |
| 28 | + |
| 29 | + override val id: String = "plugin.toolbar.${toolbarAction.id}" |
| 30 | + |
| 31 | + override val order: Int = toolbarAction.order |
| 32 | + |
| 33 | + override val honorVisibility: Boolean get() = true |
| 34 | + |
| 35 | + init { |
| 36 | + label = toolbarAction.title |
| 37 | + val iconResId = toolbarAction.icon |
| 38 | + icon = if (iconResId != null) { |
| 39 | + PluginDrawableResolver.resolve(iconResId, pluginId, context) |
| 40 | + ?: ContextCompat.getDrawable(context, R.drawable.ic_package) |
| 41 | + } else { |
| 42 | + ContextCompat.getDrawable(context, R.drawable.ic_package) |
| 43 | + } |
| 44 | + location = ActionItem.Location.EDITOR_TOOLBAR |
| 45 | + requiresUIThread = true |
| 46 | + } |
| 47 | + |
| 48 | + override fun prepare(data: ActionData) { |
| 49 | + super.prepare(data) |
| 50 | + if (!visible) { |
| 51 | + // EditorActivityAction.prepare() hides the action when the editor context is |
| 52 | + // missing; respect that and skip the plugin providers. |
| 53 | + return |
| 54 | + } |
| 55 | + runCatching { |
| 56 | + enabled = toolbarAction.isEnabledProvider?.invoke() ?: toolbarAction.isEnabled |
| 57 | + visible = toolbarAction.isVisibleProvider?.invoke() ?: toolbarAction.isVisible |
| 58 | + }.onFailure { e -> |
| 59 | + // A throwing/disposed plugin must never keep a stale icon on the toolbar. |
| 60 | + Log.w("PluginToolbarActionItem", "prepare failed for '${toolbarAction.id}'", e) |
| 61 | + enabled = false |
| 62 | + visible = false |
| 63 | + } |
| 64 | + } |
| 65 | + |
| 66 | + override fun getShowAsActionFlags(data: ActionData): Int = when (toolbarAction.showAsAction) { |
| 67 | + ShowAsAction.ALWAYS -> MenuItem.SHOW_AS_ACTION_ALWAYS |
| 68 | + ShowAsAction.IF_ROOM -> MenuItem.SHOW_AS_ACTION_IF_ROOM |
| 69 | + ShowAsAction.NEVER -> MenuItem.SHOW_AS_ACTION_NEVER |
| 70 | + ShowAsAction.WITH_TEXT -> MenuItem.SHOW_AS_ACTION_WITH_TEXT |
| 71 | + ShowAsAction.COLLAPSE_ACTION_VIEW -> MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW |
| 72 | + } |
| 73 | + |
| 74 | + override fun retrieveTooltipTag(isReadOnlyContext: Boolean): String = |
| 75 | + pluginTooltipTag(pluginId, toolbarAction.id) |
| 76 | + |
| 77 | + override fun retrieveTooltipCategory(): String = pluginCategory(pluginId) |
| 78 | + |
| 79 | + override suspend fun execAction(data: ActionData): Any { |
| 80 | + return try { |
| 81 | + toolbarAction.action.invoke() |
| 82 | + true |
| 83 | + } catch (e: Exception) { |
| 84 | + Log.e("PluginToolbarActionItem", "Error executing toolbar action '${toolbarAction.id}'", e) |
| 85 | + false |
| 86 | + } |
| 87 | + } |
| 88 | +} |
0 commit comments