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.R
import com.topjohnwu.magisk.extensions.get
import com.topjohnwu.magisk.extensions.subscribeK
import com.topjohnwu.magisk.utils.asTransitive
import com.topjohnwu.magisk.utils.availableLocales
import com.topjohnwu.magisk.utils.currentLocale
// --- Customization
@ -13,6 +16,22 @@ object Customization : SettingsItem.Section() {
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() {
override val title = "Redesign".asTransitive()
override val description =
@ -31,7 +50,6 @@ object Manager : SettingsItem.Section() {
override val title = R.string.manager.asTransitive()
}
object Language
object ClearRepoCache : SettingsItem.Blank() {
override val title = R.string.settings_clear_cache_title.asTransitive()
override val description = R.string.settings_clear_cache_summary.asTransitive()
@ -51,8 +69,17 @@ object Restore : SettingsItem.Blank() {
fun HideOrRestore() =
if (get<Context>().packageName == BuildConfig.APPLICATION_ID) Hide else Restore
//todo new dialog
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() {
override val title = R.string.settings_check_update_title.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()
}
object AccessMode
object MultiuserMode
object MountNamespaceMode
object AutomaticResponse
object RequestTimeout
object SUNotification
object AccessMode : SettingsItem.Selector() {
override val title = R.string.superuser_access.asTransitive()
override var value by dataObservable(Config.rootMode) {
Config.rootMode = entryValues.getOrNull(it)
?.toString()
?.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
import android.content.res.Resources
import android.view.MotionEvent
import android.view.View
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.utils.TransitiveText
import com.topjohnwu.magisk.view.MagiskDialog
import org.koin.core.KoinComponent
import org.koin.core.get
import kotlin.properties.ObservableProperty
import kotlin.reflect.KProperty
@ -25,15 +28,18 @@ class SettingsViewModel : CompatViewModel(), SettingsItem.Callback {
val adapter = adapterOf<SettingsItem>()
val itemBinding = itemBindingOf<SettingsItem> { it.bindExtra(BR.callback, this) }
val items = diffListOf(
// General
// Customization
Customization, Theme, Redesign,
// Manager
Manager, ClearRepoCache, HideOrRestore(), UpdateChecker, SystemlessHosts, Biometrics,
// Magisk
Magisk, SafeMode, MagiskHide,
// Superuser
Superuser
Customization,
Theme, Language, Redesign,
Manager,
ClearRepoCache, HideOrRestore(), UpdateChecker, SystemlessHosts, Biometrics,
Magisk,
SafeMode, MagiskHide,
Superuser,
AccessMode, MultiuserMode, MountNamespaceMode, AutomaticResponse, RequestTimeout,
SUNotification
)
override fun onItemPressed(view: View, item: SettingsItem) = when (item) {
@ -121,15 +127,50 @@ sealed class SettingsItem : ObservableItem<SettingsItem>() {
}
abstract class Selector : Value<Int>() {
abstract class Selector : Value<Int>(), KoinComponent {
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) {
if (entries.isEmpty() || entryValues.isEmpty()) return
MagiskDialog(view.context)
//.applyAdapter()
.applyTitle(title.getText(resources))
.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.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.appcompat.app.AlertDialog
import androidx.core.view.updateLayoutParams
import androidx.databinding.ViewDataBinding
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
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.redesign.home.itemBindingOf
import com.topjohnwu.magisk.utils.KObservableField
import me.tatarka.bindingcollectionadapter2.BindingRecyclerViewAdapters
import me.tatarka.bindingcollectionadapter2.ItemBinding
class MagiskDialog @JvmOverloads constructor(
context: Context, theme: Int = 0
@ -138,20 +147,57 @@ class MagiskDialog @JvmOverloads constructor(
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 {
setCancelable(isCancellable)
}
fun <Binding : ViewDataBinding> applyView(binding: Binding, body: Binding.() -> Unit = {}) =
apply {
resetView()
this.binding.dialogBaseContainer.addView(binding.root)
binding.apply(body)
}
fun <Binding : ViewDataBinding> applyView(
binding: Binding,
body: Binding.() -> Unit = {}
) = applyView(binding.root).also { binding.apply(body) }
fun applyView(view: View) = apply {
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) =
@ -204,3 +250,4 @@ class MagiskDialog @JvmOverloads constructor(
}
typealias OnDialogButtonClickListener = (DialogInterface) -> Unit
typealias OnDialogClickListener = (position: Int) -> Unit

View File

@ -93,6 +93,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constrainedHeight="true"
android:layout_marginTop="@dimen/l_50"
app:layout_constraintBottom_toTopOf="@+id/dialog_base_space"
app:layout_constraintLeft_toLeftOf="@+id/dialog_base_start"
app:layout_constraintRight_toRightOf="@+id/dialog_base_end"
@ -108,7 +109,6 @@
gone="@{data.message.length == 0}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/l_50"
android:gravity="center"
android:text="@{data.message}"
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"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="@{(view) -> callback.onItemPressed(view, item)}"
android:onClick="@{(view) -> item.onPressed(view, callback)}"
tools:layout_gravity="center">
<androidx.constraintlayout.widget.ConstraintLayout
@ -57,6 +57,7 @@
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="vertical"
android:layout_marginStart="@{item.icon == 0 ? (int) @dimen/l1 : 0}"
android:paddingTop="@dimen/l1"
android:paddingBottom="@dimen/l1"
app:layout_constraintBottom_toTopOf="@+id/selector_divider"
@ -69,7 +70,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="@{item.icon == 0 ? (int) @dimen/l1 : 0}"
android:gravity="start"
android:text="@{item.title}"
android:textAppearance="@style/AppearanceFoundation.Body"
@ -91,18 +91,23 @@
<View
android:id="@+id/selector_divider"
gone="@{item.selectedEntry == null}"
android:layout_width="match_parent"
android:layout_height="1.25dp"
android:background="?colorOnSurfaceVariant"
android:layout_height="1dp"
android:background="?colorSurface"
app:layout_constraintTop_toBottomOf="@+id/selector_text" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/selector_selection"
gone="@{item.selectedEntry == null}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/l1"
android:textAppearance="@style/AppearanceFoundation.Caption.Variant"
android:textStyle="bold"
android:layout_marginStart="@dimen/l1"
android:layout_marginTop="@dimen/l_75"
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_constraintStart_toStartOf="@+id/selector_text"
app:layout_constraintTop_toBottomOf="@+id/selector_divider"