Commit e81f00ef authored by Viktor De Pasquale's avatar Viktor De Pasquale

Updated Hide screen with new arch

parent cda14af2
package com.topjohnwu.magisk.di package com.topjohnwu.magisk.di
import com.topjohnwu.magisk.ui.MainViewModel 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.home.HomeViewModel
import com.topjohnwu.magisk.ui.superuser.SuperuserViewModel import com.topjohnwu.magisk.ui.superuser.SuperuserViewModel
import org.koin.androidx.viewmodel.dsl.viewModel import org.koin.androidx.viewmodel.dsl.viewModel
...@@ -11,4 +12,5 @@ val viewModelModules = module { ...@@ -11,4 +12,5 @@ val viewModelModules = module {
viewModel { MainViewModel() } viewModel { MainViewModel() }
viewModel { HomeViewModel(get(), get()) } viewModel { HomeViewModel(get(), get()) }
viewModel { SuperuserViewModel(get(), get(), get()) } viewModel { SuperuserViewModel(get(), get(), get()) }
viewModel { HideViewModel(get(), get()) }
} }
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)
}
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 }
}
\ No newline at end of file
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
}
\ No newline at end of file
package com.topjohnwu.magisk.model.entity.state
enum class IndeterminateState {
INDETERMINATE, CHECKED, UNCHECKED
}
\ No newline at end of file
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
\ No newline at end of file
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"
)
}
}
\ No newline at end of file
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());
}
}
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 ...@@ -8,6 +8,8 @@ import androidx.appcompat.widget.Toolbar
import androidx.databinding.BindingAdapter import androidx.databinding.BindingAdapter
import androidx.drawerlayout.widget.DrawerLayout import androidx.drawerlayout.widget.DrawerLayout
import com.google.android.material.navigation.NavigationView import com.google.android.material.navigation.NavigationView
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.model.entity.state.IndeterminateState
@BindingAdapter("onNavigationClick") @BindingAdapter("onNavigationClick")
...@@ -35,3 +37,23 @@ fun setImageResource(view: AppCompatImageView, @DrawableRes resId: Int) { ...@@ -35,3 +37,23 @@ fun setImageResource(view: AppCompatImageView, @DrawableRes resId: Int) {
fun setTint(view: AppCompatImageView, @ColorInt tint: Int) { fun setTint(view: AppCompatImageView, @ColorInt tint: Int) {
view.setColorFilter(tint) 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
}
)
}
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
\ No newline at end of file
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)
\ No newline at end of file
package com.topjohnwu.magisk.utils
import io.reactivex.Single
fun <T : Any> T.toSingle() = Single.just(this)
\ No newline at end of file
<?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>
\ No newline at end of file
<?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>
\ No newline at end of file
<?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>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?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" xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/swipeRefreshLayout" xmlns:tools="http://schemas.android.com/tools">
android:layout_width="match_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<LinearLayout <data>
<variable
name="viewModel"
type="com.topjohnwu.magisk.ui.hide.HideViewModel" />
</data>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical"> app:onRefreshListener="@{() -> viewModel.refresh()}"
app:refreshing="@{viewModel.loading}">
<androidx.recyclerview.widget.RecyclerView <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_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:clipToPadding="false" android:clipToPadding="false"
android:dividerHeight="@dimen/card_divider_space" android:orientation="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" /> 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>
\ No newline at end of file
<?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>
<?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>
#Tue Mar 26 00:03:20 EDT 2019 #Fri Apr 19 09:51:32 CEST 2019
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists 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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment