From f6f031b031019e518e6390ee6cf5cea6cb24e959 Mon Sep 17 00:00:00 2001 From: Daniel Alome Date: Wed, 5 Aug 2026 23:04:26 +0100 Subject: [PATCH 1/2] ADFA-4446: Invalidate Glide's cache when a plugin icon file changes --- .../androidide/adapters/PluginListAdapter.kt | 277 ++++++++++-------- 1 file changed, 147 insertions(+), 130 deletions(-) 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 e9d7b2c652..9ab245b9e2 100644 --- a/app/src/main/java/com/itsaky/androidide/adapters/PluginListAdapter.kt +++ b/app/src/main/java/com/itsaky/androidide/adapters/PluginListAdapter.kt @@ -9,6 +9,7 @@ import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView 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 @@ -18,137 +19,153 @@ import com.itsaky.androidide.utils.isSystemInDarkMode import java.io.File class PluginListAdapter( - private val onActionClick: (PluginInfo, Action) -> Unit + private val onActionClick: (PluginInfo, Action) -> Unit, ) : ListAdapter(PluginDiffCallback()) { - - enum class Action { - ENABLE, - DISABLE, - UNINSTALL, - DETAILS - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PluginViewHolder { - val binding = ItemPluginBinding.inflate( - LayoutInflater.from(parent.context), - parent, - false - ) - return PluginViewHolder(binding) - } - - override fun onBindViewHolder(holder: PluginViewHolder, position: Int) { - holder.bind(getItem(position)) - } - - inner class PluginViewHolder( - private val binding: ItemPluginBinding - ) : RecyclerView.ViewHolder(binding.root) { - - fun bind(plugin: PluginInfo) { - binding.apply { - pluginName.text = plugin.metadata.name - pluginDescription.text = plugin.metadata.description - val version = plugin.metadata.version - val segments = version.split('.') - pluginVersion.text = if (segments.size > 3) { - "v${segments.take(3).joinToString(".")}..." - } else { - "v$version" - } - pluginAuthor.text = "by ${plugin.metadata.author}" - - val iconPath = if (itemView.context.isSystemInDarkMode()) { - plugin.metadata.iconNightPath - } else { - plugin.metadata.iconDayPath - } - - pluginIcon.background = null - pluginIcon.imageTintList = null - val iconFile = iconPath?.let(::File)?.takeIf { it.exists() } - if (iconFile != null) { - Glide.with(pluginIcon) - .load(iconFile) - .placeholder(R.drawable.ic_extension) - .error(R.drawable.ic_extension) - .into(pluginIcon) - } else { - Glide.with(pluginIcon).clear(pluginIcon) - pluginIcon.setImageResource(R.drawable.ic_extension) - } - - // Set status - val statusText = when { - !plugin.isLoaded -> "Not Loaded" - !plugin.isEnabled -> "Disabled" - else -> "Enabled" - } - pluginStatus.text = statusText - - // Set status color - val statusColor = when { - !plugin.isLoaded -> R.color.error - !plugin.isEnabled -> R.color.warning - else -> R.color.success - } - pluginStatus.setTextColor( - itemView.context.getColor(statusColor) - ) - - // Setup menu button - btnMenu.setOnClickListener { view -> - showPopupMenu(view, plugin) - } - - // 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 - } - } - } - - private fun showPopupMenu(view: View, plugin: PluginInfo) { - val popup = PopupMenu(view.context, view) - - // Add menu items based on plugin state - if (plugin.isLoaded) { - if (plugin.isEnabled) { - popup.menu.add(0, 1, 0, "Disable") - } else { - popup.menu.add(0, 2, 0, "Enable") - } - popup.menu.add(0, 3, 0, "Uninstall") - } - popup.menu.add(0, 4, 0, "Details") - - popup.setOnMenuItemClickListener { menuItem -> - when (menuItem.itemId) { - 1 -> onActionClick(plugin, Action.DISABLE) - 2 -> onActionClick(plugin, Action.ENABLE) - 3 -> onActionClick(plugin, Action.UNINSTALL) - 4 -> onActionClick(plugin, Action.DETAILS) - } - true - } - - popup.show() - } - } + enum class Action { + ENABLE, + DISABLE, + UNINSTALL, + DETAILS, + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int, + ): PluginViewHolder { + val binding = + ItemPluginBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false, + ) + return PluginViewHolder(binding) + } + + override fun onBindViewHolder( + holder: PluginViewHolder, + position: Int, + ) { + holder.bind(getItem(position)) + } + + inner class PluginViewHolder( + private val binding: ItemPluginBinding, + ) : RecyclerView.ViewHolder(binding.root) { + fun bind(plugin: PluginInfo) { + binding.apply { + pluginName.text = plugin.metadata.name + pluginDescription.text = plugin.metadata.description + val version = plugin.metadata.version + val segments = version.split('.') + pluginVersion.text = + if (segments.size > 3) { + "v${segments.take(3).joinToString(".")}..." + } else { + "v$version" + } + pluginAuthor.text = "by ${plugin.metadata.author}" + + val iconPath = + if (itemView.context.isSystemInDarkMode()) { + plugin.metadata.iconNightPath + } else { + plugin.metadata.iconDayPath + } + + pluginIcon.background = null + pluginIcon.imageTintList = null + val iconFile = iconPath?.let(::File)?.takeIf { it.exists() } + if (iconFile != null) { + Glide + .with(pluginIcon) + .load(iconFile) + .signature(ObjectKey(iconFile.lastModified())) + .placeholder(R.drawable.ic_extension) + .error(R.drawable.ic_extension) + .into(pluginIcon) + } else { + Glide.with(pluginIcon).clear(pluginIcon) + pluginIcon.setImageResource(R.drawable.ic_extension) + } + + // Set status + val statusText = + when { + !plugin.isLoaded -> "Not Loaded" + !plugin.isEnabled -> "Disabled" + else -> "Enabled" + } + pluginStatus.text = statusText + + // Set status color + val statusColor = + when { + !plugin.isLoaded -> R.color.error + !plugin.isEnabled -> R.color.warning + else -> R.color.success + } + pluginStatus.setTextColor( + itemView.context.getColor(statusColor), + ) + + // Setup menu button + btnMenu.setOnClickListener { view -> + showPopupMenu(view, plugin) + } + + // 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 + } + } + } + + private fun showPopupMenu( + view: View, + plugin: PluginInfo, + ) { + val popup = PopupMenu(view.context, view) + + // Add menu items based on plugin state + if (plugin.isLoaded) { + if (plugin.isEnabled) { + popup.menu.add(0, 1, 0, "Disable") + } else { + popup.menu.add(0, 2, 0, "Enable") + } + popup.menu.add(0, 3, 0, "Uninstall") + } + popup.menu.add(0, 4, 0, "Details") + + popup.setOnMenuItemClickListener { menuItem -> + when (menuItem.itemId) { + 1 -> onActionClick(plugin, Action.DISABLE) + 2 -> onActionClick(plugin, Action.ENABLE) + 3 -> onActionClick(plugin, Action.UNINSTALL) + 4 -> onActionClick(plugin, Action.DETAILS) + } + true + } + + popup.show() + } + } } class PluginDiffCallback : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: PluginInfo, newItem: PluginInfo): Boolean { - return oldItem.metadata.id == newItem.metadata.id - } - - override fun areContentsTheSame(oldItem: PluginInfo, newItem: PluginInfo): Boolean { - return oldItem == newItem - } -} \ No newline at end of file + override fun areItemsTheSame( + oldItem: PluginInfo, + newItem: PluginInfo, + ): Boolean = oldItem.metadata.id == newItem.metadata.id + + override fun areContentsTheSame( + oldItem: PluginInfo, + newItem: PluginInfo, + ): Boolean = oldItem == newItem +} From ce30d2f0cd3f3d4ad74fd2f4c595fa1e526c5a95 Mon Sep 17 00:00:00 2001 From: Daniel Alome Date: Thu, 6 Aug 2026 16:45:36 +0100 Subject: [PATCH 2/2] attend to Pr comment --- .../androidide/adapters/PluginListAdapter.kt | 55 ++++++++++--------- resources/src/main/res/values/strings.xml | 5 ++ 2 files changed, 33 insertions(+), 27 deletions(-) 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 9ab245b9e2..a0a39f460b 100644 --- a/app/src/main/java/com/itsaky/androidide/adapters/PluginListAdapter.kt +++ b/app/src/main/java/com/itsaky/androidide/adapters/PluginListAdapter.kt @@ -2,9 +2,11 @@ package com.itsaky.androidide.adapters import android.view.LayoutInflater +import android.view.Menu import android.view.View import android.view.ViewGroup import android.widget.PopupMenu +import androidx.annotation.StringRes import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView @@ -21,11 +23,13 @@ import java.io.File class PluginListAdapter( private val onActionClick: (PluginInfo, Action) -> Unit, ) : ListAdapter(PluginDiffCallback()) { - enum class Action { - ENABLE, - DISABLE, - UNINSTALL, - DETAILS, + enum class Action( + @StringRes val labelRes: Int, + ) { + ENABLE(R.string.enable_plugin), + DISABLE(R.string.disable_plugin), + UNINSTALL(R.string.uninstall_plugin), + DETAILS(R.string.plugin_action_details), } override fun onCreateViewHolder( @@ -63,7 +67,8 @@ class PluginListAdapter( } else { "v$version" } - pluginAuthor.text = "by ${plugin.metadata.author}" + pluginAuthor.text = + itemView.context.getString(R.string.plugin_author_by, plugin.metadata.author) val iconPath = if (itemView.context.isSystemInDarkMode()) { @@ -88,16 +93,14 @@ class PluginListAdapter( pluginIcon.setImageResource(R.drawable.ic_extension) } - // Set status val statusText = when { - !plugin.isLoaded -> "Not Loaded" - !plugin.isEnabled -> "Disabled" - else -> "Enabled" + !plugin.isLoaded -> R.string.plugin_status_not_loaded + !plugin.isEnabled -> R.string.plugin_status_disabled + else -> R.string.plugin_status_enabled } - pluginStatus.text = statusText + pluginStatus.setText(statusText) - // Set status color val statusColor = when { !plugin.isLoaded -> R.color.error @@ -131,30 +134,28 @@ class PluginListAdapter( plugin: PluginInfo, ) { val popup = PopupMenu(view.context, view) + val actions = menuActionsFor(plugin) - // Add menu items based on plugin state - if (plugin.isLoaded) { - if (plugin.isEnabled) { - popup.menu.add(0, 1, 0, "Disable") - } else { - popup.menu.add(0, 2, 0, "Enable") - } - popup.menu.add(0, 3, 0, "Uninstall") + actions.forEachIndexed { index, action -> + popup.menu.add(Menu.NONE, index, index, action.labelRes) } - popup.menu.add(0, 4, 0, "Details") popup.setOnMenuItemClickListener { menuItem -> - when (menuItem.itemId) { - 1 -> onActionClick(plugin, Action.DISABLE) - 2 -> onActionClick(plugin, Action.ENABLE) - 3 -> onActionClick(plugin, Action.UNINSTALL) - 4 -> onActionClick(plugin, Action.DETAILS) - } + onActionClick(plugin, actions[menuItem.itemId]) true } popup.show() } + + private fun menuActionsFor(plugin: PluginInfo): List = + buildList { + if (plugin.isLoaded) { + add(if (plugin.isEnabled) Action.DISABLE else Action.ENABLE) + add(Action.UNINSTALL) + } + add(Action.DETAILS) + } } } diff --git a/resources/src/main/res/values/strings.xml b/resources/src/main/res/values/strings.xml index c56ca9ac40..e0523e5ec9 100644 --- a/resources/src/main/res/values/strings.xml +++ b/resources/src/main/res/values/strings.xml @@ -907,7 +907,12 @@ Enable Disable Uninstall + Details Plugin Details + by %1$s + Enabled + Disabled + Not Loaded Permissions Dependencies