Update MagiskHide list
This commit is contained in:
parent
c7e30ac63e
commit
9df6b0618a
@ -82,6 +82,7 @@ dependencies {
|
||||
implementation(project(":app:signing"))
|
||||
|
||||
implementation("com.github.topjohnwu:jtar:1.0.0")
|
||||
implementation("com.github.topjohnwu:indeterminate-checkbox:1.0.6")
|
||||
implementation("com.jakewharton.timber:timber:4.7.1")
|
||||
|
||||
implementation(kotlin("stdlib"))
|
||||
|
@ -5,14 +5,12 @@ import android.graphics.drawable.Drawable
|
||||
import com.topjohnwu.magisk.ktx.packageInfo
|
||||
import com.topjohnwu.magisk.ktx.processes
|
||||
|
||||
class HideAppInfo(
|
||||
data class HideAppInfo(
|
||||
val info: ApplicationInfo,
|
||||
val name: String,
|
||||
val icon: Drawable
|
||||
) {
|
||||
|
||||
val processes = info.packageInfo?.processes?.distinct() ?: listOf(info.packageName)
|
||||
|
||||
}
|
||||
|
||||
data class StatefulProcess(
|
||||
@ -21,7 +19,7 @@ data class StatefulProcess(
|
||||
val isHidden: Boolean
|
||||
)
|
||||
|
||||
class ProcessHideApp(
|
||||
data class HideAppTarget(
|
||||
val info: HideAppInfo,
|
||||
val processes: List<StatefulProcess>
|
||||
)
|
||||
|
@ -7,60 +7,64 @@ import com.topjohnwu.magisk.BR
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.databinding.ObservableItem
|
||||
import com.topjohnwu.magisk.ktx.startAnimations
|
||||
import com.topjohnwu.magisk.model.entity.ProcessHideApp
|
||||
import com.topjohnwu.magisk.model.entity.HideAppTarget
|
||||
import com.topjohnwu.magisk.model.entity.StatefulProcess
|
||||
import com.topjohnwu.magisk.ui.hide.HideViewModel
|
||||
import com.topjohnwu.magisk.utils.addOnPropertyChangedCallback
|
||||
import com.topjohnwu.magisk.utils.set
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class HideItem(val item: ProcessHideApp) : ObservableItem<HideItem>() {
|
||||
class HideItem(
|
||||
val item: HideAppTarget,
|
||||
viewModel: HideViewModel
|
||||
) : ObservableItem<HideItem>() {
|
||||
|
||||
override val layoutRes = R.layout.item_hide_md2
|
||||
|
||||
val packageName = item.info.info.packageName.orEmpty()
|
||||
val items = item.processes.map { HideProcessItem(it) }
|
||||
val items = item.processes.map { HideProcessItem(it, viewModel) }
|
||||
|
||||
@get:Bindable
|
||||
var isExpanded = false
|
||||
set(value) = set(value, field, { field = it }, BR.expanded)
|
||||
|
||||
@get:Bindable
|
||||
var itemsChecked = 0
|
||||
set(value) = set(value, field, { field = it }, BR.itemsChecked, BR.itemsCheckedPercent)
|
||||
set(value) = set(value, field, { field = it }, BR.itemsCheckedPercent)
|
||||
|
||||
@get:Bindable
|
||||
val itemsCheckedPercent get() = (itemsChecked.toFloat() / items.size * 100).roundToInt()
|
||||
|
||||
private val isHidden get() = itemsChecked == items.size
|
||||
private var state: Boolean? = false
|
||||
set(value) = set(value, field, { field = it }, BR.hiddenState)
|
||||
|
||||
@get:Bindable
|
||||
var hiddenState: Boolean?
|
||||
get() = state
|
||||
set(value) = set(value, state, { state = it }, BR.hiddenState) {
|
||||
if (value == true) {
|
||||
items.filterNot { it.isHidden }
|
||||
} else {
|
||||
items
|
||||
}.forEach { it.toggle() }
|
||||
}
|
||||
|
||||
init {
|
||||
items.forEach { it.addOnPropertyChangedCallback(BR.hidden) { recalculateChecked() } }
|
||||
recalculateChecked()
|
||||
}
|
||||
|
||||
fun collapse(v: View) {
|
||||
(v.parent.parent as? ViewGroup)?.startAnimations()
|
||||
isExpanded = false
|
||||
}
|
||||
|
||||
fun toggle(v: View) {
|
||||
fun toggleExpand(v: View) {
|
||||
(v.parent as? ViewGroup)?.startAnimations()
|
||||
isExpanded = !isExpanded
|
||||
}
|
||||
|
||||
fun toggle(viewModel: HideViewModel): Boolean {
|
||||
// contract implies that isHidden == all checked
|
||||
if (!isHidden) {
|
||||
items.filterNot { it.isHidden }
|
||||
} else {
|
||||
items
|
||||
}.forEach { it.toggle(viewModel) }
|
||||
return true
|
||||
}
|
||||
|
||||
private fun recalculateChecked() {
|
||||
itemsChecked = items.count { it.isHidden }
|
||||
state = when (itemsChecked) {
|
||||
0 -> false
|
||||
items.size -> true
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
override fun contentSameAs(other: HideItem): Boolean = item == other.item
|
||||
@ -68,18 +72,21 @@ class HideItem(val item: ProcessHideApp) : ObservableItem<HideItem>() {
|
||||
|
||||
}
|
||||
|
||||
class HideProcessItem(val item: StatefulProcess) : ObservableItem<HideProcessItem>() {
|
||||
class HideProcessItem(
|
||||
val item: StatefulProcess,
|
||||
val viewModel: HideViewModel
|
||||
) : ObservableItem<HideProcessItem>() {
|
||||
|
||||
override val layoutRes = R.layout.item_hide_process_md2
|
||||
|
||||
@get:Bindable
|
||||
var isHidden = item.isHidden
|
||||
set(value) = set(value, field, { field = it }, BR.hidden)
|
||||
set(value) = set(value, field, { field = it }, BR.hidden) {
|
||||
viewModel.toggleItem(this)
|
||||
}
|
||||
|
||||
|
||||
fun toggle(viewModel: HideViewModel) {
|
||||
fun toggle() {
|
||||
isHidden = !isHidden
|
||||
viewModel.toggleItem(this)
|
||||
}
|
||||
|
||||
override fun contentSameAs(other: HideProcessItem) = item == other.item
|
||||
|
@ -8,8 +8,8 @@ import com.topjohnwu.magisk.core.Config
|
||||
import com.topjohnwu.magisk.core.utils.currentLocale
|
||||
import com.topjohnwu.magisk.data.repository.MagiskRepository
|
||||
import com.topjohnwu.magisk.model.entity.HideAppInfo
|
||||
import com.topjohnwu.magisk.model.entity.HideAppTarget
|
||||
import com.topjohnwu.magisk.model.entity.HideTarget
|
||||
import com.topjohnwu.magisk.model.entity.ProcessHideApp
|
||||
import com.topjohnwu.magisk.model.entity.StatefulProcess
|
||||
import com.topjohnwu.magisk.model.entity.recycler.HideItem
|
||||
import com.topjohnwu.magisk.model.entity.recycler.HideProcessItem
|
||||
@ -54,7 +54,10 @@ class HideViewModel(
|
||||
val apps = magiskRepo.fetchApps()
|
||||
val hides = magiskRepo.fetchHideTargets()
|
||||
val (appList, diff) = withContext(Dispatchers.Default) {
|
||||
val list = apps.map { mergeAppTargets(it, hides) }.map { HideItem(it) }.sort()
|
||||
val list = apps
|
||||
.map { createTarget(it, hides) }
|
||||
.map { HideItem(it, this@HideViewModel) }
|
||||
.sort()
|
||||
list to items.calculateDiff(list)
|
||||
}
|
||||
items.update(appList, diff)
|
||||
@ -64,12 +67,13 @@ class HideViewModel(
|
||||
|
||||
// ---
|
||||
|
||||
private fun mergeAppTargets(a: HideAppInfo, ts: List<HideTarget>): ProcessHideApp {
|
||||
val relevantTargets = ts.filter { it.packageName == a.info.packageName }
|
||||
val packageName = a.info.packageName
|
||||
val processes = a.processes
|
||||
.map { StatefulProcess(it, packageName, relevantTargets.any { i -> it == i.process }) }
|
||||
return ProcessHideApp(a, processes)
|
||||
private fun createTarget(app: HideAppInfo, hideList: List<HideTarget>): HideAppTarget {
|
||||
val hidden = hideList.filter { it.packageName == app.info.packageName }
|
||||
val packageName = app.info.packageName
|
||||
val processes = app.processes.map { name ->
|
||||
StatefulProcess(name, packageName, hidden.any { name == it.process })
|
||||
}
|
||||
return HideAppTarget(app, processes)
|
||||
}
|
||||
|
||||
private fun List<HideItem>.sort() = compareByDescending<HideItem> { it.itemsChecked != 0 }
|
||||
|
@ -14,6 +14,8 @@ import androidx.annotation.DrawableRes
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.databinding.BindingAdapter
|
||||
import androidx.databinding.InverseBindingAdapter
|
||||
import androidx.databinding.InverseBindingListener
|
||||
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
|
||||
import androidx.recyclerview.widget.*
|
||||
import com.google.android.material.button.MaterialButton
|
||||
@ -23,6 +25,7 @@ import com.google.android.material.textfield.TextInputLayout
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.ktx.replaceRandomWithSpecial
|
||||
import com.topjohnwu.superuser.internal.UiThreadHandler
|
||||
import com.topjohnwu.widget.IndeterminateCheckBox
|
||||
import kotlinx.coroutines.*
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@ -246,3 +249,23 @@ fun RecyclerView.setSpanCount(count: Int) {
|
||||
is StaggeredGridLayoutManager -> lama.spanCount = count
|
||||
}
|
||||
}
|
||||
|
||||
@BindingAdapter("state")
|
||||
fun setState(view: IndeterminateCheckBox, state: Boolean?) {
|
||||
if (view.state != state)
|
||||
view.state = state
|
||||
}
|
||||
|
||||
@InverseBindingAdapter(attribute = "state")
|
||||
fun getState(view: IndeterminateCheckBox) = view.state
|
||||
|
||||
|
||||
@BindingAdapter("stateAttrChanged")
|
||||
fun setListeners(
|
||||
view: IndeterminateCheckBox,
|
||||
attrChange: InverseBindingListener
|
||||
) {
|
||||
view.setOnStateChangedListener { _, _ ->
|
||||
attrChange.onChange()
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,7 @@
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/hide_content"
|
||||
dividerVertical="@{@drawable/divider_l1}"
|
||||
dividerVertical="@{@drawable/divider_l_50}"
|
||||
invisibleUnless="@{viewModel.loaded || !viewModel.items.empty}"
|
||||
itemBinding="@{viewModel.itemBinding}"
|
||||
items="@{viewModel.items}"
|
||||
@ -70,9 +70,10 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
android:orientation="vertical"
|
||||
tools:visibility="gone">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/safetynet_attest_loading"
|
||||
|
@ -33,13 +33,12 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?selectableItemBackground"
|
||||
android:onClick="@{(v) -> item.toggle(v)}">
|
||||
android:onClick="@{item::toggleExpand}">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
<ImageView
|
||||
android:id="@+id/hide_icon"
|
||||
style="@style/WidgetFoundation.Image"
|
||||
android:layout_margin="@dimen/l1"
|
||||
android:alpha="@{item.isExpanded ? 0.1f : 1f}"
|
||||
android:src="@{item.item.info.icon}"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
@ -47,22 +46,7 @@
|
||||
app:layout_constraintVertical_bias="0"
|
||||
tools:src="@drawable/ic_magisk" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/hide_action"
|
||||
style="@style/WidgetFoundation.Image"
|
||||
isSelected="@{item.itemsCheckedPercent == 100}"
|
||||
goneUnless="@{item.isExpanded}"
|
||||
android:layout_margin="@dimen/l1"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:onClick="@{() -> item.toggle(viewModel)}"
|
||||
android:padding="@dimen/l_25"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/hide_icon"
|
||||
app:layout_constraintEnd_toEndOf="@+id/hide_icon"
|
||||
app:layout_constraintStart_toStartOf="@+id/hide_icon"
|
||||
app:layout_constraintTop_toTopOf="@+id/hide_icon"
|
||||
app:srcCompat="@drawable/ic_hide_select_md2" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
<TextView
|
||||
android:id="@+id/hide_name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
@ -79,7 +63,7 @@
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
tools:text="@string/app_name" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
<TextView
|
||||
android:id="@+id/hide_package"
|
||||
gone="@{item.item.info.info.packageName.empty}"
|
||||
android:layout_width="0dp"
|
||||
@ -92,18 +76,14 @@
|
||||
app:layout_constraintTop_toBottomOf="@+id/hide_name"
|
||||
tools:text="com.topjohnwu.magisk" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
<com.topjohnwu.widget.IndeterminateCheckBox
|
||||
android:id="@+id/hide_expand_icon"
|
||||
style="@style/WidgetFoundation.Icon"
|
||||
invisible="@{item.item.processes.empty}"
|
||||
srcCompat="@{item.isExpanded ? R.drawable.ic_close_md2 : R.drawable.ic_back_md2}"
|
||||
android:background="@null"
|
||||
android:rotation="180"
|
||||
android:rotationX="@{item.isExpanded ? 180 : 0}"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:srcCompat="@drawable/ic_back_md2" />
|
||||
state="@={item.hiddenState}"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
@ -19,11 +19,9 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:alpha="@{item.isHidden() ? 1f : .7f}"
|
||||
android:background="?selectableItemBackground"
|
||||
android:onClick="@{() -> item.toggle(viewModel)}">
|
||||
android:alpha="@{item.hidden ? 1f : .7f}">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/l1"
|
||||
@ -39,18 +37,15 @@
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="com.topjohnwu.magisk" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||
android:id="@+id/hide_process_checkbox"
|
||||
style="@style/WidgetFoundation.Image.Small"
|
||||
isSelected="@{item.isHidden}"
|
||||
android:checked="@={item.hidden}"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="@dimen/l_50"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:background="@drawable/bg_checkbox"
|
||||
app:srcCompat="@drawable/ic_checkbox" />
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user