You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
435 lines
22 KiB
Kotlin
435 lines
22 KiB
Kotlin
/*
|
|
* Copyright (c) 2023 to present Androidacy and contributors. Names, logos, icons, and the Androidacy name are all trademarks of Androidacy and may not be used without license. See LICENSE for more information.
|
|
*/
|
|
|
|
@file:Suppress("ktConcatNullable")
|
|
|
|
package com.fox2code.mmm.module
|
|
|
|
import android.annotation.SuppressLint
|
|
import android.content.DialogInterface
|
|
import android.graphics.Color
|
|
import android.graphics.Typeface
|
|
import android.graphics.drawable.Drawable
|
|
import android.util.TypedValue
|
|
import android.view.LayoutInflater
|
|
import android.view.View
|
|
import android.view.View.OnLongClickListener
|
|
import android.view.ViewGroup
|
|
import android.widget.CompoundButton
|
|
import android.widget.HorizontalScrollView
|
|
import android.widget.ImageButton
|
|
import android.widget.TextView
|
|
import android.widget.Toast
|
|
import androidx.annotation.ColorInt
|
|
import androidx.annotation.StringRes
|
|
import androidx.cardview.widget.CardView
|
|
import androidx.core.graphics.ColorUtils
|
|
import androidx.recyclerview.widget.RecyclerView
|
|
import com.fox2code.mmm.BuildConfig
|
|
import com.fox2code.mmm.MainApplication
|
|
import com.fox2code.mmm.R
|
|
import com.fox2code.mmm.manager.ModuleInfo
|
|
import com.fox2code.mmm.manager.ModuleManager.Companion.instance
|
|
import com.google.android.material.chip.Chip
|
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
import com.google.android.material.materialswitch.MaterialSwitch
|
|
import com.topjohnwu.superuser.internal.UiThreadHandler
|
|
import timber.log.Timber
|
|
|
|
class ModuleViewAdapter : RecyclerView.Adapter<ModuleViewAdapter.ViewHolder>() {
|
|
val moduleHolders = ArrayList<ModuleHolder>()
|
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
|
val view = LayoutInflater.from(parent.context).inflate(R.layout.module_entry, parent, false)
|
|
return ViewHolder(view)
|
|
}
|
|
|
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
|
val moduleHolder = moduleHolders[position]
|
|
try {
|
|
if (holder.update(moduleHolder)) {
|
|
UiThreadHandler.handler.post {
|
|
if (moduleHolders[position] == moduleHolder) {
|
|
moduleHolders.removeAt(position)
|
|
notifyItemRemoved(position)
|
|
}
|
|
}
|
|
}
|
|
} catch (e: Exception) {
|
|
Timber.e(e, "Error while updating module holder. This may mean we're trying to update too early.")
|
|
}
|
|
}
|
|
|
|
override fun getItemCount(): Int {
|
|
return moduleHolders.size
|
|
}
|
|
|
|
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
|
private val cardView: CardView
|
|
private val invalidPropsChip: Chip
|
|
private val buttonAction: ImageButton
|
|
private val switchMaterial: MaterialSwitch
|
|
private val titleText: TextView
|
|
private val creditText: TextView
|
|
private val descriptionText: TextView
|
|
private val moduleOptionsHolder: HorizontalScrollView
|
|
private val updateText: TextView
|
|
private val actionsButtons: Array<Chip?>
|
|
private val actionButtonsTypes: ArrayList<ActionButtonType?>
|
|
|
|
@Suppress("MemberVisibilityCanBePrivate")
|
|
var moduleHolder: ModuleHolder? = null
|
|
var background: Drawable
|
|
private var initState = true
|
|
|
|
init {
|
|
cardView = itemView.findViewById(R.id.card_view)
|
|
invalidPropsChip = itemView.findViewById(R.id.invalid_module_props)
|
|
buttonAction = itemView.findViewById(R.id.button_action)
|
|
switchMaterial = itemView.findViewById(R.id.switch_action)
|
|
titleText = itemView.findViewById(R.id.title_text)
|
|
creditText = itemView.findViewById(R.id.credit_text)
|
|
descriptionText = itemView.findViewById(R.id.description_text)
|
|
moduleOptionsHolder = itemView.findViewById(R.id.module_options_holder)
|
|
updateText = itemView.findViewById(R.id.updated_text)
|
|
actionsButtons = arrayOfNulls(6)
|
|
actionsButtons[0] = itemView.findViewById(R.id.button_action1)
|
|
actionsButtons[1] = itemView.findViewById(R.id.button_action2)
|
|
actionsButtons[2] = itemView.findViewById(R.id.button_action3)
|
|
actionsButtons[3] = itemView.findViewById(R.id.button_action4)
|
|
actionsButtons[4] = itemView.findViewById(R.id.button_action5)
|
|
actionsButtons[5] = itemView.findViewById(R.id.button_action6)
|
|
background = cardView.background
|
|
// Apply default
|
|
cardView.setOnClickListener { v: View? ->
|
|
val moduleHolder = moduleHolder
|
|
if (moduleHolder != null) {
|
|
var onClickListener = moduleHolder.onClickListener
|
|
if (onClickListener != null) {
|
|
onClickListener.onClick(v)
|
|
} else if (moduleHolder.notificationType != null) {
|
|
onClickListener = moduleHolder.notificationType.onClickListener
|
|
onClickListener?.onClick(v)
|
|
}
|
|
}
|
|
}
|
|
buttonAction.isClickable = false
|
|
switchMaterial.isEnabled = false
|
|
switchMaterial.setOnCheckedChangeListener { _: CompoundButton?, checked: Boolean ->
|
|
if (initState) return@setOnCheckedChangeListener // Skip if non user
|
|
val moduleHolder = moduleHolder
|
|
if (moduleHolder?.moduleInfo != null) {
|
|
val moduleInfo: ModuleInfo? = moduleHolder.moduleInfo
|
|
if (!instance?.setEnabledState(
|
|
moduleInfo!!, checked
|
|
)!!
|
|
) {
|
|
switchMaterial.isChecked =
|
|
(moduleInfo?.flags ?: 0) and ModuleInfo.FLAG_MODULE_DISABLED == 0
|
|
}
|
|
}
|
|
}
|
|
actionButtonsTypes = ArrayList()
|
|
for (i in actionsButtons.indices) {
|
|
actionsButtons[i]?.setOnClickListener(View.OnClickListener setOnClickListener@{ v: View? ->
|
|
if (initState) return@setOnClickListener // Skip if non user
|
|
val moduleHolder = moduleHolder
|
|
if (i < actionButtonsTypes.size && moduleHolder != null) {
|
|
(v as Chip?)?.let { actionButtonsTypes[i]!!.doAction(it, moduleHolder) }
|
|
if (moduleHolder.shouldRemove()) {
|
|
cardView.visibility = View.GONE
|
|
}
|
|
}
|
|
})
|
|
actionsButtons[i]?.setOnLongClickListener(OnLongClickListener setOnLongClickListener@{ v: View? ->
|
|
if (initState) return@setOnLongClickListener false // Skip if non user
|
|
val moduleHolder = moduleHolder
|
|
var didSomething = false
|
|
if (i < actionButtonsTypes.size && moduleHolder != null) {
|
|
didSomething = (v as Chip?)?.let {
|
|
actionButtonsTypes[i]!!
|
|
.doActionLong(it, moduleHolder)
|
|
} == true
|
|
if (moduleHolder.shouldRemove()) {
|
|
cardView.visibility = View.GONE
|
|
}
|
|
}
|
|
didSomething
|
|
})
|
|
}
|
|
initState = false
|
|
}
|
|
|
|
fun getString(@StringRes resId: Int): String {
|
|
return itemView.context.getString(resId)
|
|
}
|
|
|
|
@SuppressLint("SetTextI18n")
|
|
fun update(moduleHolder: ModuleHolder): Boolean {
|
|
initState = true
|
|
if (moduleHolder.isModuleHolder && moduleHolder.shouldRemove()) {
|
|
cardView.visibility = View.GONE
|
|
this.moduleHolder = null
|
|
initState = false
|
|
return true
|
|
}
|
|
val type = moduleHolder.type
|
|
val vType = moduleHolder.getCompareType(type)
|
|
cardView.visibility = View.VISIBLE
|
|
val showCaseMode = MainApplication.isShowcaseMode
|
|
if (moduleHolder.isModuleHolder) {
|
|
buttonAction.visibility = View.GONE
|
|
buttonAction.background = null
|
|
val localModuleInfo = moduleHolder.moduleInfo
|
|
if (localModuleInfo != null) {
|
|
localModuleInfo.verify()
|
|
switchMaterial.visibility = View.VISIBLE
|
|
switchMaterial.isChecked =
|
|
localModuleInfo.flags and ModuleInfo.FLAG_MODULE_DISABLED == 0
|
|
} else {
|
|
switchMaterial.visibility = View.GONE
|
|
}
|
|
creditText.visibility = View.VISIBLE
|
|
moduleOptionsHolder.visibility = View.VISIBLE
|
|
descriptionText.visibility = View.VISIBLE
|
|
val moduleInfo = moduleHolder.mainModuleInfo
|
|
moduleInfo.verify()
|
|
moduleInfo.name.also { titleText.text = it }
|
|
if (localModuleInfo == null || moduleInfo.versionCode > localModuleInfo.updateVersionCode) {
|
|
@Suppress("ktConcatNullable")
|
|
creditText.text =
|
|
(if ((localModuleInfo == null) || (moduleInfo.version == localModuleInfo.version)) moduleInfo.version else localModuleInfo.version + " (" + getString(
|
|
R.string.module_last_update
|
|
) + " " + moduleInfo.version + ")") + " " + getString(
|
|
R.string.module_by
|
|
) + " " + moduleInfo.author
|
|
} else {
|
|
val updateVersionOurs: String?
|
|
@Suppress("ktConcatNullable")
|
|
updateVersionOurs =
|
|
if (localModuleInfo.updateVersion != null) localModuleInfo.updateVersion + " (" + localModuleInfo.updateVersionCode + ")" else localModuleInfo.version + " (" + localModuleInfo.versionCode + ")"
|
|
creditText.text = updateVersionOurs
|
|
}
|
|
// add an onclick listener to the credit text to show the versionCode
|
|
creditText.setOnClickListener { _: View? ->
|
|
// if both local and remote moduleInfo are available, show the versionCode of both
|
|
if (localModuleInfo != null) {
|
|
// if moduleInfo and localModuleInfo have the same versionCode, only show one, otherwise show both
|
|
if (localModuleInfo.versionCode == moduleInfo.versionCode) {
|
|
Toast.makeText(
|
|
itemView.context,
|
|
getString(R.string.module_version) + " " + localModuleInfo.version + " (" + localModuleInfo.versionCode + ")",
|
|
Toast.LENGTH_LONG
|
|
).show()
|
|
} else {
|
|
// format is Version: version (versionCode) | Remote Version: version (versionCode)
|
|
Toast.makeText(
|
|
itemView.context,
|
|
getString(R.string.module_version) + " " + localModuleInfo.version + " (" + localModuleInfo.versionCode + ") | " + getString(
|
|
R.string.module_remote_version
|
|
) + " " + moduleInfo.version + " (" + moduleInfo.versionCode + ")",
|
|
Toast.LENGTH_LONG
|
|
).show()
|
|
}
|
|
} else {
|
|
Toast.makeText(
|
|
itemView.context,
|
|
getString(R.string.module_remote_version) + " " + moduleInfo.version + " (" + moduleInfo.versionCode + ")",
|
|
Toast.LENGTH_LONG
|
|
).show()
|
|
}
|
|
}
|
|
if (moduleInfo.description == null || moduleInfo.description!!.isEmpty()) {
|
|
descriptionText.setText(R.string.no_desc_found)
|
|
} else {
|
|
descriptionText.text = moduleInfo.description
|
|
}
|
|
val updateText = moduleHolder.updateTimeText
|
|
if (updateText.isNotEmpty()) {
|
|
val repoModule = moduleHolder.repoModule
|
|
this.updateText.visibility = View.VISIBLE
|
|
this.updateText.text = """${getString(R.string.module_last_update)} $updateText
|
|
${getString(R.string.module_repo)} ${moduleHolder.repoName}""" + if ((repoModule?.qualityText
|
|
?: 0) == 0
|
|
) "" else "\n" + getString(
|
|
repoModule!!.qualityText
|
|
) + " " + repoModule.qualityValue
|
|
} else if (moduleHolder.moduleId == "hosts") {
|
|
this.updateText.visibility = View.VISIBLE
|
|
this.updateText.setText(R.string.magisk_builtin_module)
|
|
} else if (moduleHolder.moduleId.contains("substratum")) {
|
|
this.updateText.visibility = View.VISIBLE
|
|
this.updateText.setText(R.string.substratum_builtin_module)
|
|
} else {
|
|
this.updateText.visibility = View.GONE
|
|
}
|
|
actionButtonsTypes.clear()
|
|
moduleHolder.getButtons(itemView.context, actionButtonsTypes, showCaseMode)
|
|
switchMaterial.isEnabled =
|
|
!showCaseMode && !moduleHolder.hasFlag(ModuleInfo.FLAG_MODULE_UPDATING)
|
|
for (i in actionsButtons.indices) {
|
|
val imageButton = actionsButtons[i]
|
|
if (i < actionButtonsTypes.size) {
|
|
imageButton!!.visibility = View.VISIBLE
|
|
imageButton.importantForAccessibility =
|
|
View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
|
|
val button = actionButtonsTypes[i]
|
|
button!!.update(imageButton, moduleHolder)
|
|
imageButton.contentDescription = button.name
|
|
} else {
|
|
imageButton!!.visibility = View.GONE
|
|
imageButton.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
|
|
imageButton.contentDescription = null
|
|
}
|
|
}
|
|
if (actionButtonsTypes.isEmpty()) {
|
|
moduleOptionsHolder.visibility = View.GONE
|
|
}
|
|
cardView.isClickable = false
|
|
if (moduleHolder.isModuleHolder && moduleHolder.hasFlag(ModuleInfo.FLAG_MODULE_ACTIVE)) {
|
|
titleText.typeface = Typeface.DEFAULT_BOLD
|
|
} else {
|
|
titleText.typeface = Typeface.DEFAULT
|
|
}
|
|
} else {
|
|
if (type === ModuleHolder.Type.SEPARATOR && moduleHolder.filterLevel != 0) {
|
|
buttonAction.visibility = View.VISIBLE
|
|
buttonAction.setImageResource(moduleHolder.filterLevel)
|
|
buttonAction.setBackgroundResource(R.drawable.bg_baseline_circle_24)
|
|
} else {
|
|
buttonAction.visibility =
|
|
if (type === ModuleHolder.Type.NOTIFICATION) View.VISIBLE else View.GONE
|
|
buttonAction.background = null
|
|
}
|
|
switchMaterial.visibility = View.GONE
|
|
creditText.visibility = View.GONE
|
|
moduleOptionsHolder.visibility = View.GONE
|
|
descriptionText.visibility = View.GONE
|
|
updateText.visibility = View.GONE
|
|
titleText.text = " "
|
|
creditText.text = " "
|
|
descriptionText.text = " "
|
|
switchMaterial.isEnabled = false
|
|
actionButtonsTypes.clear()
|
|
for (button in actionsButtons) {
|
|
button!!.visibility = View.GONE
|
|
button.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
|
|
button.contentDescription = null
|
|
}
|
|
if (type === ModuleHolder.Type.NOTIFICATION) {
|
|
val notificationType = moduleHolder.notificationType
|
|
if (notificationType?.textId != null) {
|
|
titleText.setText(notificationType.textId)
|
|
}
|
|
// set title text appearance
|
|
titleText.setTextAppearance(com.google.android.material.R.style.TextAppearance_Material3_BodyLarge)
|
|
if (notificationType != null) {
|
|
buttonAction.setImageResource(notificationType.iconId)
|
|
}
|
|
if (notificationType != null) {
|
|
cardView.isClickable =
|
|
notificationType.onClickListener != null || moduleHolder.onClickListener != null
|
|
}
|
|
if (notificationType != null) {
|
|
titleText.typeface =
|
|
if (notificationType.special) Typeface.DEFAULT_BOLD else Typeface.DEFAULT
|
|
}
|
|
} else {
|
|
cardView.isClickable = moduleHolder.onClickListener != null
|
|
titleText.typeface = Typeface.DEFAULT
|
|
titleText.setTextAppearance(com.google.android.material.R.style.TextAppearance_Material3_BodyMedium)
|
|
titleText.setTextSize(TypedValue.COMPLEX_UNIT_SP, 19f)
|
|
}
|
|
}
|
|
if (type === ModuleHolder.Type.SEPARATOR) {
|
|
if (moduleHolder.separator != null) {
|
|
titleText.text = getString(moduleHolder.separator.title)
|
|
}
|
|
}
|
|
if (DEBUG) {
|
|
if (vType != null) {
|
|
titleText.text =
|
|
titleText.text.toString() + " " + formatType(type) + " " + formatType(vType)
|
|
}
|
|
}
|
|
// Coloration system
|
|
val drawable = cardView.background
|
|
if (drawable != null) background = drawable
|
|
invalidPropsChip.visibility = View.GONE
|
|
if (type.hasBackground) {
|
|
if (drawable == null) {
|
|
cardView.background = background
|
|
}
|
|
var backgroundAttr = androidx.appcompat.R.attr.colorBackgroundFloating
|
|
var foregroundAttr = com.google.android.material.R.attr.colorOnBackground
|
|
if (type === ModuleHolder.Type.NOTIFICATION) {
|
|
if (moduleHolder.notificationType != null) {
|
|
foregroundAttr = moduleHolder.notificationType.foregroundAttr
|
|
}
|
|
if (moduleHolder.notificationType != null) {
|
|
backgroundAttr = moduleHolder.notificationType.backgroundAttr
|
|
}
|
|
} else if (type === ModuleHolder.Type.INSTALLED && moduleHolder.hasFlag(ModuleInfo.FLAG_METADATA_INVALID)) {
|
|
invalidPropsChip.setOnClickListener { view: View ->
|
|
val builder = MaterialAlertDialogBuilder(view.context)
|
|
builder.setTitle(R.string.low_quality_module)
|
|
.setMessage(R.string.low_quality_module_desc).setCancelable(true)
|
|
.setPositiveButton(
|
|
R.string.ok
|
|
) { x: DialogInterface, _: Int -> x.dismiss() }
|
|
.show()
|
|
}
|
|
// Backup restore
|
|
// foregroundAttr = R.attr.colorOnError;
|
|
// backgroundAttr = R.attr.colorError;
|
|
}
|
|
val theme = cardView.context.theme
|
|
val value = TypedValue()
|
|
theme.resolveAttribute(backgroundAttr, value, true)
|
|
@ColorInt var bgColor = value.data
|
|
theme.resolveAttribute(foregroundAttr, value, true)
|
|
@ColorInt val fgColor = value.data
|
|
// Fix card background being invisible on light theme
|
|
if (bgColor == Color.WHITE) {
|
|
bgColor = -0x70708
|
|
}
|
|
// if force_transparency is true or theme is transparent_light, set diff bgColor
|
|
// get string value of Theme
|
|
val themeName = theme.toString()
|
|
if (theme.resources.getBoolean(R.bool.force_transparency) || themeName.contains("transparent")) {
|
|
if (BuildConfig.DEBUG) Timber.d("Theme is transparent, fixing bgColor")
|
|
bgColor = ColorUtils.setAlphaComponent(bgColor, 0x70)
|
|
}
|
|
titleText.setTextColor(fgColor)
|
|
buttonAction.setColorFilter(fgColor)
|
|
cardView.setCardBackgroundColor(bgColor)
|
|
} else {
|
|
val theme = titleText.context.theme
|
|
val value = TypedValue()
|
|
theme.resolveAttribute(
|
|
com.google.android.material.R.attr.colorOnBackground,
|
|
value,
|
|
true
|
|
)
|
|
buttonAction.setColorFilter(value.data)
|
|
titleText.setTextColor(value.data)
|
|
cardView.background = null
|
|
}
|
|
if (type === ModuleHolder.Type.FOOTER) {
|
|
titleText.minHeight = moduleHolder.footerPx
|
|
} else {
|
|
titleText.minHeight = 0
|
|
}
|
|
this.moduleHolder = moduleHolder
|
|
initState = false
|
|
return false
|
|
}
|
|
}
|
|
|
|
companion object {
|
|
private const val DEBUG = false
|
|
private fun formatType(type: ModuleHolder.Type): String {
|
|
return type.name.substring(0, 3) + "_" + type.ordinal
|
|
}
|
|
}
|
|
} |