Added fair amount of settings implemented from the UI side

Updated dialog to create recycler as it behaves better than regular listview
This commit is contained in:
Viktor De Pasquale 2019-11-28 21:53:31 +01:00
parent 627b40799c
commit cb96b536a2
6 changed files with 278 additions and 36 deletions

View File

@ -5,7 +5,10 @@ import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.Config import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.extensions.get import com.topjohnwu.magisk.extensions.get
import com.topjohnwu.magisk.extensions.subscribeK
import com.topjohnwu.magisk.utils.asTransitive import com.topjohnwu.magisk.utils.asTransitive
import com.topjohnwu.magisk.utils.availableLocales
import com.topjohnwu.magisk.utils.currentLocale
// --- Customization // --- Customization
@ -13,6 +16,22 @@ object Customization : SettingsItem.Section() {
override val title = "Customization".asTransitive() override val title = "Customization".asTransitive()
} }
object Language : SettingsItem.Selector() {
override var value by dataObservable(0) {
Config.locale = entryValues.getOrNull(it)?.toString() ?: return@dataObservable
}
override val title = R.string.language.asTransitive()
init {
availableLocales.subscribeK { (names, values) ->
setValues(names, values)
val selectedLocale = currentLocale.getDisplayName(currentLocale)
value = names.indexOfFirst { it == selectedLocale }.let { if (it == -1) 0 else it }
}
}
}
object Redesign : SettingsItem.Toggle() { object Redesign : SettingsItem.Toggle() {
override val title = "Redesign".asTransitive() override val title = "Redesign".asTransitive()
override val description = override val description =
@ -31,7 +50,6 @@ object Manager : SettingsItem.Section() {
override val title = R.string.manager.asTransitive() override val title = R.string.manager.asTransitive()
} }
object Language
object ClearRepoCache : SettingsItem.Blank() { object ClearRepoCache : SettingsItem.Blank() {
override val title = R.string.settings_clear_cache_title.asTransitive() override val title = R.string.settings_clear_cache_title.asTransitive()
override val description = R.string.settings_clear_cache_summary.asTransitive() override val description = R.string.settings_clear_cache_summary.asTransitive()
@ -51,8 +69,17 @@ object Restore : SettingsItem.Blank() {
fun HideOrRestore() = fun HideOrRestore() =
if (get<Context>().packageName == BuildConfig.APPLICATION_ID) Hide else Restore if (get<Context>().packageName == BuildConfig.APPLICATION_ID) Hide else Restore
//todo new dialog
object DownloadPath object DownloadPath
object UpdateChannel
//fixme this
object UpdateChannel : SettingsItem.Selector() {
override var value by dataObservable(Config.updateChannel) { Config.updateChannel = it }
}
//fixme new dialog
object UpdateChannelUrl
object UpdateChecker : SettingsItem.Toggle() { object UpdateChecker : SettingsItem.Toggle() {
override val title = R.string.settings_check_update_title.asTransitive() override val title = R.string.settings_check_update_title.asTransitive()
override val description = R.string.settings_check_update_summary.asTransitive() override val description = R.string.settings_check_update_summary.asTransitive()
@ -95,9 +122,100 @@ object Superuser : SettingsItem.Section() {
override val title = R.string.superuser.asTransitive() override val title = R.string.superuser.asTransitive()
} }
object AccessMode object AccessMode : SettingsItem.Selector() {
object MultiuserMode override val title = R.string.superuser_access.asTransitive()
object MountNamespaceMode override var value by dataObservable(Config.rootMode) {
object AutomaticResponse Config.rootMode = entryValues.getOrNull(it)
object RequestTimeout ?.toString()
object SUNotification ?.toInt() ?: return@dataObservable
}
init {
setValues(
resources.getStringArray(R.array.su_access),
resources.getStringArray(R.array.value_array)
)
}
}
object MultiuserMode : SettingsItem.Selector() {
override val title = R.string.multiuser_mode.asTransitive()
override var value by dataObservable(Config.suMultiuserMode) {
Config.suMultiuserMode = entryValues.getOrNull(it)
?.toString()
?.toInt() ?: return@dataObservable
}
init {
setValues(
resources.getStringArray(R.array.multiuser_mode),
resources.getStringArray(R.array.value_array)
)
}
}
object MountNamespaceMode : SettingsItem.Selector() {
override val title = R.string.mount_namespace_mode.asTransitive()
override var value by dataObservable(Config.suMntNamespaceMode) {
Config.suMntNamespaceMode = entryValues.getOrNull(it)
?.toString()
?.toInt() ?: return@dataObservable
}
init {
setValues(
resources.getStringArray(R.array.namespace),
resources.getStringArray(R.array.value_array)
)
}
}
object AutomaticResponse : SettingsItem.Selector() {
override val title = R.string.auto_response.asTransitive()
override var value by dataObservable(Config.suAutoReponse) {
Config.suAutoReponse = entryValues.getOrNull(it)
?.toString()
?.toInt() ?: return@dataObservable
}
init {
setValues(
resources.getStringArray(R.array.auto_response),
resources.getStringArray(R.array.value_array)
)
}
}
object RequestTimeout : SettingsItem.Selector() {
override val title = R.string.request_timeout.asTransitive()
override var value by dataObservable(-1) {
Config.suDefaultTimeout = entryValues.getOrNull(it)
?.toString()
?.toInt() ?: return@dataObservable
}
init {
setValues(
resources.getStringArray(R.array.request_timeout),
resources.getStringArray(R.array.request_timeout_value)
)
val currentValue = Config.suDefaultTimeout.toString()
value = entryValues.indexOfFirst { it == currentValue }
}
}
object SUNotification : SettingsItem.Selector() {
override val title = R.string.superuser_notification.asTransitive()
override var value by dataObservable(Config.suNotification) {
Config.suNotification = entryValues.getOrNull(it)
?.toString()
?.toInt() ?: return@dataObservable
}
init {
setValues(
resources.getStringArray(R.array.su_notification),
resources.getStringArray(R.array.value_array)
)
}
}

View File

@ -1,5 +1,6 @@
package com.topjohnwu.magisk.redesign.settings package com.topjohnwu.magisk.redesign.settings
import android.content.res.Resources
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View import android.view.View
import androidx.annotation.CallSuper import androidx.annotation.CallSuper
@ -17,6 +18,8 @@ import com.topjohnwu.magisk.redesign.module.adapterOf
import com.topjohnwu.magisk.redesign.superuser.diffListOf import com.topjohnwu.magisk.redesign.superuser.diffListOf
import com.topjohnwu.magisk.utils.TransitiveText import com.topjohnwu.magisk.utils.TransitiveText
import com.topjohnwu.magisk.view.MagiskDialog import com.topjohnwu.magisk.view.MagiskDialog
import org.koin.core.KoinComponent
import org.koin.core.get
import kotlin.properties.ObservableProperty import kotlin.properties.ObservableProperty
import kotlin.reflect.KProperty import kotlin.reflect.KProperty
@ -25,15 +28,18 @@ class SettingsViewModel : CompatViewModel(), SettingsItem.Callback {
val adapter = adapterOf<SettingsItem>() val adapter = adapterOf<SettingsItem>()
val itemBinding = itemBindingOf<SettingsItem> { it.bindExtra(BR.callback, this) } val itemBinding = itemBindingOf<SettingsItem> { it.bindExtra(BR.callback, this) }
val items = diffListOf( val items = diffListOf(
// General Customization,
// Customization Theme, Language, Redesign,
Customization, Theme, Redesign,
// Manager Manager,
Manager, ClearRepoCache, HideOrRestore(), UpdateChecker, SystemlessHosts, Biometrics, ClearRepoCache, HideOrRestore(), UpdateChecker, SystemlessHosts, Biometrics,
// Magisk
Magisk, SafeMode, MagiskHide, Magisk,
// Superuser SafeMode, MagiskHide,
Superuser
Superuser,
AccessMode, MultiuserMode, MountNamespaceMode, AutomaticResponse, RequestTimeout,
SUNotification
) )
override fun onItemPressed(view: View, item: SettingsItem) = when (item) { override fun onItemPressed(view: View, item: SettingsItem) = when (item) {
@ -121,14 +127,49 @@ sealed class SettingsItem : ObservableItem<SettingsItem>() {
} }
abstract class Selector : Value<Int>() { abstract class Selector : Value<Int>(), KoinComponent {
override val layoutRes = R.layout.item_settings_selector override val layoutRes = R.layout.item_settings_selector
protected val resources get() = get<Resources>()
@Bindable
var entries: Array<out CharSequence> = arrayOf()
private set
@Bindable
var entryValues: Array<out CharSequence> = arrayOf()
private set
val selectedEntry
@Bindable get() = entries.getOrNull(value)
fun setValues(
entries: Array<out CharSequence>,
values: Array<out CharSequence>
) {
check(entries.size <= values.size) { "List sizes must match" }
this.entries = entries
this.entryValues = values
notifyChange(BR.entries)
notifyChange(BR.entryValues)
notifyChange(BR.selectedEntry)
}
override fun onPressed(view: View, callback: Callback) { override fun onPressed(view: View, callback: Callback) {
if (entries.isEmpty() || entryValues.isEmpty()) return
MagiskDialog(view.context) MagiskDialog(view.context)
//.applyAdapter() .applyTitle(title.getText(resources))
super.onPressed(view, callback) .applyButton(MagiskDialog.ButtonType.NEGATIVE) {
titleRes = android.R.string.cancel
}
.applyAdapter(entries) {
value = it
notifyChange(BR.selectedEntry)
super.onPressed(view, callback)
}
.reveal()
} }
} }

View File

@ -8,13 +8,22 @@ import android.graphics.drawable.Drawable
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.view.updateLayoutParams
import androidx.databinding.ViewDataBinding import androidx.databinding.ViewDataBinding
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.ComparableRvItem
import com.topjohnwu.magisk.databinding.DialogMagiskBaseBinding import com.topjohnwu.magisk.databinding.DialogMagiskBaseBinding
import com.topjohnwu.magisk.redesign.home.itemBindingOf
import com.topjohnwu.magisk.utils.KObservableField import com.topjohnwu.magisk.utils.KObservableField
import me.tatarka.bindingcollectionadapter2.BindingRecyclerViewAdapters
import me.tatarka.bindingcollectionadapter2.ItemBinding
class MagiskDialog @JvmOverloads constructor( class MagiskDialog @JvmOverloads constructor(
context: Context, theme: Int = 0 context: Context, theme: Int = 0
@ -138,20 +147,57 @@ class MagiskDialog @JvmOverloads constructor(
ButtonBuilder(button).apply(builder) ButtonBuilder(button).apply(builder)
} }
class DialogItem(
val item: CharSequence,
val position: Int
) : ComparableRvItem<DialogItem>() {
override val layoutRes = R.layout.item_list_single_line
override fun itemSameAs(other: DialogItem) = item == other.item
override fun contentSameAs(other: DialogItem) = itemSameAs(other)
}
interface ActualOnDialogClickListener {
fun onClick(position: Int)
}
fun applyAdapter(
list: Array<out CharSequence>,
listener: OnDialogClickListener
) = applyView(
RecyclerView(context).also {
it.isNestedScrollingEnabled = false
it.layoutManager = LinearLayoutManager(context)
val actualListener = object : ActualOnDialogClickListener {
override fun onClick(position: Int) {
listener(position)
dismiss()
}
}
val items = list.mapIndexed { i, it -> DialogItem(it, i) }
val binding = itemBindingOf<DialogItem> { it.bindExtra(BR.listener, actualListener) }
.let { ItemBinding.of(it) }
BindingRecyclerViewAdapters.setAdapter(it, binding, items, null, null, null, null)
}
)
fun cancellable(isCancellable: Boolean) = apply { fun cancellable(isCancellable: Boolean) = apply {
setCancelable(isCancellable) setCancelable(isCancellable)
} }
fun <Binding : ViewDataBinding> applyView(binding: Binding, body: Binding.() -> Unit = {}) = fun <Binding : ViewDataBinding> applyView(
apply { binding: Binding,
resetView() body: Binding.() -> Unit = {}
this.binding.dialogBaseContainer.addView(binding.root) ) = applyView(binding.root).also { binding.apply(body) }
binding.apply(body)
}
fun applyView(view: View) = apply { fun applyView(view: View) = apply {
resetView() resetView()
this.binding.dialogBaseContainer.addView(view) binding.dialogBaseContainer.addView(view)
view.updateLayoutParams<ViewGroup.LayoutParams> {
width = ViewGroup.LayoutParams.MATCH_PARENT
height = ViewGroup.LayoutParams.WRAP_CONTENT
}
} }
fun onDismiss(callback: OnDialogButtonClickListener) = fun onDismiss(callback: OnDialogButtonClickListener) =
@ -203,4 +249,5 @@ class MagiskDialog @JvmOverloads constructor(
//endregion //endregion
} }
typealias OnDialogButtonClickListener = (DialogInterface) -> Unit typealias OnDialogButtonClickListener = (DialogInterface) -> Unit
typealias OnDialogClickListener = (position: Int) -> Unit

View File

@ -93,6 +93,7 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constrainedHeight="true" app:layout_constrainedHeight="true"
android:layout_marginTop="@dimen/l_50"
app:layout_constraintBottom_toTopOf="@+id/dialog_base_space" app:layout_constraintBottom_toTopOf="@+id/dialog_base_space"
app:layout_constraintLeft_toLeftOf="@+id/dialog_base_start" app:layout_constraintLeft_toLeftOf="@+id/dialog_base_start"
app:layout_constraintRight_toRightOf="@+id/dialog_base_end" app:layout_constraintRight_toRightOf="@+id/dialog_base_end"
@ -108,7 +109,6 @@
gone="@{data.message.length == 0}" gone="@{data.message.length == 0}"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="@dimen/l_50"
android:gravity="center" android:gravity="center"
android:text="@{data.message}" android:text="@{data.message}"
android:textAppearance="@style/AppearanceFoundation.Body" android:textAppearance="@style/AppearanceFoundation.Body"

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="item"
type="com.topjohnwu.magisk.view.MagiskDialog.DialogItem" />
<variable
name="listener"
type="com.topjohnwu.magisk.view.MagiskDialog.ActualOnDialogClickListener" />
</data>
<androidx.appcompat.widget.AppCompatTextView
android:id="@android:id/text1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:onClick="@{() -> listener.onClick(item.position)}"
android:paddingStart="@dimen/l1"
android:paddingTop="@dimen/l_75"
android:paddingEnd="@dimen/l1"
android:paddingBottom="@dimen/l_75"
android:text="@{item.item}"
android:textAppearance="@style/AppearanceFoundation.Body"
tools:text="@tools:sample/lorem" />
</layout>

View File

@ -19,7 +19,7 @@
style="@style/WidgetFoundation.Card" style="@style/WidgetFoundation.Card"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:onClick="@{(view) -> callback.onItemPressed(view, item)}" android:onClick="@{(view) -> item.onPressed(view, callback)}"
tools:layout_gravity="center"> tools:layout_gravity="center">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
@ -57,6 +57,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center_vertical" android:gravity="center_vertical"
android:orientation="vertical" android:orientation="vertical"
android:layout_marginStart="@{item.icon == 0 ? (int) @dimen/l1 : 0}"
android:paddingTop="@dimen/l1" android:paddingTop="@dimen/l1"
android:paddingBottom="@dimen/l1" android:paddingBottom="@dimen/l1"
app:layout_constraintBottom_toTopOf="@+id/selector_divider" app:layout_constraintBottom_toTopOf="@+id/selector_divider"
@ -69,7 +70,6 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
android:layout_marginStart="@{item.icon == 0 ? (int) @dimen/l1 : 0}"
android:gravity="start" android:gravity="start"
android:text="@{item.title}" android:text="@{item.title}"
android:textAppearance="@style/AppearanceFoundation.Body" android:textAppearance="@style/AppearanceFoundation.Body"
@ -91,18 +91,23 @@
<View <View
android:id="@+id/selector_divider" android:id="@+id/selector_divider"
gone="@{item.selectedEntry == null}"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="1.25dp" android:layout_height="1dp"
android:background="?colorOnSurfaceVariant" android:background="?colorSurface"
app:layout_constraintTop_toBottomOf="@+id/selector_text" /> app:layout_constraintTop_toBottomOf="@+id/selector_text" />
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/selector_selection" android:id="@+id/selector_selection"
gone="@{item.selectedEntry == null}"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="@dimen/l1" android:layout_marginStart="@dimen/l1"
android:textAppearance="@style/AppearanceFoundation.Caption.Variant" android:layout_marginTop="@dimen/l_75"
android:textStyle="bold" android:layout_marginEnd="@dimen/l1"
android:layout_marginBottom="@dimen/l_75"
android:text="@{item.selectedEntry}"
android:textAppearance="@style/AppearanceFoundation.Tiny.Variant"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="@+id/selector_text" app:layout_constraintStart_toStartOf="@+id/selector_text"
app:layout_constraintTop_toBottomOf="@+id/selector_divider" app:layout_constraintTop_toBottomOf="@+id/selector_divider"