Updated Hide screen with new arch
This commit is contained in:
parent
cda14af208
commit
e81f00ef1a
@ -1,6 +1,7 @@
|
||||
package com.topjohnwu.magisk.di
|
||||
|
||||
import com.topjohnwu.magisk.ui.MainViewModel
|
||||
import com.topjohnwu.magisk.ui.hide.HideViewModel
|
||||
import com.topjohnwu.magisk.ui.home.HomeViewModel
|
||||
import com.topjohnwu.magisk.ui.superuser.SuperuserViewModel
|
||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
||||
@ -11,4 +12,5 @@ val viewModelModules = module {
|
||||
viewModel { MainViewModel() }
|
||||
viewModel { HomeViewModel(get(), get()) }
|
||||
viewModel { SuperuserViewModel(get(), get(), get()) }
|
||||
viewModel { HideViewModel(get(), get()) }
|
||||
}
|
||||
|
@ -0,0 +1,16 @@
|
||||
package com.topjohnwu.magisk.model.entity
|
||||
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.graphics.drawable.Drawable
|
||||
import com.topjohnwu.magisk.utils.packageInfo
|
||||
import com.topjohnwu.magisk.utils.processes
|
||||
|
||||
class HideAppInfo(
|
||||
val info: ApplicationInfo,
|
||||
val name: String,
|
||||
val icon: Drawable
|
||||
) {
|
||||
|
||||
val processes = info.packageInfo?.processes?.distinct() ?: listOf(info.packageName)
|
||||
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package com.topjohnwu.magisk.model.entity
|
||||
|
||||
class HideTarget(line: String) {
|
||||
|
||||
private val split = line.split(Regex("\\|"), 2)
|
||||
|
||||
val packageName = split[0]
|
||||
val process = split.getOrElse(1) { packageName }
|
||||
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
package com.topjohnwu.magisk.model.entity.recycler
|
||||
|
||||
import com.skoumal.teanity.databinding.ComparableRvItem
|
||||
import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
|
||||
import com.skoumal.teanity.rxbus.RxBus
|
||||
import com.skoumal.teanity.util.DiffObservableList
|
||||
import com.skoumal.teanity.util.KObservableField
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.model.entity.HideAppInfo
|
||||
import com.topjohnwu.magisk.model.entity.HideTarget
|
||||
import com.topjohnwu.magisk.model.entity.state.IndeterminateState
|
||||
import com.topjohnwu.magisk.model.events.HideProcessEvent
|
||||
import com.topjohnwu.magisk.utils.inject
|
||||
import com.topjohnwu.magisk.utils.toggle
|
||||
|
||||
class HideRvItem(val item: HideAppInfo, targets: List<HideTarget>) :
|
||||
ComparableRvItem<HideRvItem>() {
|
||||
|
||||
override val layoutRes: Int = R.layout.item_hide_app
|
||||
|
||||
val packageName = item.info.packageName.orEmpty()
|
||||
val items = DiffObservableList(callback).also {
|
||||
val items = item.processes.map {
|
||||
val isHidden = targets.any { target ->
|
||||
packageName == target.packageName && it == target.process
|
||||
}
|
||||
HideProcessRvItem(packageName, it, isHidden)
|
||||
}
|
||||
it.update(items)
|
||||
}
|
||||
val isHiddenState = KObservableField(currentState)
|
||||
val isExpanded = KObservableField(false)
|
||||
|
||||
private val itemsProcess get() = items.filterIsInstance<HideProcessRvItem>()
|
||||
|
||||
private val currentState
|
||||
get() = when (itemsProcess.count { it.isHidden.value }) {
|
||||
items.size -> IndeterminateState.CHECKED
|
||||
in 1 until items.size -> IndeterminateState.INDETERMINATE
|
||||
else -> IndeterminateState.UNCHECKED
|
||||
}
|
||||
|
||||
init {
|
||||
itemsProcess.forEach {
|
||||
it.isHidden.addOnPropertyChangedCallback { isHiddenState.value = currentState }
|
||||
}
|
||||
}
|
||||
|
||||
fun toggle() {
|
||||
val desiredState = when (isHiddenState.value) {
|
||||
IndeterminateState.INDETERMINATE,
|
||||
IndeterminateState.UNCHECKED -> true
|
||||
IndeterminateState.CHECKED -> false
|
||||
}
|
||||
itemsProcess.forEach { it.isHidden.value = desiredState }
|
||||
isHiddenState.value = currentState
|
||||
}
|
||||
|
||||
fun toggleExpansion() {
|
||||
if (items.size <= 1) return
|
||||
isExpanded.toggle()
|
||||
}
|
||||
|
||||
override fun contentSameAs(other: HideRvItem): Boolean = items.all { other.items.contains(it) }
|
||||
override fun itemSameAs(other: HideRvItem): Boolean = item.info == other.item.info
|
||||
|
||||
}
|
||||
|
||||
class HideProcessRvItem(
|
||||
val packageName: String,
|
||||
val process: String,
|
||||
isHidden: Boolean
|
||||
) : ComparableRvItem<HideProcessRvItem>() {
|
||||
|
||||
override val layoutRes: Int = R.layout.item_hide_process
|
||||
|
||||
val isHidden = KObservableField(isHidden)
|
||||
|
||||
private val rxBus: RxBus by inject()
|
||||
|
||||
init {
|
||||
this.isHidden.addOnPropertyChangedCallback {
|
||||
rxBus.post(HideProcessEvent(this@HideProcessRvItem))
|
||||
}
|
||||
}
|
||||
|
||||
fun toggle() = isHidden.toggle()
|
||||
|
||||
override fun contentSameAs(other: HideProcessRvItem): Boolean = itemSameAs(other)
|
||||
override fun itemSameAs(other: HideProcessRvItem): Boolean =
|
||||
packageName == other.packageName && process == other.process
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package com.topjohnwu.magisk.model.entity.state
|
||||
|
||||
enum class IndeterminateState {
|
||||
INDETERMINATE, CHECKED, UNCHECKED
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package com.topjohnwu.magisk.model.events
|
||||
|
||||
import com.skoumal.teanity.rxbus.RxBus
|
||||
import com.topjohnwu.magisk.model.entity.recycler.HideProcessRvItem
|
||||
|
||||
class HideProcessEvent(val item: HideProcessRvItem) : RxBus.Event
|
116
app/src/main/java/com/topjohnwu/magisk/ui/hide/HideViewModel.kt
Normal file
116
app/src/main/java/com/topjohnwu/magisk/ui/hide/HideViewModel.kt
Normal file
@ -0,0 +1,116 @@
|
||||
package com.topjohnwu.magisk.ui.hide
|
||||
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageManager
|
||||
import com.skoumal.teanity.databinding.ComparableRvItem
|
||||
import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
|
||||
import com.skoumal.teanity.extensions.subscribeK
|
||||
import com.skoumal.teanity.rxbus.RxBus
|
||||
import com.skoumal.teanity.util.DiffObservableList
|
||||
import com.skoumal.teanity.util.KObservableField
|
||||
import com.topjohnwu.magisk.App
|
||||
import com.topjohnwu.magisk.BR
|
||||
import com.topjohnwu.magisk.model.entity.HideAppInfo
|
||||
import com.topjohnwu.magisk.model.entity.HideTarget
|
||||
import com.topjohnwu.magisk.model.entity.recycler.HideProcessRvItem
|
||||
import com.topjohnwu.magisk.model.entity.recycler.HideRvItem
|
||||
import com.topjohnwu.magisk.model.events.HideProcessEvent
|
||||
import com.topjohnwu.magisk.ui.base.MagiskViewModel
|
||||
import com.topjohnwu.magisk.utils.Utils
|
||||
import com.topjohnwu.magisk.utils.toSingle
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import io.reactivex.Single
|
||||
import me.tatarka.bindingcollectionadapter2.OnItemBind
|
||||
import timber.log.Timber
|
||||
|
||||
class HideViewModel(
|
||||
private val packageManager: PackageManager,
|
||||
rxBus: RxBus
|
||||
) : MagiskViewModel() {
|
||||
|
||||
val query = KObservableField("")
|
||||
val isShowSystem = KObservableField(false)
|
||||
|
||||
private val allItems = DiffObservableList(ComparableRvItem.callback)
|
||||
val items = DiffObservableList(ComparableRvItem.callback)
|
||||
val itemBinding = OnItemBind<ComparableRvItem<*>> { itemBinding, _, item ->
|
||||
item.bind(itemBinding)
|
||||
itemBinding.bindExtra(BR.viewModel, this@HideViewModel)
|
||||
}
|
||||
|
||||
init {
|
||||
rxBus.register<HideProcessEvent>()
|
||||
.subscribeK { toggleItem(it.item) }
|
||||
.add()
|
||||
|
||||
isShowSystem.addOnPropertyChangedCallback { query() }
|
||||
query.addOnPropertyChangedCallback { query() }
|
||||
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun refresh() {
|
||||
// fetching this for every item is nonsensical, so we add .cache() so the response is all
|
||||
// the same for every single mapped item, it only actually executes the whole thing the
|
||||
// first time around.
|
||||
val hideTargets = Shell.su("magiskhide --ls").toSingle()
|
||||
.map { it.exec().out }
|
||||
.flattenAsFlowable { it }
|
||||
.map { HideTarget(it) }
|
||||
.toList()
|
||||
.cache()
|
||||
|
||||
Single.fromCallable { packageManager.getInstalledApplications(0) }
|
||||
.flattenAsFlowable { it }
|
||||
.filter { it.enabled && !blacklist.contains(it.packageName) }
|
||||
.map {
|
||||
val label = Utils.getAppLabel(it, packageManager)
|
||||
val icon = it.loadIcon(packageManager)
|
||||
HideAppInfo(it, label, icon)
|
||||
}
|
||||
.filter { it.processes.isNotEmpty() }
|
||||
.map { HideRvItem(it, hideTargets.blockingGet()) }
|
||||
.toList()
|
||||
.map { it.sortBy { it.item.info.name }; it }
|
||||
.applyViewModel(this)
|
||||
.subscribeK(onError = Timber::e) {
|
||||
allItems.update(it)
|
||||
query()
|
||||
}
|
||||
.add()
|
||||
}
|
||||
|
||||
private fun query(showSystem: Boolean = isShowSystem.value, query: String = this.query.value) {
|
||||
allItems.toSingle()
|
||||
.map { it.filterIsInstance<HideRvItem>() }
|
||||
.flattenAsFlowable { it }
|
||||
.filter { it.item.name.contains(query) || it.item.processes.any { it.contains(query) } }
|
||||
.filter { if (showSystem) true else it.item.info.flags and ApplicationInfo.FLAG_SYSTEM == 0 }
|
||||
.toList()
|
||||
.subscribeK { items.update(it) }
|
||||
.add()
|
||||
}
|
||||
|
||||
private fun toggleItem(item: HideProcessRvItem) {
|
||||
val state = if (item.isHidden.value) "add" else "rm"
|
||||
"magiskhide --%s %s %s".format(state, item.packageName, item.process)
|
||||
.let { Shell.su(it) }
|
||||
.toSingle()
|
||||
.map { it.submit() }
|
||||
.subscribeK()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val blacklist = listOf(
|
||||
App.self.packageName,
|
||||
"android",
|
||||
"com.android.chrome",
|
||||
"com.chrome.beta",
|
||||
"com.chrome.dev",
|
||||
"com.chrome.canary",
|
||||
"com.android.webview",
|
||||
"com.google.android.webview"
|
||||
)
|
||||
}
|
||||
|
||||
}
|
@ -1,100 +0,0 @@
|
||||
package com.topjohnwu.magisk.ui.hide;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.SearchView;
|
||||
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.model.adapters.ApplicationAdapter;
|
||||
import com.topjohnwu.magisk.ui.base.BaseFragment;
|
||||
import com.topjohnwu.magisk.utils.Event;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
import butterknife.BindView;
|
||||
|
||||
public class MagiskHideFragment extends BaseFragment {
|
||||
|
||||
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
|
||||
@BindView(R.id.recyclerView) RecyclerView recyclerView;
|
||||
|
||||
private SearchView search;
|
||||
private ApplicationAdapter adapter;
|
||||
private SearchView.OnQueryTextListener searchListener;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_magisk_hide, container, false);
|
||||
unbinder = new MagiskHideFragment_ViewBinding(this, view);
|
||||
|
||||
adapter = new ApplicationAdapter(requireActivity());
|
||||
recyclerView.setAdapter(adapter);
|
||||
|
||||
mSwipeRefreshLayout.setRefreshing(true);
|
||||
mSwipeRefreshLayout.setOnRefreshListener(adapter::refresh);
|
||||
|
||||
searchListener = new SearchView.OnQueryTextListener() {
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String query) {
|
||||
adapter.filter(query);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String newText) {
|
||||
adapter.filter(newText);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
requireActivity().setTitle(R.string.magiskhide);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.menu_magiskhide, menu);
|
||||
search = (SearchView) menu.findItem(R.id.app_search).getActionView();
|
||||
search.setOnQueryTextListener(searchListener);
|
||||
menu.findItem(R.id.show_system).setChecked(Config.get(Config.Key.SHOW_SYSTEM_APP));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == R.id.show_system) {
|
||||
boolean showSystem = !item.isChecked();
|
||||
item.setChecked(showSystem);
|
||||
Config.set(Config.Key.SHOW_SYSTEM_APP, showSystem);
|
||||
adapter.setShowSystem(showSystem);
|
||||
adapter.filter(search.getQuery().toString());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getListeningEvents() {
|
||||
return new int[] {Event.MAGISK_HIDE_DONE};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvent(int event) {
|
||||
mSwipeRefreshLayout.setRefreshing(false);
|
||||
adapter.filter(search.getQuery().toString());
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
package com.topjohnwu.magisk.ui.hide
|
||||
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.widget.SearchView
|
||||
import com.topjohnwu.magisk.Config
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.databinding.FragmentMagiskHideBinding
|
||||
import com.topjohnwu.magisk.ui.base.MagiskFragment
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
|
||||
class MagiskHideFragment : MagiskFragment<HideViewModel, FragmentMagiskHideBinding>(),
|
||||
SearchView.OnQueryTextListener {
|
||||
|
||||
override val layoutRes: Int = R.layout.fragment_magisk_hide
|
||||
override val viewModel: HideViewModel by viewModel()
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
setHasOptionsMenu(true)
|
||||
requireActivity().setTitle(R.string.magiskhide)
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.menu_magiskhide, menu)
|
||||
menu.apply {
|
||||
(findItem(R.id.app_search).actionView as? SearchView)
|
||||
?.setOnQueryTextListener(this@MagiskHideFragment)
|
||||
|
||||
val showSystem = Config.get<Boolean>(Config.Key.SHOW_SYSTEM_APP)
|
||||
|
||||
findItem(R.id.show_system).isChecked = showSystem
|
||||
viewModel.isShowSystem.value = showSystem
|
||||
}
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
if (item.itemId == R.id.show_system) {
|
||||
val showSystem = !item.isChecked
|
||||
item.isChecked = showSystem
|
||||
Config.set(Config.Key.SHOW_SYSTEM_APP, showSystem)
|
||||
viewModel.isShowSystem.value = showSystem
|
||||
//adapter!!.setShowSystem(showSystem)
|
||||
//adapter!!.filter(search!!.query.toString())
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onQueryTextSubmit(query: String?): Boolean {
|
||||
viewModel.query.value = query.orEmpty()
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onQueryTextChange(query: String?): Boolean {
|
||||
viewModel.query.value = query.orEmpty()
|
||||
return false
|
||||
}
|
||||
|
||||
/*override fun onEvent(event: Int) {
|
||||
//mSwipeRefreshLayout!!.isRefreshing = false
|
||||
adapter!!.filter(search!!.query.toString())
|
||||
}*/
|
||||
}
|
@ -8,6 +8,8 @@ import androidx.appcompat.widget.Toolbar
|
||||
import androidx.databinding.BindingAdapter
|
||||
import androidx.drawerlayout.widget.DrawerLayout
|
||||
import com.google.android.material.navigation.NavigationView
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.model.entity.state.IndeterminateState
|
||||
|
||||
|
||||
@BindingAdapter("onNavigationClick")
|
||||
@ -35,3 +37,23 @@ fun setImageResource(view: AppCompatImageView, @DrawableRes resId: Int) {
|
||||
fun setTint(view: AppCompatImageView, @ColorInt tint: Int) {
|
||||
view.setColorFilter(tint)
|
||||
}
|
||||
|
||||
@BindingAdapter("isChecked")
|
||||
fun setChecked(view: AppCompatImageView, isChecked: Boolean) {
|
||||
val state = when (isChecked) {
|
||||
true -> IndeterminateState.CHECKED
|
||||
else -> IndeterminateState.UNCHECKED
|
||||
}
|
||||
setChecked(view, state)
|
||||
}
|
||||
|
||||
@BindingAdapter("isChecked")
|
||||
fun setChecked(view: AppCompatImageView, isChecked: IndeterminateState) {
|
||||
view.setImageResource(
|
||||
when (isChecked) {
|
||||
IndeterminateState.INDETERMINATE -> R.drawable.ic_indeterminate
|
||||
IndeterminateState.CHECKED -> R.drawable.ic_checked
|
||||
IndeterminateState.UNCHECKED -> R.drawable.ic_unchecked
|
||||
}
|
||||
)
|
||||
}
|
||||
|
50
app/src/main/java/com/topjohnwu/magisk/utils/XAndroid.kt
Normal file
50
app/src/main/java/com/topjohnwu/magisk/utils/XAndroid.kt
Normal file
@ -0,0 +1,50 @@
|
||||
package com.topjohnwu.magisk.utils
|
||||
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.ComponentInfo
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.pm.PackageManager.*
|
||||
|
||||
val PackageInfo.processes
|
||||
get() = activities?.processNames.orEmpty() +
|
||||
services?.processNames.orEmpty() +
|
||||
receivers?.processNames.orEmpty() +
|
||||
providers?.processNames.orEmpty()
|
||||
|
||||
val Array<out ComponentInfo>.processNames get() = mapNotNull { it.processName }
|
||||
|
||||
val ApplicationInfo.packageInfo: PackageInfo?
|
||||
get() {
|
||||
val pm: PackageManager by inject()
|
||||
|
||||
return try {
|
||||
val request = GET_ACTIVITIES or
|
||||
GET_SERVICES or
|
||||
GET_RECEIVERS or
|
||||
GET_PROVIDERS
|
||||
pm.getPackageInfo(packageName, request)
|
||||
} catch (e1: Exception) {
|
||||
try {
|
||||
pm.activities(packageName).apply {
|
||||
services = pm.services(packageName)
|
||||
receivers = pm.receivers(packageName)
|
||||
providers = pm.providers(packageName)
|
||||
}
|
||||
} catch (e2: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun PackageManager.activities(packageName: String) =
|
||||
getPackageInfo(packageName, GET_ACTIVITIES)
|
||||
|
||||
fun PackageManager.services(packageName: String) =
|
||||
getPackageInfo(packageName, GET_SERVICES).services
|
||||
|
||||
fun PackageManager.receivers(packageName: String) =
|
||||
getPackageInfo(packageName, GET_RECEIVERS).receivers
|
||||
|
||||
fun PackageManager.providers(packageName: String) =
|
||||
getPackageInfo(packageName, GET_PROVIDERS).providers
|
20
app/src/main/java/com/topjohnwu/magisk/utils/XKoin.kt
Normal file
20
app/src/main/java/com/topjohnwu/magisk/utils/XKoin.kt
Normal file
@ -0,0 +1,20 @@
|
||||
package com.topjohnwu.magisk.utils
|
||||
|
||||
import org.koin.core.context.GlobalContext
|
||||
import org.koin.core.parameter.ParametersDefinition
|
||||
import org.koin.core.qualifier.Qualifier
|
||||
import org.koin.core.scope.Scope
|
||||
|
||||
fun getKoin() = GlobalContext.get().koin
|
||||
|
||||
inline fun <reified T : Any> inject(
|
||||
qualifier: Qualifier? = null,
|
||||
scope: Scope? = null,
|
||||
noinline parameters: ParametersDefinition? = null
|
||||
) = lazy { get<T>(qualifier, scope, parameters) }
|
||||
|
||||
inline fun <reified T : Any> get(
|
||||
qualifier: Qualifier? = null,
|
||||
scope: Scope? = null,
|
||||
noinline parameters: ParametersDefinition? = null
|
||||
): T = getKoin().get(qualifier, scope, parameters)
|
5
app/src/main/java/com/topjohnwu/magisk/utils/XRx.kt
Normal file
5
app/src/main/java/com/topjohnwu/magisk/utils/XRx.kt
Normal file
@ -0,0 +1,5 @@
|
||||
package com.topjohnwu.magisk.utils
|
||||
|
||||
import io.reactivex.Single
|
||||
|
||||
fun <T : Any> T.toSingle() = Single.just(this)
|
11
app/src/main/res/drawable/ic_checked.xml
Normal file
11
app/src/main/res/drawable/ic_checked.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M20,12A8,8 0 0,1 12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4C12.76,4 13.5,4.11 14.2,4.31L15.77,2.74C14.61,2.26 13.34,2 12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12M7.91,10.08L6.5,11.5L11,16L21,6L19.59,4.58L11,13.17L7.91,10.08Z" />
|
||||
</vector>
|
10
app/src/main/res/drawable/ic_indeterminate.xml
Normal file
10
app/src/main/res/drawable/ic_indeterminate.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M7,13H17V11H7" />
|
||||
</vector>
|
10
app/src/main/res/drawable/ic_unchecked.xml
Normal file
10
app/src/main/res/drawable/ic_unchecked.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4A8,8 0 0,1 20,12A8,8 0 0,1 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z" />
|
||||
</vector>
|
@ -1,25 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout 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"
|
||||
android:id="@+id/swipeRefreshLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:orientation="vertical">
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<LinearLayout
|
||||
<data>
|
||||
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="com.topjohnwu.magisk.ui.hide.HideViewModel" />
|
||||
|
||||
</data>
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
app:onRefreshListener="@{() -> viewModel.refresh()}"
|
||||
app:refreshing="@{viewModel.loading}">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerView"
|
||||
dividerColor="@{@android:color/transparent}"
|
||||
dividerSize="@{@dimen/margin_generic}"
|
||||
itemBinding="@{viewModel.itemBinding}"
|
||||
items="@{viewModel.items}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:dividerHeight="@dimen/card_divider_space"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/margin_generic"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:listitem="@layout/item_hide_app" />
|
||||
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</layout>
|
||||
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
124
app/src/main/res/layout/item_hide_app.xml
Normal file
124
app/src/main/res/layout/item_hide_app.xml
Normal file
@ -0,0 +1,124 @@
|
||||
<?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.HideRvItem" />
|
||||
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="com.topjohnwu.magisk.ui.hide.HideViewModel" />
|
||||
|
||||
</data>
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
style="@style/Widget.Card"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:minHeight="?android:attr/listPreferredItemHeight"
|
||||
android:onClick="@{() -> item.toggleExpansion()}"
|
||||
app:cardElevation="@dimen/card_elevation">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="@dimen/margin_generic_half">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/hide_app_icon"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:gravity="end"
|
||||
android:src="@{item.item.icon}"
|
||||
app:layout_constraintBottom_toTopOf="@+id/hide_app_processes"
|
||||
app:layout_constraintEnd_toStartOf="@+id/hide_app_name"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@drawable/ic_magisk" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/hide_app_name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/margin_generic"
|
||||
android:layout_marginEnd="@dimen/margin_generic"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:text="@{item.item.name}"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textIsSelectable="false"
|
||||
app:layout_constraintBottom_toTopOf="@+id/hide_app_package"
|
||||
app:layout_constraintEnd_toStartOf="@+id/hide_app_checkbox"
|
||||
app:layout_constraintStart_toEndOf="@+id/hide_app_icon"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
tools:text="Magisk" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/hide_app_package"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:text="@{item.packageName}"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textColor="@android:color/tertiary_text_dark"
|
||||
android:textIsSelectable="false"
|
||||
app:layout_constraintBottom_toTopOf="@+id/hide_app_processes"
|
||||
app:layout_constraintEnd_toStartOf="@id/hide_app_arrow"
|
||||
app:layout_constraintStart_toStartOf="@+id/hide_app_name"
|
||||
app:layout_constraintTop_toBottomOf="@+id/hide_app_name"
|
||||
tools:text="com.topjohnwu.magisk" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/hide_app_arrow"
|
||||
gone="@{item.items.size == 1}"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginStart="5dp"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:background="@android:color/transparent"
|
||||
android:rotation="@{item.isExpanded() ? 180 : 0}"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/hide_app_package"
|
||||
app:layout_constraintEnd_toEndOf="@+id/hide_app_name"
|
||||
app:layout_constraintStart_toEndOf="@+id/hide_app_package"
|
||||
app:layout_constraintTop_toTopOf="@+id/hide_app_package"
|
||||
app:srcCompat="@drawable/ic_arrow"
|
||||
app:tint="?attr/imageColorTint" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/hide_app_checkbox"
|
||||
style="@style/Widget.Icon"
|
||||
isChecked="@{item.isHiddenState}"
|
||||
android:onClick="@{() -> item.toggle()}"
|
||||
app:layout_constraintBottom_toTopOf="@+id/hide_app_processes"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/hide_app_name"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:tint="?attr/imageColorTint"
|
||||
tools:src="@drawable/ic_checked" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/hide_app_processes"
|
||||
gone="@{!item.isExpanded}"
|
||||
itemBinding="@{viewModel.itemBinding}"
|
||||
items="@{item.items}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="@dimen/margin_generic_half"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
tools:listitem="@layout/item_hide_process" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
</layout>
|
51
app/src/main/res/layout/item_hide_process.xml
Normal file
51
app/src/main/res/layout/item_hide_process.xml
Normal file
@ -0,0 +1,51 @@
|
||||
<?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.HideProcessRvItem" />
|
||||
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="com.topjohnwu.magisk.ui.hide.HideViewModel" />
|
||||
|
||||
</data>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:onClick="@{() -> item.toggle()}">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="@dimen/margin_generic"
|
||||
android:ellipsize="marquee"
|
||||
android:singleLine="true"
|
||||
android:text="@{item.process}"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textColor="@android:color/tertiary_text_dark"
|
||||
android:textIsSelectable="false"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/hide_process_icon"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="com.topjohnwu.magisk.process" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/hide_process_icon"
|
||||
style="@style/Widget.Icon"
|
||||
isChecked="@{item.isHidden}"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:tint="?attr/imageColorTint"
|
||||
tools:src="@drawable/ic_checked" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</layout>
|
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,6 @@
|
||||
#Tue Mar 26 00:03:20 EDT 2019
|
||||
#Fri Apr 19 09:51:32 CEST 2019
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.3-rc-2-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.3.1-all.zip
|
||||
|
Loading…
Reference in New Issue
Block a user