Updated policy (apps) layout to be more compact

Added pinch in to increase list span count / out to decrease
  The setting will be remembered across the whole app (every list that uses Staggered Grid)
Updated indication of whether the policy has root access enabled permitted or not
  Displays crossed out app logo if not permitted
This commit is contained in:
Viktor De Pasquale 2020-01-04 16:07:53 +01:00
parent eb929160b3
commit 8737be2623
15 changed files with 304 additions and 135 deletions

View File

@ -55,6 +55,7 @@ object Config : PreferenceModel, DBConfig {
const val SAFETY = "safety_notice" const val SAFETY = "safety_notice"
const val THEME_ORDINAL = "theme_ordinal" const val THEME_ORDINAL = "theme_ordinal"
const val BOOT_ID = "boot_id" const val BOOT_ID = "boot_id"
const val LIST_SPAN_COUNT = "list_span_count"
// system state // system state
const val MAGISKHIDE = "magiskhide" const val MAGISKHIDE = "magiskhide"
@ -109,8 +110,7 @@ object Config : PreferenceModel, DBConfig {
Value.CANARY_DEBUG_CHANNEL Value.CANARY_DEBUG_CHANNEL
else else
Value.CANARY_CHANNEL Value.CANARY_CHANNEL
} } else Value.DEFAULT_CHANNEL
else Value.DEFAULT_CHANNEL
var bootId by preference(Key.BOOT_ID, "") var bootId by preference(Key.BOOT_ID, "")
@ -137,6 +137,7 @@ object Config : PreferenceModel, DBConfig {
@JvmStatic @JvmStatic
var coreOnly by preference(Key.COREONLY, false) var coreOnly by preference(Key.COREONLY, false)
var showSystemApp by preference(Key.SHOW_SYSTEM_APP, false) var showSystemApp by preference(Key.SHOW_SYSTEM_APP, false)
var listSpanCount by preference(Key.LIST_SPAN_COUNT, 2)
var customChannelUrl by preference(Key.CUSTOM_CHANNEL, "") var customChannelUrl by preference(Key.CUSTOM_CHANNEL, "")
var locale by preference(Key.LOCALE, "") var locale by preference(Key.LOCALE, "")
@ -149,7 +150,8 @@ object Config : PreferenceModel, DBConfig {
var keyStoreRaw by dbStrings(Key.KEYSTORE, "", true) var keyStoreRaw by dbStrings(Key.KEYSTORE, "", true)
// Always return a path in external storage where we can write // Always return a path in external storage where we can write
val downloadDirectory get() = val downloadDirectory
get() =
Utils.ensureDownloadPath(downloadPath) ?: get<Context>().getExternalFilesDir(null)!! Utils.ensureDownloadPath(downloadPath) ?: get<Context>().getExternalFilesDir(null)!!
private const val SU_FINGERPRINT = "su_fingerprint" private const val SU_FINGERPRINT = "su_fingerprint"

View File

@ -1,8 +1,6 @@
package com.topjohnwu.magisk.model.entity.recycler package com.topjohnwu.magisk.model.entity.recycler
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.view.View
import android.view.ViewGroup
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.ComparableRvItem import com.topjohnwu.magisk.databinding.ComparableRvItem
import com.topjohnwu.magisk.extensions.addOnPropertyChangedCallback import com.topjohnwu.magisk.extensions.addOnPropertyChangedCallback
@ -14,8 +12,6 @@ import com.topjohnwu.magisk.model.events.PolicyUpdateEvent
import com.topjohnwu.magisk.redesign.superuser.SuperuserViewModel import com.topjohnwu.magisk.redesign.superuser.SuperuserViewModel
import com.topjohnwu.magisk.utils.KObservableField import com.topjohnwu.magisk.utils.KObservableField
import com.topjohnwu.magisk.utils.RxBus import com.topjohnwu.magisk.utils.RxBus
import com.topjohnwu.magisk.utils.rotationTo
import com.topjohnwu.magisk.utils.setRevealed
class PolicyRvItem(val item: MagiskPolicy, val icon: Drawable) : ComparableRvItem<PolicyRvItem>() { class PolicyRvItem(val item: MagiskPolicy, val icon: Drawable) : ComparableRvItem<PolicyRvItem>() {
@ -73,18 +69,15 @@ class PolicyItem(val item: MagiskPolicy, val icon: Drawable) : ComparableRvItem<
fun toggle(viewModel: SuperuserViewModel) { fun toggle(viewModel: SuperuserViewModel) {
if (isExpanded.value) { if (isExpanded.value) {
toggle()
return return
} }
isEnabled.toggle() isEnabled.toggle()
viewModel.togglePolicy(this, isEnabled.value) viewModel.togglePolicy(this, isEnabled.value)
} }
fun toggle(view: View) { fun toggle() {
isExpanded.toggle() isExpanded.toggle()
view.rotationTo(if (isExpanded.value) 225 else 180)
(view.parent as ViewGroup)
.findViewById<View>(R.id.policy_expand_container)
.setRevealed(isExpanded.value)
} }
fun toggleNotify(viewModel: SuperuserViewModel) { fun toggleNotify(viewModel: SuperuserViewModel) {

View File

@ -19,6 +19,7 @@ import com.topjohnwu.magisk.redesign.compat.CompatFragment
import com.topjohnwu.magisk.redesign.compat.hideKeyboard import com.topjohnwu.magisk.redesign.compat.hideKeyboard
import com.topjohnwu.magisk.utils.EndlessRecyclerScrollListener import com.topjohnwu.magisk.utils.EndlessRecyclerScrollListener
import com.topjohnwu.magisk.utils.MotionRevealHelper import com.topjohnwu.magisk.utils.MotionRevealHelper
import com.topjohnwu.magisk.utils.PinchZoomTouchListener
import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
class ModuleFragment : CompatFragment<ModuleViewModel, FragmentModuleMd2Binding>(), class ModuleFragment : CompatFragment<ModuleViewModel, FragmentModuleMd2Binding>(),
@ -67,13 +68,18 @@ class ModuleFragment : CompatFragment<ModuleViewModel, FragmentModuleMd2Binding>
if (newState != RecyclerView.SCROLL_STATE_IDLE) hideKeyboard() if (newState != RecyclerView.SCROLL_STATE_IDLE) hideKeyboard()
} }
}) })
PinchZoomTouchListener.attachTo(binding.moduleFilterInclude.moduleFilterList)
PinchZoomTouchListener.attachTo(binding.moduleList)
} }
override fun onDestroyView() { override fun onDestroyView() {
listeners.forEach { listeners.forEach {
binding.moduleRemote.removeOnScrollListener(it) binding.moduleList.removeOnScrollListener(it)
binding.moduleFilterInclude.moduleFilterList.removeOnScrollListener(it) binding.moduleFilterInclude.moduleFilterList.removeOnScrollListener(it)
} }
PinchZoomTouchListener.clear(binding.moduleList)
PinchZoomTouchListener.clear(binding.moduleFilterInclude.moduleFilterList)
super.onDestroyView() super.onDestroyView()
} }
@ -101,14 +107,14 @@ class ModuleFragment : CompatFragment<ModuleViewModel, FragmentModuleMd2Binding>
// --- // ---
override fun onReselected() { override fun onReselected() {
binding.moduleRemote binding.moduleList
.takeIf { .takeIf {
(it.layoutManager as? StaggeredGridLayoutManager)?.let { (it.layoutManager as? StaggeredGridLayoutManager)?.let {
it.findFirstVisibleItemPositions(IntArray(it.spanCount)).min() it.findFirstVisibleItemPositions(IntArray(it.spanCount)).min()
} ?: 0 > 10 } ?: 0 > 10
} }
?.also { it.scrollToPosition(10) } ?.also { it.scrollToPosition(10) }
.let { binding.moduleRemote } .let { binding.moduleList }
.also { it.post { it.smoothScrollToPosition(0) } } .also { it.post { it.smoothScrollToPosition(0) } }
} }
@ -117,11 +123,11 @@ class ModuleFragment : CompatFragment<ModuleViewModel, FragmentModuleMd2Binding>
override fun onPreBind(binding: FragmentModuleMd2Binding) = Unit override fun onPreBind(binding: FragmentModuleMd2Binding) = Unit
private fun setEndlessScroller() { private fun setEndlessScroller() {
val lama = binding.moduleRemote.layoutManager ?: return val lama = binding.moduleList.layoutManager ?: return
lama.isAutoMeasureEnabled = false lama.isAutoMeasureEnabled = false
val listener = EndlessRecyclerScrollListener(lama, viewModel::loadRemote) val listener = EndlessRecyclerScrollListener(lama, viewModel::loadRemote)
binding.moduleRemote.addOnScrollListener(listener) binding.moduleList.addOnScrollListener(listener)
listeners.add(listener) listeners.add(listener)
} }

View File

@ -1,9 +1,12 @@
package com.topjohnwu.magisk.redesign.settings package com.topjohnwu.magisk.redesign.settings
import android.graphics.Insets import android.graphics.Insets
import android.os.Bundle
import android.view.View
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.FragmentSettingsMd2Binding import com.topjohnwu.magisk.databinding.FragmentSettingsMd2Binding
import com.topjohnwu.magisk.redesign.compat.CompatFragment import com.topjohnwu.magisk.redesign.compat.CompatFragment
import com.topjohnwu.magisk.utils.PinchZoomTouchListener
import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
class SettingsFragment : CompatFragment<SettingsViewModel, FragmentSettingsMd2Binding>() { class SettingsFragment : CompatFragment<SettingsViewModel, FragmentSettingsMd2Binding>() {
@ -19,6 +22,16 @@ class SettingsFragment : CompatFragment<SettingsViewModel, FragmentSettingsMd2Bi
activity.title = resources.getString(R.string.section_settings) activity.title = resources.getString(R.string.section_settings)
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
PinchZoomTouchListener.attachTo(binding.settingsList)
}
override fun onDestroyView() {
PinchZoomTouchListener.clear(binding.settingsList)
super.onDestroyView()
}
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
viewModel.items.forEach { it.refresh() } viewModel.items.forEach { it.refresh() }

View File

@ -1,13 +1,16 @@
package com.topjohnwu.magisk.redesign.superuser package com.topjohnwu.magisk.redesign.superuser
import android.graphics.Insets import android.graphics.Insets
import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuInflater import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.FragmentSuperuserMd2Binding import com.topjohnwu.magisk.databinding.FragmentSuperuserMd2Binding
import com.topjohnwu.magisk.model.navigation.Navigation import com.topjohnwu.magisk.model.navigation.Navigation
import com.topjohnwu.magisk.redesign.compat.CompatFragment import com.topjohnwu.magisk.redesign.compat.CompatFragment
import com.topjohnwu.magisk.utils.PinchZoomTouchListener
import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
class SuperuserFragment : CompatFragment<SuperuserViewModel, FragmentSuperuserMd2Binding>() { class SuperuserFragment : CompatFragment<SuperuserViewModel, FragmentSuperuserMd2Binding>() {
@ -23,6 +26,16 @@ class SuperuserFragment : CompatFragment<SuperuserViewModel, FragmentSuperuserMd
setHasOptionsMenu(true) setHasOptionsMenu(true)
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
PinchZoomTouchListener.attachTo(binding.superuserList)
}
override fun onDestroyView() {
PinchZoomTouchListener.clear(binding.superuserList)
super.onDestroyView()
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.menu_superuser_md2, menu) inflater.inflate(R.menu.menu_superuser_md2, menu)
} }

View File

@ -449,3 +449,11 @@ fun View.setRotationNotAnimated(rotation: Int) {
fun TextView.setTextSafe(text: Int) { fun TextView.setTextSafe(text: Int) {
if (text == 0) this.text = null else setText(text) if (text == 0) this.text = null else setText(text)
} }
@BindingAdapter("android:onLongClick")
fun View.setOnLongClickListenerBinding(listener: () -> Unit) {
setOnLongClickListener {
listener()
true
}
}

View File

@ -0,0 +1,24 @@
package com.topjohnwu.magisk.utils
import android.view.ScaleGestureDetector
abstract class PinchGestureCallback : ScaleGestureDetector.SimpleOnScaleGestureListener() {
private var startFactor: Float = 1f
override fun onScaleBegin(detector: ScaleGestureDetector?): Boolean {
startFactor = detector?.scaleFactor ?: 1f
return super.onScaleBegin(detector)
}
override fun onScaleEnd(detector: ScaleGestureDetector?) {
val endFactor = detector?.scaleFactor ?: 1f
if (endFactor > startFactor) onZoom()
else if (endFactor < startFactor) onPinch()
}
abstract fun onPinch()
abstract fun onZoom()
}

View File

@ -0,0 +1,66 @@
package com.topjohnwu.magisk.utils
import android.annotation.SuppressLint
import android.view.MotionEvent
import android.view.ScaleGestureDetector
import android.view.View
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import androidx.transition.TransitionManager
import com.topjohnwu.magisk.Config
import kotlin.math.max
import kotlin.math.min
class PinchZoomTouchListener private constructor(
private val view: RecyclerView,
private val max: Int = 3,
private val min: Int = 1
) : View.OnTouchListener {
private val layoutManager
get() = view.layoutManager
private val pinchListener = object : PinchGestureCallback() {
override fun onPinch() = updateSpanCount(Config.listSpanCount + 1)
override fun onZoom() = updateSpanCount(Config.listSpanCount - 1)
}
private val gestureDetector by lazy { ScaleGestureDetector(view.context, pinchListener) }
init {
updateSpanCount(Config.listSpanCount, false)
}
@SuppressLint("ClickableViewAccessibility")
override fun onTouch(v: View?, event: MotionEvent?): Boolean {
gestureDetector.onTouchEvent(event)
return false
}
private fun updateSpanCount(count: Int, animate: Boolean = true) {
if (animate) {
TransitionManager.beginDelayedTransition(view)
}
val boundCount = max(min, min(max, count))
when (val l = layoutManager) {
is StaggeredGridLayoutManager -> l.spanCount = boundCount
is GridLayoutManager -> l.spanCount = boundCount
else -> Unit
}
Config.listSpanCount = boundCount
}
companion object {
@SuppressLint("ClickableViewAccessibility")
fun attachTo(view: RecyclerView) = view.setOnTouchListener(PinchZoomTouchListener(view))
fun clear(view: View) = view.setOnTouchListener(null)
}
}

View 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="?colorOnSurface"
android:pathData="M16,17H7V10.5C7,8 9,6 11.5,6C14,6 16,8 16,10.5M18,16V10.5C18,7.43 15.86,4.86 13,4.18V3.5A1.5,1.5 0 0,0 11.5,2A1.5,1.5 0 0,0 10,3.5V4.18C7.13,4.86 5,7.43 5,10.5V16L3,18V19H20V18M11.5,22A2,2 0 0,0 13.5,20H9.5A2,2 0 0,0 11.5,22Z" />
</vector>

View 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="?colorError"
android:pathData="M22.11 21.46L2.39 1.73L1.11 3L4.06 5.95C2.78 7.63 2 9.72 2 12C2 17.5 6.5 22 12 22C14.28 22 16.37 21.23 18.05 19.94L20.84 22.73L22.11 21.46M12 20C7.58 20 4 16.42 4 12C4 10.27 4.56 8.68 5.5 7.38L16.62 18.5C15.32 19.45 13.73 20 12 20M8.17 4.97L6.72 3.5C8.25 2.56 10.06 2 12 2C17.5 2 22 6.5 22 12C22 13.94 21.44 15.75 20.5 17.28L19.03 15.83C19.65 14.69 20 13.39 20 12C20 7.58 16.42 4 12 4C10.61 4 9.31 4.35 8.17 4.97Z" />
</vector>

View File

@ -20,7 +20,7 @@
android:layout_height="match_parent"> android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/module_remote" android:id="@+id/module_list"
adapter="@{viewModel.adapter}" adapter="@{viewModel.adapter}"
dividerHorizontal="@{R.drawable.divider_l1}" dividerHorizontal="@{R.drawable.divider_l1}"
dividerVertical="@{R.drawable.divider_l1}" dividerVertical="@{R.drawable.divider_l1}"

View File

@ -19,6 +19,7 @@
dividerVertical="@{R.drawable.divider_l_50}" dividerVertical="@{R.drawable.divider_l_50}"
itemBinding="@{viewModel.itemBinding}" itemBinding="@{viewModel.itemBinding}"
items="@{viewModel.items}" items="@{viewModel.items}"
android:id="@+id/settings_list"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:focusableInTouchMode="false" android:focusableInTouchMode="false"

View File

@ -25,6 +25,7 @@
items="@{viewModel.items}" items="@{viewModel.items}"
nestedScrollingEnabled="@{false}" nestedScrollingEnabled="@{false}"
android:layout_width="match_parent" android:layout_width="match_parent"
android:id="@+id/superuser_list"
android:layout_height="match_parent" android:layout_height="match_parent"
android:clipToPadding="false" android:clipToPadding="false"
android:orientation="vertical" android:orientation="vertical"

View File

@ -22,50 +22,71 @@
android:layout_gravity="center" android:layout_gravity="center"
android:alpha="@{item.isEnabled() ? 1f : .5f}" android:alpha="@{item.isEnabled() ? 1f : .5f}"
android:onClick="@{() -> item.toggle(viewModel)}" android:onClick="@{() -> item.toggle(viewModel)}"
android:onLongClick="@{() -> item.toggle()}"
tools:layout_marginBottom="@dimen/l1" tools:layout_marginBottom="@dimen/l1"
tools:layout_marginEnd="@dimen/l1" tools:layout_marginEnd="@dimen/l1"
tools:layout_width="200dp"> tools:layout_width="200dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content">
android:paddingTop="@dimen/l2"
android:paddingBottom="@dimen/l2">
<androidx.appcompat.widget.AppCompatImageView <androidx.appcompat.widget.AppCompatImageView
android:id="@+id/policy_app_icon" android:id="@+id/policy_app_icon"
style="@style/WidgetFoundation.Image.Big" style="@style/WidgetFoundation.Image"
android:layout_marginStart="@dimen/l1"
android:layout_marginTop="@dimen/l1"
android:layout_marginBottom="@dimen/l1"
android:src="@{item.icon}" android:src="@{item.icon}"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0"
tools:srcCompat="@drawable/ic_logo" /> tools:srcCompat="@drawable/ic_logo" />
<androidx.appcompat.widget.AppCompatImageView
style="@style/WidgetFoundation.Image.Big"
gone="@{item.isEnabled()}"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@+id/policy_app_icon"
app:layout_constraintEnd_toEndOf="@+id/policy_app_icon"
app:layout_constraintStart_toStartOf="@+id/policy_app_icon"
app:layout_constraintTop_toTopOf="@+id/policy_app_icon"
app:srcCompat="@drawable/ic_off"
app:tint="?colorSurface"
tools:visibility="visible" />
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/policy_app_name" android:id="@+id/policy_app_name"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/margin_generic" android:layout_marginLeft="@dimen/margin_generic"
android:layout_marginTop="@dimen/l1"
android:layout_marginRight="@dimen/margin_generic" android:layout_marginRight="@dimen/margin_generic"
android:ellipsize="middle" android:ellipsize="middle"
android:gravity="center" android:gravity="start"
android:singleLine="true" android:maxLines="2"
android:text="@{item.item.appName}" android:text="@{item.item.appName}"
android:textAppearance="@style/AppearanceFoundation.Title" android:textAppearance="@style/AppearanceFoundation.Body"
android:textIsSelectable="false" android:textIsSelectable="false"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toEndOf="@+id/policy_app_icon"
app:layout_constraintTop_toBottomOf="@+id/policy_app_icon" app:layout_constraintTop_toTopOf="@+id/policy_app_icon"
tools:text="@string/app_name" /> tools:text="@string/app_name" />
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/policy_package_name" android:id="@+id/policy_package_name"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/l1"
android:ellipsize="middle" android:ellipsize="middle"
android:gravity="center" android:gravity="start"
android:singleLine="true" android:maxLines="2"
android:text="@{item.item.packageName}" android:text="@{item.item.packageName}"
android:textAppearance="@style/AppearanceFoundation.Caption.Variant" android:textAppearance="@style/AppearanceFoundation.Caption.Variant"
android:textColor="@android:color/tertiary_text_dark" android:textColor="@android:color/tertiary_text_dark"
@ -74,61 +95,70 @@
app:layout_constraintEnd_toEndOf="@id/policy_app_name" app:layout_constraintEnd_toEndOf="@id/policy_app_name"
app:layout_constraintStart_toStartOf="@id/policy_app_name" app:layout_constraintStart_toStartOf="@id/policy_app_name"
app:layout_constraintTop_toBottomOf="@id/policy_app_name" app:layout_constraintTop_toBottomOf="@id/policy_app_name"
app:layout_constraintVertical_bias="0"
tools:text="com.topjohnwu.magisk" /> tools:text="com.topjohnwu.magisk" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/policy_expand_container" android:id="@+id/policy_expand_container"
revealFix="@{item.isExpanded()}" gone="@{!item.isExpanded}"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
app:layout_constraintTop_toBottomOf="@+id/policy_package_name"
android:background="?colorSurfaceVariant" android:background="?colorSurfaceVariant"
android:visibility="invisible" android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
tools:alpha="0.9" app:layout_constraintTop_toBottomOf="@+id/policy_package_name"
tools:visibility="visible"> tools:visibility="visible">
<androidx.appcompat.widget.AppCompatImageView <com.google.android.material.button.MaterialButton
android:id="@+id/policy_notify" android:id="@+id/policy_notify"
style="@style/WidgetFoundation.Icon" style="@style/WidgetFoundation.Button.Text"
android:background="?selectableItemBackground"
isSelected="@{item.shouldNotify}" isSelected="@{item.shouldNotify}"
tooltipText="@{@string/superuser_toggle_notification}" android:layout_width="match_parent"
android:contentDescription="@string/superuser_toggle_notification" android:layout_height="wrap_content"
android:minHeight="24dp"
android:onClick="@{() -> item.toggleNotify(viewModel)}" android:onClick="@{() -> item.toggleNotify(viewModel)}"
app:layout_constraintBottom_toTopOf="@+id/policy_delete" android:text="@string/superuser_toggle_notification"
app:layout_constraintEnd_toStartOf="@+id/policy_log" android:textAllCaps="false"
app:layout_constraintHorizontal_chainStyle="packed" android:textAppearance="@style/AppearanceFoundation.Tiny"
android:textColor="@color/color_state_primary_transient"
app:icon="@drawable/ic_notifications_md2"
app:iconTint="@color/color_state_primary_transient"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
app:srcCompat="@drawable/ic_notifications"
app:tint="@color/color_state_primary_transient" /> app:tint="@color/color_state_primary_transient" />
<androidx.appcompat.widget.AppCompatImageView <com.google.android.material.button.MaterialButton
android:id="@+id/policy_log" android:id="@+id/policy_log"
style="@style/WidgetFoundation.Icon" style="@style/WidgetFoundation.Button.Text"
android:background="?selectableItemBackground"
isSelected="@{item.shouldLog}" isSelected="@{item.shouldLog}"
tooltipText="@{@string/superuser_toggle_log}" android:layout_width="match_parent"
android:contentDescription="@string/superuser_toggle_log" android:layout_height="wrap_content"
android:minHeight="24dp"
android:onClick="@{() -> item.toggleLog(viewModel)}" android:onClick="@{() -> item.toggleLog(viewModel)}"
app:layout_constraintBottom_toBottomOf="@+id/policy_notify" android:text="@string/superuser_toggle_log"
android:textAllCaps="false"
android:textAppearance="@style/AppearanceFoundation.Tiny"
android:textColor="@color/color_state_primary_transient"
app:icon="@drawable/ic_bug_md2"
app:iconTint="@color/color_state_primary_transient"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/policy_notify" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/policy_notify" app:layout_constraintTop_toBottomOf="@+id/policy_notify"
app:srcCompat="@drawable/ic_bug_report"
app:tint="@color/color_state_primary_transient" /> app:tint="@color/color_state_primary_transient" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/policy_delete" android:id="@+id/policy_delete"
style="@style/WidgetFoundation.Button.Text" style="@style/WidgetFoundation.Button.Text"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:minHeight="24dp"
android:onClick="@{() -> viewModel.deletePressed(item)}" android:onClick="@{() -> viewModel.deletePressed(item)}"
android:text="@string/superuser_toggle_revoke" android:text="@string/superuser_toggle_revoke"
android:textAllCaps="false" android:textAllCaps="false"
@ -139,20 +169,12 @@
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/policy_notify" app:layout_constraintTop_toBottomOf="@+id/policy_log"
app:rippleColor="?colorError" /> app:rippleColor="?colorError" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
<androidx.appcompat.widget.AppCompatImageView </LinearLayout>
style="@style/WidgetFoundation.Icon"
isEnabled="@{item.isEnabled}"
isSelected="@{item.isExpanded}"
android:layout_gravity="top|right"
android:onClick="@{(view) -> item.toggle(view)}"
android:rotation="@{item.isExpanded ? 225 : 180}"
app:srcCompat="@drawable/ic_more_collapse"
tools:ignore="RtlHardcoded" />
</com.google.android.material.card.MaterialCardView> </com.google.android.material.card.MaterialCardView>

View File

@ -86,8 +86,8 @@
<string name="module_state_restore">Restore</string> <string name="module_state_restore">Restore</string>
<string name="module_action_install_external">Install from storage</string> <string name="module_action_install_external">Install from storage</string>
<string name="superuser_toggle_log">Toggles logging</string> <string name="superuser_toggle_log">Logs</string>
<string name="superuser_toggle_notification">Toggles “toast” notifications</string> <string name="superuser_toggle_notification">Notifications</string>
<string name="superuser_toggle_revoke">Revoke</string> <string name="superuser_toggle_revoke">Revoke</string>
<string name="hide_filter_hint">Filter by name</string> <string name="hide_filter_hint">Filter by name</string>