mirror of
https://github.com/topjohnwu/Magisk.git
synced 2024-12-25 09:37:38 +00:00
Added implementation of hide screen
Very much wip and doesn't work at all
This commit is contained in:
parent
722fba7805
commit
f76c020dd7
@ -17,7 +17,7 @@ import org.koin.dsl.module
|
||||
|
||||
val redesignModule = module {
|
||||
viewModel { FlashViewModel() }
|
||||
viewModel { HideViewModel() }
|
||||
viewModel { HideViewModel(get(), get()) }
|
||||
viewModel { HomeViewModel(get()) }
|
||||
viewModel { LogViewModel() }
|
||||
viewModel { ModuleViewModel() }
|
||||
|
@ -197,5 +197,8 @@ fun <T> ObservableField<T>.toObservable(): Observable<T> {
|
||||
|
||||
fun <T : Any> T.toSingle() = Single.just(this)
|
||||
|
||||
fun <T1, T2, R> zip(t1: Single<T1>, t2: Single<T2>, zipper: (T1, T2) -> R) =
|
||||
Single.zip(t1, t2, BiFunction<T1, T2, R> { rt1, rt2 -> zipper(rt1, rt2) })
|
||||
inline fun <T1, T2, R> zip(
|
||||
t1: Single<T1>,
|
||||
t2: Single<T2>,
|
||||
crossinline zipper: (T1, T2) -> R
|
||||
) = Single.zip(t1, t2, BiFunction<T1, T2, R> { rt1, rt2 -> zipper(rt1, rt2) })
|
@ -14,3 +14,13 @@ class HideAppInfo(
|
||||
val processes = info.packageInfo?.processes?.distinct() ?: listOf(info.packageName)
|
||||
|
||||
}
|
||||
|
||||
data class StatefulProcess(
|
||||
val name: String,
|
||||
val isHidden: Boolean
|
||||
)
|
||||
|
||||
class ProcessHideApp(
|
||||
val info: HideAppInfo,
|
||||
val processes: List<StatefulProcess>
|
||||
)
|
@ -1,18 +1,69 @@
|
||||
package com.topjohnwu.magisk.model.entity.recycler
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.databinding.ComparableRvItem
|
||||
import com.topjohnwu.magisk.extensions.addOnPropertyChangedCallback
|
||||
import com.topjohnwu.magisk.extensions.inject
|
||||
import com.topjohnwu.magisk.extensions.startAnimations
|
||||
import com.topjohnwu.magisk.extensions.toggle
|
||||
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.state.IndeterminateState
|
||||
import com.topjohnwu.magisk.model.events.HideProcessEvent
|
||||
import com.topjohnwu.magisk.utils.DiffObservableList
|
||||
import com.topjohnwu.magisk.utils.KObservableField
|
||||
import com.topjohnwu.magisk.utils.RxBus
|
||||
|
||||
class HideItem(val item: ProcessHideApp) : ComparableRvItem<HideItem>() {
|
||||
|
||||
override val layoutRes = R.layout.item_hide_md2
|
||||
|
||||
val items = item.processes.map { HideProcessItem(it) }
|
||||
|
||||
val isExpanded = KObservableField(false)
|
||||
val itemsChecked = KObservableField(0)
|
||||
val isHidden get() = itemsChecked.value == items.size
|
||||
|
||||
init {
|
||||
items.forEach { it.isHidden.addOnPropertyChangedCallback { recalculateChecked() } }
|
||||
}
|
||||
|
||||
fun collapse(v: View) {
|
||||
(v.parent.parent as? ViewGroup)?.startAnimations()
|
||||
isExpanded.value = false
|
||||
}
|
||||
|
||||
fun expand(v: View) {
|
||||
(v.parent as? ViewGroup)?.startAnimations()
|
||||
isExpanded.value = true
|
||||
}
|
||||
|
||||
private fun recalculateChecked() {
|
||||
itemsChecked.value = items.count { it.isHidden.value }
|
||||
}
|
||||
|
||||
override fun contentSameAs(other: HideItem): Boolean = item == other.item
|
||||
override fun itemSameAs(other: HideItem): Boolean = item.info == other.item.info
|
||||
|
||||
}
|
||||
|
||||
class HideProcessItem(val item: StatefulProcess) : ComparableRvItem<HideProcessItem>() {
|
||||
|
||||
override val layoutRes = R.layout.item_hide_process_md2
|
||||
|
||||
val isHidden = KObservableField(item.isHidden)
|
||||
|
||||
fun toggle() = isHidden.toggle()
|
||||
|
||||
override fun contentSameAs(other: HideProcessItem) = item == other.item
|
||||
override fun itemSameAs(other: HideProcessItem) = item.name == other.item.name
|
||||
|
||||
}
|
||||
|
||||
class HideRvItem(val item: HideAppInfo, targets: List<HideTarget>) :
|
||||
ComparableRvItem<HideRvItem>() {
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.topjohnwu.magisk.redesign.hide
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Insets
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.databinding.FragmentHideMd2Binding
|
||||
import com.topjohnwu.magisk.redesign.compat.CompatFragment
|
||||
@ -11,6 +12,8 @@ class HideFragment : CompatFragment<HideViewModel, FragmentHideMd2Binding>() {
|
||||
override val layoutRes = R.layout.fragment_hide_md2
|
||||
override val viewModel by viewModel<HideViewModel>()
|
||||
|
||||
override fun consumeSystemWindowInsets(insets: Insets) = insets
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
|
||||
|
@ -1,5 +1,124 @@
|
||||
package com.topjohnwu.magisk.redesign.hide
|
||||
|
||||
import android.content.pm.ApplicationInfo
|
||||
import com.topjohnwu.magisk.BR
|
||||
import com.topjohnwu.magisk.data.repository.MagiskRepository
|
||||
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.KObservableField
|
||||
import com.topjohnwu.magisk.utils.RxBus
|
||||
import com.topjohnwu.magisk.utils.currentLocale
|
||||
import io.reactivex.disposables.Disposable
|
||||
import java.util.*
|
||||
|
||||
class HideViewModel : CompatViewModel()
|
||||
class HideViewModel(
|
||||
private val magiskRepo: MagiskRepository,
|
||||
rxBus: RxBus
|
||||
) : CompatViewModel() {
|
||||
|
||||
@Volatile
|
||||
private var cache = listOf<HideItem>()
|
||||
set(value) {
|
||||
field = Collections.synchronizedList(value)
|
||||
}
|
||||
private var queryJob: Disposable? = null
|
||||
set(value) {
|
||||
field?.dispose()
|
||||
field = value
|
||||
}
|
||||
|
||||
val query = KObservableField("")
|
||||
val isShowSystem = KObservableField(true)
|
||||
val items = diffListOf<HideItem>()
|
||||
val itemBinding = itemBindingOf<HideItem> {
|
||||
it.bindExtra(BR.viewModel, this)
|
||||
}
|
||||
val itemInternalBinding = itemBindingOf<HideProcessItem> {
|
||||
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) } }
|
||||
.flattenAsFlowable { it }
|
||||
.map { HideItem(it) }
|
||||
.toList()
|
||||
.map { it.sort() }
|
||||
.subscribeK {
|
||||
cache = it
|
||||
queryIfNecessary()
|
||||
}
|
||||
|
||||
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 processes = a.processes
|
||||
.map { StatefulProcess(it, 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 }
|
||||
))
|
||||
|
||||
// ---
|
||||
|
||||
/** 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 {
|
||||
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
|
||||
}
|
||||
.sequential()
|
||||
.toList()
|
||||
.map { it to items.calculateDiff(it) }
|
||||
.subscribeK { items.update(it.first, it.second) }
|
||||
.let { queryJob = it }
|
||||
|
||||
// ---
|
||||
|
||||
private fun toggleItem(item: HideProcessRvItem) = magiskRepo
|
||||
.toggleHide(item.isHidden.value, item.packageName, item.process)
|
||||
.subscribeK()
|
||||
.add()
|
||||
|
||||
}
|
@ -22,6 +22,27 @@
|
||||
</vector>
|
||||
</item>
|
||||
|
||||
<item
|
||||
android:id="@+id/collapsed_selected"
|
||||
android:state_selected="true">
|
||||
<vector
|
||||
android:name="vector"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<group
|
||||
android:name="group"
|
||||
android:pivotX="12"
|
||||
android:pivotY="12">
|
||||
<path
|
||||
android:name="path"
|
||||
android:fillColor="#000"
|
||||
android:pathData="M 12 2 C 9.349 2 6.804 3.054 4.929 4.929 C 3.054 6.804 2 9.349 2 12 C 2 14.651 3.054 17.196 4.929 19.071 C 6.804 20.946 9.349 22 12 22 C 13.755 22 15.48 21.538 17 20.66 C 18.52 19.783 19.783 18.52 20.66 17 C 21.538 15.48 22 13.755 22 12 C 22 10.245 21.538 8.52 20.66 7 C 19.783 5.48 18.52 4.217 17 3.34 C 15.48 2.462 13.755 2 12 2 Z" />
|
||||
</group>
|
||||
</vector>
|
||||
</item>
|
||||
|
||||
<item android:id="@+id/expanded">
|
||||
<vector
|
||||
android:name="vector"
|
||||
@ -52,4 +73,14 @@
|
||||
android:fromId="@+id/collapsed"
|
||||
android:toId="@id/expanded" />
|
||||
|
||||
<transition
|
||||
android:drawable="@drawable/avd_circle_from_filled"
|
||||
android:fromId="@+id/expanded"
|
||||
android:toId="@+id/collapsed_selected" />
|
||||
|
||||
<transition
|
||||
android:drawable="@drawable/avd_circle_to_filled"
|
||||
android:fromId="@+id/collapsed_selected"
|
||||
android:toId="@id/expanded" />
|
||||
|
||||
</animated-selector>
|
@ -1,23 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<data>
|
||||
|
||||
<import type="com.topjohnwu.magisk.R" />
|
||||
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="com.topjohnwu.magisk.redesign.hide.HideViewModel" />
|
||||
|
||||
</data>
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
dividerVertical="@{R.drawable.divider_l1}"
|
||||
itemBinding="@{viewModel.itemBinding}"
|
||||
items="@{viewModel.items}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
android:clipToPadding="false"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="@dimen/l1"
|
||||
android:paddingTop="@{viewModel.insets.top + (int) @dimen/internal_action_bar_size + (int) @dimen/l1}"
|
||||
android:paddingEnd="@dimen/l1"
|
||||
android:paddingBottom="@{viewModel.insets.bottom}"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:listitem="@layout/item_hide_md2"
|
||||
tools:paddingTop="40dp" />
|
||||
|
||||
</layout>
|
118
app/src/main/res/layout/item_hide_md2.xml
Normal file
118
app/src/main/res/layout/item_hide_md2.xml
Normal file
@ -0,0 +1,118 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<data>
|
||||
|
||||
<variable
|
||||
name="item"
|
||||
type="com.topjohnwu.magisk.model.entity.recycler.HideItem" />
|
||||
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="com.topjohnwu.magisk.redesign.hide.HideViewModel" />
|
||||
|
||||
</data>
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
style="?styleCardNormal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:layout_gravity="center"
|
||||
tools:layout_marginBottom="@dimen/l1">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
invisible="@{item.isExpanded}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?selectableItemBackground"
|
||||
android:onClick="@{(v) -> item.expand(v)}">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/hide_icon"
|
||||
style="?styleImageNormal"
|
||||
android:layout_margin="@dimen/l1"
|
||||
android:src="@{item.item.info.icon}"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@drawable/ic_magisk" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/hide_name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/l1"
|
||||
android:ellipsize="middle"
|
||||
android:singleLine="true"
|
||||
android:text="@{item.item.info.name}"
|
||||
android:textAppearance="?appearanceTextBodyNormal"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toTopOf="@+id/hide_package"
|
||||
app:layout_constraintEnd_toStartOf="@+id/hide_expand_icon"
|
||||
app:layout_constraintStart_toEndOf="@+id/hide_icon"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
tools:text="@string/app_name" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/hide_package"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?appearanceTextCaptionVariant"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="@+id/hide_name"
|
||||
app:layout_constraintStart_toStartOf="@+id/hide_name"
|
||||
app:layout_constraintTop_toBottomOf="@+id/hide_name"
|
||||
tools:text="com.topjohnwu.magisk" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/hide_expand_icon"
|
||||
style="?styleIconNormal"
|
||||
invisible="@{item.item.processes.empty}"
|
||||
android:background="@null"
|
||||
android:rotation="180"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_back_md2" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<LinearLayout
|
||||
goneUnless="@{item.isExpanded}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
style="?styleToolbar"
|
||||
onNavigationClick="@{(v) -> item.collapse(v)}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
app:navigationIcon="@drawable/ic_back_md2"
|
||||
app:title="Processes"
|
||||
app:titleTextAppearance="?appearanceTextBodyNormal" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
itemBinding="@{viewModel.itemInternalBinding}"
|
||||
items="@{item.items}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:listitem="@layout/item_hide_process_md2" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ProgressBar
|
||||
style="?styleProgressDeterminate"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_gravity="top"
|
||||
android:max="@{item.items.size()}"
|
||||
android:progress="@{item.itemsChecked}" />
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
</layout>
|
56
app/src/main/res/layout/item_hide_process_md2.xml
Normal file
56
app/src/main/res/layout/item_hide_process_md2.xml
Normal file
@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<data>
|
||||
|
||||
<variable
|
||||
name="item"
|
||||
type="com.topjohnwu.magisk.model.entity.recycler.HideProcessItem" />
|
||||
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="com.topjohnwu.magisk.redesign.hide.HideViewModel" />
|
||||
|
||||
</data>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/l1"
|
||||
android:layout_marginTop="@dimen/l_50"
|
||||
android:layout_marginEnd="@dimen/l1"
|
||||
android:layout_marginBottom="@dimen/l_50"
|
||||
android:singleLine="true"
|
||||
android:text="@{item.item.name}"
|
||||
android:textAppearance="?appearanceTextCaptionVariant"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/hide_process_checkbox"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="com.topjohnwu.magisk" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/hide_process_checkbox"
|
||||
style="?styleImageSmall"
|
||||
isSelected="@{item.isHidden}"
|
||||
android:layout_marginTop="@dimen/l_50"
|
||||
android:layout_marginEnd="@dimen/l1"
|
||||
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"
|
||||
app:srcCompat="@drawable/ic_radio_check_button"
|
||||
app:tint="?colorPrimary" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</layout>
|
Loading…
x
Reference in New Issue
Block a user