Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
278 changes: 148 additions & 130 deletions app/src/main/java/com/itsaky/androidide/adapters/PluginListAdapter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@
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
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
Expand All @@ -18,137 +21,152 @@ 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<PluginInfo, PluginListAdapter.PluginViewHolder>(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(
@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(
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 =
itemView.context.getString(R.string.plugin_author_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)
Comment thread
Daniel-ADFA marked this conversation as resolved.
} else {
Glide.with(pluginIcon).clear(pluginIcon)
pluginIcon.setImageResource(R.drawable.ic_extension)
}

val statusText =
when {
!plugin.isLoaded -> R.string.plugin_status_not_loaded
!plugin.isEnabled -> R.string.plugin_status_disabled
else -> R.string.plugin_status_enabled
}
pluginStatus.setText(statusText)

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)
val actions = menuActionsFor(plugin)

actions.forEachIndexed { index, action ->
popup.menu.add(Menu.NONE, index, index, action.labelRes)
}

popup.setOnMenuItemClickListener { menuItem ->
onActionClick(plugin, actions[menuItem.itemId])
true
}

popup.show()
}

private fun menuActionsFor(plugin: PluginInfo): List<Action> =
buildList {
if (plugin.isLoaded) {
add(if (plugin.isEnabled) Action.DISABLE else Action.ENABLE)
add(Action.UNINSTALL)
}
add(Action.DETAILS)
}
}
}

class PluginDiffCallback : DiffUtil.ItemCallback<PluginInfo>() {
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
}
}
override fun areItemsTheSame(
oldItem: PluginInfo,
newItem: PluginInfo,
): Boolean = oldItem.metadata.id == newItem.metadata.id

override fun areContentsTheSame(
oldItem: PluginInfo,
newItem: PluginInfo,
): Boolean = oldItem == newItem
Comment thread
Daniel-ADFA marked this conversation as resolved.
}
5 changes: 5 additions & 0 deletions resources/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -912,7 +912,12 @@
<string name="enable_plugin">Enable</string>
<string name="disable_plugin">Disable</string>
<string name="uninstall_plugin">Uninstall</string>
<string name="plugin_action_details">Details</string>
<string name="plugin_details">Plugin Details</string>
<string name="plugin_author_by">by %1$s</string>
<string name="plugin_status_enabled">Enabled</string>
<string name="plugin_status_disabled">Disabled</string>
<string name="plugin_status_not_loaded">Not Loaded</string>
<string name="plugin_permissions">Permissions</string>
<string name="plugin_dependencies">Dependencies</string>

Expand Down
Loading