mirror of
https://github.com/topjohnwu/Magisk.git
synced 2024-12-25 10:17:38 +00:00
Updated Hide screen to be fully functioning
...although still misses search :(
This commit is contained in:
parent
f76c020dd7
commit
6aa22267f4
@ -17,7 +17,7 @@ import org.koin.dsl.module
|
||||
|
||||
val redesignModule = module {
|
||||
viewModel { FlashViewModel() }
|
||||
viewModel { HideViewModel(get(), get()) }
|
||||
viewModel { HideViewModel(get()) }
|
||||
viewModel { HomeViewModel(get()) }
|
||||
viewModel { LogViewModel() }
|
||||
viewModel { ModuleViewModel() }
|
||||
|
@ -17,6 +17,7 @@ class HideAppInfo(
|
||||
|
||||
data class StatefulProcess(
|
||||
val name: String,
|
||||
val packageName: String,
|
||||
val isHidden: Boolean
|
||||
)
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.topjohnwu.magisk.model.entity.recycler
|
||||
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.topjohnwu.magisk.R
|
||||
@ -14,6 +15,7 @@ import com.topjohnwu.magisk.model.entity.ProcessHideApp
|
||||
import com.topjohnwu.magisk.model.entity.StatefulProcess
|
||||
import com.topjohnwu.magisk.model.entity.state.IndeterminateState
|
||||
import com.topjohnwu.magisk.model.events.HideProcessEvent
|
||||
import com.topjohnwu.magisk.redesign.hide.HideViewModel
|
||||
import com.topjohnwu.magisk.utils.DiffObservableList
|
||||
import com.topjohnwu.magisk.utils.KObservableField
|
||||
import com.topjohnwu.magisk.utils.RxBus
|
||||
@ -22,14 +24,18 @@ class HideItem(val item: ProcessHideApp) : ComparableRvItem<HideItem>() {
|
||||
|
||||
override val layoutRes = R.layout.item_hide_md2
|
||||
|
||||
val packageName = item.info.info.packageName.orEmpty()
|
||||
val items = item.processes.map { HideProcessItem(it) }
|
||||
|
||||
val isExpanded = KObservableField(false)
|
||||
val itemsChecked = KObservableField(0)
|
||||
val isHidden get() = itemsChecked.value == items.size
|
||||
|
||||
/** [toggle] depends on this functionality */
|
||||
private val isHidden get() = itemsChecked.value == items.size
|
||||
|
||||
init {
|
||||
items.forEach { it.isHidden.addOnPropertyChangedCallback { recalculateChecked() } }
|
||||
recalculateChecked()
|
||||
}
|
||||
|
||||
fun collapse(v: View) {
|
||||
@ -42,6 +48,17 @@ class HideItem(val item: ProcessHideApp) : ComparableRvItem<HideItem>() {
|
||||
isExpanded.value = true
|
||||
}
|
||||
|
||||
fun toggle(menuItem: MenuItem, viewModel: HideViewModel): Boolean {
|
||||
if (menuItem.itemId != R.id.action_toggle) return false
|
||||
// contract implies that isHidden == all checked
|
||||
if (!isHidden) {
|
||||
items.filterNot { it.isHidden.value }
|
||||
} else {
|
||||
items
|
||||
}.forEach { it.toggle(viewModel) }
|
||||
return true
|
||||
}
|
||||
|
||||
private fun recalculateChecked() {
|
||||
itemsChecked.value = items.count { it.isHidden.value }
|
||||
}
|
||||
@ -57,7 +74,10 @@ class HideProcessItem(val item: StatefulProcess) : ComparableRvItem<HideProcessI
|
||||
|
||||
val isHidden = KObservableField(item.isHidden)
|
||||
|
||||
fun toggle() = isHidden.toggle()
|
||||
fun toggle(viewModel: HideViewModel) {
|
||||
isHidden.toggle()
|
||||
viewModel.toggleItem(this)
|
||||
}
|
||||
|
||||
override fun contentSameAs(other: HideProcessItem) = item == other.item
|
||||
override fun itemSameAs(other: HideProcessItem) = item.name == other.item.name
|
||||
|
@ -2,6 +2,9 @@ package com.topjohnwu.magisk.redesign.hide
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Insets
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.databinding.FragmentHideMd2Binding
|
||||
import com.topjohnwu.magisk.redesign.compat.CompatFragment
|
||||
@ -16,8 +19,17 @@ class HideFragment : CompatFragment<HideViewModel, FragmentHideMd2Binding>() {
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
|
||||
activity.setTitle(R.string.magiskhide)
|
||||
setHasOptionsMenu(true)
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.menu_hide_md2, menu)
|
||||
menu.findItem(R.id.action_show_system)?.isChecked = viewModel.isShowSystem
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return viewModel.menuItemPressed(item)
|
||||
}
|
||||
|
||||
}
|
@ -1,46 +1,37 @@
|
||||
package com.topjohnwu.magisk.redesign.hide
|
||||
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.view.MenuItem
|
||||
import com.topjohnwu.magisk.BR
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.data.repository.MagiskRepository
|
||||
import com.topjohnwu.magisk.databinding.ComparableRvItem
|
||||
import com.topjohnwu.magisk.extensions.subscribeK
|
||||
import com.topjohnwu.magisk.extensions.toSingle
|
||||
import com.topjohnwu.magisk.model.entity.HideAppInfo
|
||||
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
|
||||
import com.topjohnwu.magisk.model.entity.recycler.HideProcessRvItem
|
||||
import com.topjohnwu.magisk.model.events.HideProcessEvent
|
||||
import com.topjohnwu.magisk.redesign.compat.CompatViewModel
|
||||
import com.topjohnwu.magisk.redesign.home.itemBindingOf
|
||||
import com.topjohnwu.magisk.redesign.superuser.diffListOf
|
||||
import com.topjohnwu.magisk.utils.DiffObservableList
|
||||
import com.topjohnwu.magisk.utils.FilterableDiffObservableList
|
||||
import com.topjohnwu.magisk.utils.KObservableField
|
||||
import com.topjohnwu.magisk.utils.RxBus
|
||||
import com.topjohnwu.magisk.utils.currentLocale
|
||||
import io.reactivex.disposables.Disposable
|
||||
import java.util.*
|
||||
|
||||
class HideViewModel(
|
||||
private val magiskRepo: MagiskRepository,
|
||||
rxBus: RxBus
|
||||
private val magiskRepo: MagiskRepository
|
||||
) : CompatViewModel() {
|
||||
|
||||
@Volatile
|
||||
private var cache = listOf<HideItem>()
|
||||
var isShowSystem = false
|
||||
set(value) {
|
||||
field = Collections.synchronizedList(value)
|
||||
}
|
||||
private var queryJob: Disposable? = null
|
||||
set(value) {
|
||||
field?.dispose()
|
||||
field = value
|
||||
query()
|
||||
}
|
||||
|
||||
val query = KObservableField("")
|
||||
val isShowSystem = KObservableField(true)
|
||||
val items = diffListOf<HideItem>()
|
||||
val items = filterableListOf<HideItem>()
|
||||
val itemBinding = itemBindingOf<HideItem> {
|
||||
it.bindExtra(BR.viewModel, this)
|
||||
}
|
||||
@ -48,12 +39,6 @@ class HideViewModel(
|
||||
it.bindExtra(BR.viewModel, this)
|
||||
}
|
||||
|
||||
init {
|
||||
rxBus.register<HideProcessEvent>()
|
||||
.subscribeK { toggleItem(it.item) }
|
||||
.add()
|
||||
}
|
||||
|
||||
override fun refresh() = magiskRepo.fetchApps()
|
||||
.map { it to magiskRepo.fetchHideTargets().blockingGet() }
|
||||
.map { pair -> pair.first.map { mergeAppTargets(it, pair.second) } }
|
||||
@ -61,64 +46,66 @@ class HideViewModel(
|
||||
.map { HideItem(it) }
|
||||
.toList()
|
||||
.map { it.sort() }
|
||||
.map { it to items.calculateDiff(it) }
|
||||
.subscribeK {
|
||||
cache = it
|
||||
queryIfNecessary()
|
||||
items.update(it.first, it.second)
|
||||
query()
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
queryJob?.dispose()
|
||||
super.onCleared()
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
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, relevantTargets.any { i -> it == i.process }) }
|
||||
.map { StatefulProcess(it, packageName, relevantTargets.any { i -> it == i.process }) }
|
||||
return ProcessHideApp(a, processes)
|
||||
}
|
||||
|
||||
private fun List<HideItem>.sort() = sortedWith(compareBy(
|
||||
{ it.isHidden },
|
||||
{ it.item.info.name.toLowerCase(currentLocale) },
|
||||
{ it.item.info.info.packageName }
|
||||
))
|
||||
private fun List<HideItem>.sort() = compareByDescending<HideItem> { it.itemsChecked.value }
|
||||
.thenBy { it.item.info.name.toLowerCase(currentLocale) }
|
||||
.thenBy { it.item.info.info.packageName }
|
||||
.let { sortedWith(it) }
|
||||
|
||||
// ---
|
||||
|
||||
/** We don't need to re-query when the app count matches. */
|
||||
private fun queryIfNecessary() {
|
||||
if (items.size != cache.size) {
|
||||
query()
|
||||
}
|
||||
}
|
||||
|
||||
private fun query(
|
||||
query: String = this.query.value,
|
||||
showSystem: Boolean = isShowSystem.value
|
||||
) = cache.toSingle()
|
||||
.flattenAsFlowable { it }
|
||||
.parallel()
|
||||
.filter { showSystem || it.item.info.info.flags and ApplicationInfo.FLAG_SYSTEM == 0 }
|
||||
.filter {
|
||||
showSystem: Boolean = isShowSystem
|
||||
) = items.filter {
|
||||
fun filterSystem(): Boolean {
|
||||
return showSystem || it.item.info.info.flags and ApplicationInfo.FLAG_SYSTEM == 0
|
||||
}
|
||||
|
||||
fun filterQuery(): Boolean {
|
||||
val inName = it.item.info.name.contains(query, true)
|
||||
val inPackage = it.item.info.info.packageName.contains(query, true)
|
||||
val inProcesses = it.item.processes.any { it.name.contains(query, true) }
|
||||
inName || inPackage || inProcesses
|
||||
return inName || inPackage || inProcesses
|
||||
}
|
||||
.sequential()
|
||||
.toList()
|
||||
.map { it to items.calculateDiff(it) }
|
||||
.subscribeK { items.update(it.first, it.second) }
|
||||
.let { queryJob = it }
|
||||
|
||||
filterSystem() && filterQuery()
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
private fun toggleItem(item: HideProcessRvItem) = magiskRepo
|
||||
.toggleHide(item.isHidden.value, item.packageName, item.process)
|
||||
fun menuItemPressed(menuItem: MenuItem) = when (menuItem.itemId) {
|
||||
R.id.action_show_system -> isShowSystem = (!menuItem.isChecked)
|
||||
.also { menuItem.isChecked = it }
|
||||
else -> null
|
||||
}?.let { true } ?: false
|
||||
|
||||
fun toggleItem(item: HideProcessItem) = magiskRepo
|
||||
.toggleHide(item.isHidden.value, item.item.packageName, item.item.name)
|
||||
// might wanna reorder the list to display the item at the top
|
||||
.subscribeK()
|
||||
.add()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <T : ComparableRvItem<T>> filterableListOf(
|
||||
vararg newItems: T
|
||||
) = FilterableDiffObservableList(object : DiffObservableList.Callback<T> {
|
||||
override fun areItemsTheSame(oldItem: T, newItem: T) = oldItem.genericItemSameAs(newItem)
|
||||
override fun areContentsTheSame(oldItem: T, newItem: T) = oldItem.genericContentSameAs(newItem)
|
||||
}).also { it.update(newItems.toList()) }
|
@ -403,4 +403,9 @@ fun MaterialCardView.setCardElevationBound(elevation: Float) {
|
||||
@BindingAdapter("strokeWidth")
|
||||
fun MaterialCardView.setCardStrokeWidthBound(stroke: Float) {
|
||||
strokeWidth = stroke.roundToInt()
|
||||
}
|
||||
|
||||
@BindingAdapter("onMenuClick")
|
||||
fun Toolbar.setOnMenuClickListener(listener: Toolbar.OnMenuItemClickListener) {
|
||||
setOnMenuItemClickListener(listener)
|
||||
}
|
@ -18,9 +18,9 @@ open class DiffObservableList<T>(
|
||||
) : AbstractList<T>(), ObservableList<T> {
|
||||
|
||||
private val LIST_LOCK = Object()
|
||||
private var list: MutableList<T> = ArrayList()
|
||||
protected var list: MutableList<T> = ArrayList()
|
||||
private val listeners = ListChangeRegistry()
|
||||
private val listCallback = ObservableListUpdateCallback()
|
||||
protected val listCallback = ObservableListUpdateCallback()
|
||||
|
||||
override val size: Int get() = list.size
|
||||
|
||||
@ -38,7 +38,7 @@ open class DiffObservableList<T>(
|
||||
return doCalculateDiff(frozenList, newItems)
|
||||
}
|
||||
|
||||
private fun doCalculateDiff(oldItems: List<T>, newItems: List<T>?): DiffUtil.DiffResult {
|
||||
protected fun doCalculateDiff(oldItems: List<T>, newItems: List<T>?): DiffUtil.DiffResult {
|
||||
return DiffUtil.calculateDiff(object : DiffUtil.Callback() {
|
||||
override fun getOldListSize() = oldItems.size
|
||||
|
||||
|
@ -0,0 +1,85 @@
|
||||
package com.topjohnwu.magisk.utils
|
||||
|
||||
import android.os.Handler
|
||||
import android.os.HandlerThread
|
||||
import android.os.Looper
|
||||
import java.util.*
|
||||
|
||||
class FilterableDiffObservableList<T>(
|
||||
callback: Callback<T>
|
||||
) : DiffObservableList<T>(callback) {
|
||||
|
||||
var filter: ((T) -> Boolean)? = null
|
||||
set(value) {
|
||||
field = value
|
||||
queueUpdate()
|
||||
}
|
||||
@Volatile
|
||||
private var sublist: MutableList<T> = super.list
|
||||
|
||||
// ---
|
||||
|
||||
private val ui by lazy { Handler(Looper.getMainLooper()) }
|
||||
private val handler = Handler(HandlerThread("List${hashCode()}").apply { start() }.looper)
|
||||
private val updater = Runnable {
|
||||
val filter = filter ?: { true }
|
||||
val newList = super.list.filter(filter)
|
||||
val diff = synchronized(this) { doCalculateDiff(sublist, newList) }
|
||||
ui.post {
|
||||
sublist = Collections.synchronizedList(newList)
|
||||
diff.dispatchUpdatesTo(listCallback)
|
||||
}
|
||||
}
|
||||
|
||||
private fun queueUpdate() {
|
||||
handler.removeCallbacks(updater)
|
||||
handler.post(updater)
|
||||
}
|
||||
|
||||
fun hasFilter() = filter != null
|
||||
|
||||
fun filter(switch: (T) -> Boolean) {
|
||||
filter = switch
|
||||
}
|
||||
|
||||
fun reset() {
|
||||
filter = null
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
override fun get(index: Int): T {
|
||||
return sublist.get(index)
|
||||
}
|
||||
|
||||
override fun add(element: T): Boolean {
|
||||
return sublist.add(element)
|
||||
}
|
||||
|
||||
override fun add(index: Int, element: T) {
|
||||
sublist.add(index, element)
|
||||
}
|
||||
|
||||
override fun addAll(elements: Collection<T>): Boolean {
|
||||
return sublist.addAll(elements)
|
||||
}
|
||||
|
||||
override fun addAll(index: Int, elements: Collection<T>): Boolean {
|
||||
return sublist.addAll(index, elements)
|
||||
}
|
||||
|
||||
override fun remove(element: T): Boolean {
|
||||
return sublist.remove(element)
|
||||
}
|
||||
|
||||
override fun removeAt(index: Int): T {
|
||||
return sublist.removeAt(index)
|
||||
}
|
||||
|
||||
override fun set(index: Int, element: T): T {
|
||||
return sublist.set(index, element)
|
||||
}
|
||||
|
||||
override val size: Int
|
||||
get() = sublist.size
|
||||
}
|
@ -59,6 +59,8 @@
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/hide_package"
|
||||
android:layout_width="0dp"
|
||||
gone="@{item.item.info.info.packageName.empty}"
|
||||
android:text="@{item.item.info.info.packageName}"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?appearanceTextCaptionVariant"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
@ -90,6 +92,8 @@
|
||||
style="?styleToolbar"
|
||||
onNavigationClick="@{(v) -> item.collapse(v)}"
|
||||
android:layout_width="match_parent"
|
||||
onMenuClick="@{(it) -> item.toggle(it, viewModel)}"
|
||||
app:menu="@menu/menu_hide_item"
|
||||
android:layout_height="48dp"
|
||||
app:navigationIcon="@drawable/ic_back_md2"
|
||||
app:title="Processes"
|
||||
@ -100,6 +104,7 @@
|
||||
items="@{item.items}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:itemCount="2"
|
||||
android:orientation="vertical"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:listitem="@layout/item_hide_process_md2" />
|
||||
|
@ -17,6 +17,8 @@
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:background="?selectableItemBackground"
|
||||
android:onClick="@{() -> item.toggle(viewModel)}"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center">
|
||||
|
||||
@ -41,10 +43,8 @@
|
||||
style="?styleImageSmall"
|
||||
isSelected="@{item.isHidden}"
|
||||
android:layout_marginTop="@dimen/l_50"
|
||||
android:layout_marginEnd="@dimen/l1"
|
||||
android:layout_marginEnd="@dimen/l_75"
|
||||
android:layout_marginBottom="@dimen/l_50"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:onClick="@{() -> item.toggle()}"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
|
9
app/src/main/res/menu/menu_hide_item.xml
Normal file
9
app/src/main/res/menu/menu_hide_item.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/action_toggle"
|
||||
android:icon="@drawable/ic_hide_md2"
|
||||
android:title=""
|
||||
app:showAsAction="always" />
|
||||
</menu>
|
9
app/src/main/res/menu/menu_hide_md2.xml
Normal file
9
app/src/main/res/menu/menu_hide_md2.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/action_show_system"
|
||||
android:checkable="true"
|
||||
android:title="@string/show_system_app"
|
||||
app:showAsAction="never" />
|
||||
</menu>
|
Loading…
x
Reference in New Issue
Block a user