mirror of
https://github.com/topjohnwu/Magisk.git
synced 2024-12-22 07:57:39 +00:00
More efficient databinding
This commit is contained in:
parent
b41b2283f4
commit
2c12fe6eb2
@ -4,6 +4,7 @@ import androidx.annotation.CallSuper
|
||||
import androidx.databinding.ViewDataBinding
|
||||
import com.topjohnwu.magisk.BR
|
||||
import com.topjohnwu.magisk.utils.DiffObservableList
|
||||
import com.topjohnwu.magisk.utils.ObservableHost
|
||||
import me.tatarka.bindingcollectionadapter2.ItemBinding
|
||||
|
||||
abstract class RvItem {
|
||||
@ -45,4 +46,6 @@ abstract class ComparableRvItem<in T> : RvItem() {
|
||||
) = oldItem.genericContentSameAs(newItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract class ObservableItem<T> : ComparableRvItem<T>(), ObservableHost by ObservableHost.impl
|
||||
|
@ -3,6 +3,7 @@ package com.topjohnwu.magisk.model.entity.recycler
|
||||
import androidx.databinding.Bindable
|
||||
import com.topjohnwu.magisk.BR
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.databinding.ObservableItem
|
||||
import com.topjohnwu.magisk.ktx.timeDateFormat
|
||||
import com.topjohnwu.magisk.ktx.toTime
|
||||
import com.topjohnwu.magisk.model.entity.MagiskLog
|
||||
@ -16,13 +17,13 @@ class LogItem(val item: MagiskLog) : ObservableItem<LogItem>() {
|
||||
@Bindable get
|
||||
set(value) {
|
||||
field = value
|
||||
notifyChange(BR.top)
|
||||
notifyPropertyChanged(BR.top)
|
||||
}
|
||||
var isBottom = false
|
||||
@Bindable get
|
||||
set(value) {
|
||||
field = value
|
||||
notifyChange(BR.bottom)
|
||||
notifyPropertyChanged(BR.bottom)
|
||||
}
|
||||
|
||||
override fun itemSameAs(other: LogItem) = item.appName == other.item.appName
|
||||
|
@ -1,12 +1,16 @@
|
||||
package com.topjohnwu.magisk.model.entity.recycler
|
||||
|
||||
import androidx.databinding.*
|
||||
import androidx.databinding.Bindable
|
||||
import androidx.databinding.Observable
|
||||
import androidx.databinding.ObservableField
|
||||
import androidx.databinding.ViewDataBinding
|
||||
import androidx.recyclerview.widget.StaggeredGridLayoutManager
|
||||
import com.topjohnwu.magisk.BR
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.core.model.module.Module
|
||||
import com.topjohnwu.magisk.core.model.module.Repo
|
||||
import com.topjohnwu.magisk.databinding.ComparableRvItem
|
||||
import com.topjohnwu.magisk.databinding.ObservableItem
|
||||
import com.topjohnwu.magisk.ui.module.ModuleViewModel
|
||||
|
||||
object InstallModule : ComparableRvItem<InstallModule>() {
|
||||
@ -33,19 +37,19 @@ class SectionTitle(
|
||||
@Bindable get
|
||||
set(value) {
|
||||
field = value
|
||||
notifyChange(BR.button)
|
||||
notifyPropertyChanged(BR.button)
|
||||
}
|
||||
var icon = _icon
|
||||
@Bindable get
|
||||
set(value) {
|
||||
field = value
|
||||
notifyChange(BR.icon)
|
||||
notifyPropertyChanged(BR.icon)
|
||||
}
|
||||
var hasButton = button != 0 || icon != 0
|
||||
@Bindable get
|
||||
set(value) {
|
||||
field = value
|
||||
notifyChange(BR.hasButton)
|
||||
notifyPropertyChanged(BR.hasButton)
|
||||
}
|
||||
|
||||
override fun onBindingBound(binding: ViewDataBinding) {
|
||||
@ -66,7 +70,7 @@ sealed class RepoItem(val item: Repo) : ObservableItem<RepoItem>() {
|
||||
@Bindable get
|
||||
protected set(value) {
|
||||
field = value
|
||||
notifyChange(BR.update)
|
||||
notifyPropertyChanged(BR.update)
|
||||
}
|
||||
|
||||
override fun contentSameAs(other: RepoItem): Boolean = item == other.item
|
||||
@ -89,7 +93,7 @@ class ModuleItem(val item: Module) : ObservableItem<ModuleItem>(), Observable {
|
||||
var repo: Repo? = null
|
||||
set(value) {
|
||||
field = value
|
||||
notifyChange(BR.repo)
|
||||
notifyPropertyChanged(BR.repo)
|
||||
}
|
||||
|
||||
@get:Bindable
|
||||
@ -97,7 +101,7 @@ class ModuleItem(val item: Module) : ObservableItem<ModuleItem>(), Observable {
|
||||
get() = item.enable
|
||||
set(value) {
|
||||
item.enable = value
|
||||
notifyChange(BR.enabled)
|
||||
notifyPropertyChanged(BR.enabled)
|
||||
}
|
||||
|
||||
@get:Bindable
|
||||
@ -105,7 +109,7 @@ class ModuleItem(val item: Module) : ObservableItem<ModuleItem>(), Observable {
|
||||
get() = item.remove
|
||||
set(value) {
|
||||
item.remove = value
|
||||
notifyChange(BR.removed)
|
||||
notifyPropertyChanged(BR.removed)
|
||||
}
|
||||
|
||||
val isUpdated get() = item.updated
|
||||
@ -126,21 +130,5 @@ class ModuleItem(val item: Module) : ObservableItem<ModuleItem>(), Observable {
|
||||
&& item.name == other.item.name
|
||||
|
||||
override fun itemSameAs(other: ModuleItem): Boolean = item.id == other.item.id
|
||||
|
||||
}
|
||||
|
||||
abstract class ObservableItem<T> : ComparableRvItem<T>(), Observable {
|
||||
|
||||
private val list = PropertyChangeRegistry()
|
||||
|
||||
override fun removeOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback?) {
|
||||
list.remove(callback ?: return)
|
||||
}
|
||||
|
||||
override fun addOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback?) {
|
||||
list.add(callback ?: return)
|
||||
}
|
||||
|
||||
fun notifyChange(id: Int) = list.notifyChange(this, id)
|
||||
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import androidx.databinding.ViewDataBinding
|
||||
import androidx.recyclerview.widget.StaggeredGridLayoutManager
|
||||
import com.topjohnwu.magisk.BR
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.databinding.ObservableItem
|
||||
import com.topjohnwu.magisk.utils.TransitiveText
|
||||
import com.topjohnwu.magisk.view.MagiskDialog
|
||||
import org.koin.core.KoinComponent
|
||||
@ -37,7 +38,7 @@ sealed class SettingsItem : ObservableItem<SettingsItem>() {
|
||||
|
||||
// notify only after the callback invocation; callback can invalidate the backing data,
|
||||
// which wouldn't be recognized with reverse approach
|
||||
notifyChange(BR.description)
|
||||
notifyPropertyChanged(BR.description)
|
||||
}
|
||||
|
||||
open fun refresh() {}
|
||||
@ -60,7 +61,7 @@ sealed class SettingsItem : ObservableItem<SettingsItem>() {
|
||||
) = object : ObservableProperty<T>(initialValue) {
|
||||
override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) {
|
||||
setter(newValue)
|
||||
notifyChange(fieldId)
|
||||
notifyPropertyChanged(fieldId)
|
||||
}
|
||||
}
|
||||
|
||||
@ -169,7 +170,7 @@ sealed class SettingsItem : ObservableItem<SettingsItem>() {
|
||||
}
|
||||
.applyAdapter(entries) {
|
||||
value = it
|
||||
notifyChange(BR.selectedEntry)
|
||||
notifyPropertyChanged(BR.selectedEntry)
|
||||
super.onPressed(view, callback)
|
||||
}
|
||||
.reveal()
|
||||
|
@ -18,6 +18,8 @@ import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.databinding.ActivityMainMd2Binding
|
||||
import com.topjohnwu.magisk.ktx.startAnimations
|
||||
import com.topjohnwu.magisk.ui.base.BaseUIActivity
|
||||
import com.topjohnwu.magisk.ui.base.BaseViewModel
|
||||
import com.topjohnwu.magisk.ui.base.ReselectionTarget
|
||||
import com.topjohnwu.magisk.ui.home.HomeFragmentDirections
|
||||
import com.topjohnwu.magisk.utils.HideBottomViewOnScrollBehavior
|
||||
import com.topjohnwu.magisk.utils.HideTopViewOnScrollBehavior
|
||||
@ -26,6 +28,8 @@ import com.topjohnwu.magisk.view.MagiskDialog
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
|
||||
class MainViewModel : BaseViewModel()
|
||||
|
||||
open class MainActivity : BaseUIActivity<MainViewModel, ActivityMainMd2Binding>() {
|
||||
|
||||
override val layoutRes = R.layout.activity_main_md2
|
||||
|
@ -1,5 +0,0 @@
|
||||
package com.topjohnwu.magisk.ui
|
||||
|
||||
import com.topjohnwu.magisk.ui.base.BaseViewModel
|
||||
|
||||
class MainViewModel : BaseViewModel()
|
@ -1,7 +0,0 @@
|
||||
package com.topjohnwu.magisk.ui
|
||||
|
||||
interface ReselectionTarget {
|
||||
|
||||
fun onReselected()
|
||||
|
||||
}
|
@ -20,7 +20,6 @@ import com.topjohnwu.magisk.core.Config
|
||||
import com.topjohnwu.magisk.core.base.BaseActivity
|
||||
import com.topjohnwu.magisk.ktx.snackbar
|
||||
import com.topjohnwu.magisk.ktx.startAnimations
|
||||
import com.topjohnwu.magisk.ktx.value
|
||||
import com.topjohnwu.magisk.model.events.EventHandler
|
||||
import com.topjohnwu.magisk.model.events.SnackbarEvent
|
||||
import com.topjohnwu.magisk.model.events.ViewEvent
|
||||
@ -116,7 +115,7 @@ abstract class BaseUIActivity<ViewModel : BaseViewModel, Binding : ViewDataBindi
|
||||
}
|
||||
|
||||
override fun peekSystemWindowInsets(insets: Insets) {
|
||||
viewModel.insets.value = insets
|
||||
viewModel.insets = insets
|
||||
}
|
||||
|
||||
protected fun ViewEvent.dispatchOnSelf() = onEventDispatched(this)
|
||||
|
@ -89,3 +89,9 @@ abstract class BaseUIFragment<ViewModel : BaseViewModel, Binding : ViewDataBindi
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
interface ReselectionTarget {
|
||||
|
||||
fun onReselected()
|
||||
|
||||
}
|
||||
|
@ -5,8 +5,6 @@ import androidx.annotation.CallSuper
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.databinding.Bindable
|
||||
import androidx.databinding.Observable
|
||||
import androidx.databinding.ObservableField
|
||||
import androidx.databinding.PropertyChangeRegistry
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
@ -17,30 +15,33 @@ import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.base.BaseActivity
|
||||
import com.topjohnwu.magisk.model.events.*
|
||||
import com.topjohnwu.magisk.model.navigation.NavigationWrapper
|
||||
import com.topjohnwu.magisk.utils.ObservableHost
|
||||
import com.topjohnwu.magisk.utils.observable
|
||||
import kotlinx.coroutines.Job
|
||||
import org.koin.core.KoinComponent
|
||||
|
||||
abstract class BaseViewModel(
|
||||
initialState: State = State.LOADING
|
||||
) : ViewModel(), Observable, KoinComponent {
|
||||
) : ViewModel(), ObservableHost by ObservableHost.impl, KoinComponent {
|
||||
|
||||
enum class State {
|
||||
LOADED, LOADING, LOADING_FAILED
|
||||
}
|
||||
|
||||
val loading @Bindable get() = state == State.LOADING
|
||||
val loaded @Bindable get() = state == State.LOADED
|
||||
val loadingFailed @Bindable get() = state == State.LOADING_FAILED
|
||||
@get:Bindable
|
||||
val loading get() = state == State.LOADING
|
||||
@get:Bindable
|
||||
val loaded get() = state == State.LOADED
|
||||
@get:Bindable
|
||||
val loadFailed get() = state == State.LOADING_FAILED
|
||||
|
||||
val isConnected get() = Info.isConnected
|
||||
val viewEvents: LiveData<ViewEvent> get() = _viewEvents
|
||||
val insets = ObservableField(Insets.NONE)
|
||||
|
||||
var state: State = initialState
|
||||
set(value) {
|
||||
field = value
|
||||
notifyStateChanged()
|
||||
}
|
||||
@get:Bindable
|
||||
var insets by observable(Insets.NONE, BR.insets)
|
||||
|
||||
var state by observable(initialState, BR.loading, BR.loaded, BR.loadFailed)
|
||||
|
||||
private val _viewEvents = MutableLiveData<ViewEvent>()
|
||||
private var runningJob: Job? = null
|
||||
@ -65,12 +66,6 @@ abstract class BaseViewModel(
|
||||
|
||||
protected open fun refresh(): Job? = null
|
||||
|
||||
open fun notifyStateChanged() {
|
||||
notifyPropertyChanged(BR.loading)
|
||||
notifyPropertyChanged(BR.loaded)
|
||||
notifyPropertyChanged(BR.loadingFailed)
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
override fun onCleared() {
|
||||
isConnected.removeOnPropertyChangedCallback(refreshCallback)
|
||||
@ -108,41 +103,4 @@ abstract class BaseViewModel(
|
||||
_viewEvents.postValue(NavigationWrapper(this))
|
||||
}
|
||||
|
||||
// The following is copied from androidx.databinding.BaseObservable
|
||||
|
||||
@Transient
|
||||
private var callbacks: PropertyChangeRegistry? = null
|
||||
|
||||
@Synchronized
|
||||
override fun addOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback) {
|
||||
if (callbacks == null) {
|
||||
callbacks = PropertyChangeRegistry()
|
||||
}
|
||||
callbacks?.add(callback)
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun removeOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback) {
|
||||
callbacks?.remove(callback)
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies listeners that all properties of this instance have changed.
|
||||
*/
|
||||
@Synchronized
|
||||
fun notifyChange() {
|
||||
callbacks?.notifyCallbacks(this, 0, null)
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies listeners that a specific property has changed. The getter for the property
|
||||
* that changes should be marked with [androidx.databinding.Bindable] to generate a field in
|
||||
* `BR` to be used as `fieldId`.
|
||||
*
|
||||
* @param fieldId The generated BR id for the Bindable field.
|
||||
*/
|
||||
fun notifyPropertyChanged(fieldId: Int) {
|
||||
callbacks?.notifyCallbacks(this, fieldId, null)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ import androidx.core.graphics.Insets
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.topjohnwu.magisk.ktx.value
|
||||
import com.topjohnwu.magisk.model.events.ActivityExecutor
|
||||
import com.topjohnwu.magisk.model.events.ContextExecutor
|
||||
import com.topjohnwu.magisk.model.events.FragmentExecutor
|
||||
@ -44,7 +43,7 @@ class CompatDelegate internal constructor(
|
||||
insets.asInsets()
|
||||
.also { view.peekSystemWindowInsets(it) }
|
||||
.let { view.consumeSystemWindowInsets(it) }
|
||||
?.also { view.viewModel.insets.value = it }
|
||||
?.also { view.viewModel.insets = it }
|
||||
?.subtractBy(insets) ?: insets
|
||||
}
|
||||
if (ViewCompat.isAttachedToWindow(view.viewRoot)) {
|
||||
|
@ -3,9 +3,10 @@ package com.topjohnwu.magisk.ui.flash
|
||||
import android.content.res.Resources
|
||||
import android.net.Uri
|
||||
import android.view.MenuItem
|
||||
import androidx.databinding.Bindable
|
||||
import androidx.databinding.ObservableArrayList
|
||||
import androidx.databinding.ObservableField
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.topjohnwu.magisk.BR
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.core.Config
|
||||
import com.topjohnwu.magisk.core.Const
|
||||
@ -19,6 +20,7 @@ import com.topjohnwu.magisk.model.events.SnackbarEvent
|
||||
import com.topjohnwu.magisk.ui.base.BaseViewModel
|
||||
import com.topjohnwu.magisk.ui.base.diffListOf
|
||||
import com.topjohnwu.magisk.ui.base.itemBindingOf
|
||||
import com.topjohnwu.magisk.utils.observable
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
@ -31,8 +33,10 @@ class FlashViewModel(
|
||||
private val resources: Resources
|
||||
) : BaseViewModel() {
|
||||
|
||||
val showReboot = ObservableField(Shell.rootAccess())
|
||||
val behaviorText = ObservableField(resources.getString(R.string.flashing))
|
||||
@get:Bindable
|
||||
var showReboot by observable(Shell.rootAccess(), BR.showReboot)
|
||||
@get:Bindable
|
||||
var behaviorText by observable(resources.getString(R.string.flashing), BR.behaviorText)
|
||||
|
||||
val adapter = BindingAdapter<ConsoleItem>()
|
||||
val items = diffListOf<ConsoleItem>()
|
||||
@ -59,7 +63,7 @@ class FlashViewModel(
|
||||
FlashZip(installer, outItems, logItems).exec()
|
||||
}
|
||||
Const.Value.UNINSTALL -> {
|
||||
showReboot.value = false
|
||||
showReboot = false
|
||||
FlashZip.Uninstall(installer, outItems, logItems).exec()
|
||||
}
|
||||
Const.Value.FLASH_MAGISK -> {
|
||||
@ -70,7 +74,7 @@ class FlashViewModel(
|
||||
}
|
||||
Const.Value.PATCH_FILE -> {
|
||||
uri ?: return@launch
|
||||
showReboot.value = false
|
||||
showReboot = false
|
||||
MagiskInstaller.Patch(installer, uri, outItems, logItems).exec()
|
||||
}
|
||||
else -> {
|
||||
@ -84,7 +88,7 @@ class FlashViewModel(
|
||||
|
||||
private fun onResult(success: Boolean) {
|
||||
state = if (success) State.LOADED else State.LOADING_FAILED
|
||||
behaviorText.value = when {
|
||||
behaviorText = when {
|
||||
success -> resources.getString(R.string.done)
|
||||
else -> resources.getString(R.string.failure)
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package com.topjohnwu.magisk.ui.hide
|
||||
|
||||
import android.content.pm.ApplicationInfo
|
||||
import androidx.databinding.Bindable
|
||||
import androidx.databinding.ObservableField
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.topjohnwu.magisk.BR
|
||||
import com.topjohnwu.magisk.core.utils.currentLocale
|
||||
@ -18,6 +17,7 @@ import com.topjohnwu.magisk.ui.base.BaseViewModel
|
||||
import com.topjohnwu.magisk.ui.base.Queryable
|
||||
import com.topjohnwu.magisk.ui.base.filterableListOf
|
||||
import com.topjohnwu.magisk.ui.base.itemBindingOf
|
||||
import com.topjohnwu.magisk.utils.observable
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
@ -28,21 +28,16 @@ class HideViewModel(
|
||||
|
||||
override val queryDelay = 1000L
|
||||
|
||||
var isShowSystem = false
|
||||
@Bindable get
|
||||
set(value) {
|
||||
field = value
|
||||
notifyPropertyChanged(BR.showSystem)
|
||||
submitQuery()
|
||||
}
|
||||
@get:Bindable
|
||||
var isShowSystem by observable(false, BR.showSystem) {
|
||||
submitQuery()
|
||||
}
|
||||
|
||||
@get:Bindable
|
||||
var query by observable("", BR.query) {
|
||||
submitQuery()
|
||||
}
|
||||
|
||||
var query = ""
|
||||
@Bindable get
|
||||
set(value) {
|
||||
field = value
|
||||
notifyPropertyChanged(BR.query)
|
||||
submitQuery()
|
||||
}
|
||||
val items = filterableListOf<HideItem>()
|
||||
val itemBinding = itemBindingOf<HideItem> {
|
||||
it.bindExtra(BR.viewModel, this)
|
||||
@ -51,8 +46,6 @@ class HideViewModel(
|
||||
it.bindExtra(BR.viewModel, this)
|
||||
}
|
||||
|
||||
val isFilterExpanded = ObservableField(false)
|
||||
|
||||
override fun refresh() = viewModelScope.launch {
|
||||
state = State.LOADING
|
||||
val apps = magiskRepo.fetchApps()
|
||||
@ -106,10 +99,5 @@ class HideViewModel(
|
||||
fun resetQuery() {
|
||||
query = ""
|
||||
}
|
||||
|
||||
fun hideFilter() {
|
||||
isFilterExpanded.value = false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
package com.topjohnwu.magisk.ui.home
|
||||
|
||||
import android.os.Build
|
||||
import androidx.databinding.ObservableField
|
||||
import androidx.databinding.Bindable
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.topjohnwu.magisk.BuildConfig
|
||||
import com.topjohnwu.magisk.R
|
||||
@ -27,6 +27,7 @@ import com.topjohnwu.magisk.model.events.dialog.ManagerInstallDialog
|
||||
import com.topjohnwu.magisk.model.events.dialog.UninstallDialog
|
||||
import com.topjohnwu.magisk.ui.base.BaseViewModel
|
||||
import com.topjohnwu.magisk.ui.base.itemBindingOf
|
||||
import com.topjohnwu.magisk.utils.observable
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import kotlinx.coroutines.launch
|
||||
import me.tatarka.bindingcollectionadapter2.BR
|
||||
@ -40,22 +41,26 @@ class HomeViewModel(
|
||||
private val repoMagisk: MagiskRepository
|
||||
) : BaseViewModel() {
|
||||
|
||||
val isNoticeVisible = ObservableField(Config.safetyNotice)
|
||||
|
||||
val stateMagisk = ObservableField(MagiskState.LOADING)
|
||||
val stateManager = ObservableField(MagiskState.LOADING)
|
||||
|
||||
val stateMagiskRemoteVersion = ObservableField(R.string.loading.res())
|
||||
val stateMagiskInstalledVersion get() =
|
||||
@get:Bindable
|
||||
var isNoticeVisible by observable(Config.safetyNotice, BR.noticeVisible)
|
||||
@get:Bindable
|
||||
var stateMagisk by observable(MagiskState.LOADING, BR.stateMagisk)
|
||||
@get:Bindable
|
||||
var stateManager by observable(MagiskState.LOADING, BR.stateManager)
|
||||
@get:Bindable
|
||||
var magiskRemoteVersion by observable(R.string.loading.res(), BR.magiskRemoteVersion)
|
||||
val magiskInstalledVersion get() =
|
||||
"${Info.env.magiskVersionString} (${Info.env.magiskVersionCode})"
|
||||
val stateMagiskMode get() = R.string.home_status_normal.res()
|
||||
val magiskMode get() = R.string.home_status_normal.res()
|
||||
|
||||
val stateManagerRemoteVersion = ObservableField(R.string.loading.res())
|
||||
val stateManagerInstalledVersion = Info.stub?.let {
|
||||
@get:Bindable
|
||||
var managerRemoteVersion by observable(R.string.loading.res(), BR.managerRemoteVersion)
|
||||
val managerInstalledVersion = Info.stub?.let {
|
||||
"${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE}) (${it.version})"
|
||||
} ?: "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})"
|
||||
val statePackageName = packageName
|
||||
val stateManagerProgress = ObservableField(0)
|
||||
@get:Bindable
|
||||
var stateManagerProgress by observable(0, BR.stateManagerProgress)
|
||||
|
||||
val items = listOf(DeveloperItem.Mainline, DeveloperItem.App, DeveloperItem.Project)
|
||||
val itemBinding = itemBindingOf<HomeItem> {
|
||||
@ -70,28 +75,28 @@ class HomeViewModel(
|
||||
init {
|
||||
RemoteFileService.progressBroadcast.observeForever {
|
||||
when (it?.second) {
|
||||
is Manager -> stateManagerProgress.value = it.first.times(100f).roundToInt()
|
||||
is Manager -> stateManagerProgress = it.first.times(100f).roundToInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun refresh() = viewModelScope.launch {
|
||||
repoMagisk.fetchUpdate()?.apply {
|
||||
stateMagisk.value = when {
|
||||
stateMagisk = when {
|
||||
!Info.env.isActive -> MagiskState.NOT_INSTALLED
|
||||
magisk.isObsolete -> MagiskState.OBSOLETE
|
||||
else -> MagiskState.UP_TO_DATE
|
||||
}
|
||||
|
||||
stateManager.value = when {
|
||||
stateManager = when {
|
||||
!app.isUpdateChannelCorrect && isConnected.value -> MagiskState.NOT_INSTALLED
|
||||
app.isObsolete -> MagiskState.OBSOLETE
|
||||
else -> MagiskState.UP_TO_DATE
|
||||
}
|
||||
|
||||
stateMagiskRemoteVersion.value =
|
||||
magiskRemoteVersion =
|
||||
"${magisk.version} (${magisk.versionCode})"
|
||||
stateManagerRemoteVersion.value =
|
||||
managerRemoteVersion =
|
||||
"${app.version} (${app.versionCode}) (${stub.versionCode})"
|
||||
|
||||
launch {
|
||||
@ -122,7 +127,7 @@ class HomeViewModel(
|
||||
|
||||
fun hideNotice() {
|
||||
Config.safetyNotice = false
|
||||
isNoticeVisible.value = false
|
||||
isNoticeVisible = false
|
||||
}
|
||||
|
||||
private suspend fun ensureEnv() {
|
||||
@ -133,7 +138,7 @@ class HomeViewModel(
|
||||
|
||||
// Don't bother checking env when magisk is not installed, loading or already has been shown
|
||||
if (
|
||||
invalidStates.any { it == stateMagisk.value } ||
|
||||
invalidStates.any { it == stateMagisk } ||
|
||||
shownDialog ||
|
||||
// don't care for emulators either
|
||||
Build.DEVICE.orEmpty().contains("generic") ||
|
||||
|
@ -4,7 +4,6 @@ import android.content.Intent
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.databinding.FragmentInstallMd2Binding
|
||||
import com.topjohnwu.magisk.ktx.value
|
||||
import com.topjohnwu.magisk.model.events.RequestFileEvent
|
||||
import com.topjohnwu.magisk.ui.base.BaseUIFragment
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
@ -16,7 +15,7 @@ class InstallFragment : BaseUIFragment<InstallViewModel, FragmentInstallMd2Bindi
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
viewModel.data.value = RequestFileEvent.resolve(requestCode, resultCode, data)
|
||||
viewModel.data = RequestFileEvent.resolve(requestCode, resultCode, data)
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
|
@ -2,21 +2,21 @@ package com.topjohnwu.magisk.ui.install
|
||||
|
||||
import android.net.Uri
|
||||
import android.widget.Toast
|
||||
import androidx.databinding.ObservableField
|
||||
import androidx.databinding.Bindable
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.topjohnwu.magisk.BR
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.download.DownloadService
|
||||
import com.topjohnwu.magisk.core.download.RemoteFileService
|
||||
import com.topjohnwu.magisk.core.utils.Utils
|
||||
import com.topjohnwu.magisk.data.repository.StringRepository
|
||||
import com.topjohnwu.magisk.ktx.addOnPropertyChangedCallback
|
||||
import com.topjohnwu.magisk.ktx.value
|
||||
import com.topjohnwu.magisk.model.entity.internal.Configuration
|
||||
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
|
||||
import com.topjohnwu.magisk.model.events.RequestFileEvent
|
||||
import com.topjohnwu.magisk.model.events.dialog.SecondSlotWarningDialog
|
||||
import com.topjohnwu.magisk.ui.base.BaseViewModel
|
||||
import com.topjohnwu.magisk.utils.observable
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.core.get
|
||||
@ -29,11 +29,26 @@ class InstallViewModel(
|
||||
val isRooted get() = Shell.rootAccess()
|
||||
val isAB get() = Info.isAB
|
||||
|
||||
val step = ObservableField(0)
|
||||
val method = ObservableField(-1)
|
||||
val progress = ObservableField(0)
|
||||
val data = ObservableField(null as Uri?)
|
||||
val notes = ObservableField("")
|
||||
@get:Bindable
|
||||
var step by observable(0, BR.step)
|
||||
@get:Bindable
|
||||
var method by observable(-1, BR.method) {
|
||||
when (it) {
|
||||
R.id.method_patch -> {
|
||||
Utils.toast(R.string.patch_file_msg, Toast.LENGTH_LONG)
|
||||
RequestFileEvent().publish()
|
||||
}
|
||||
R.id.method_inactive_slot -> {
|
||||
SecondSlotWarningDialog().publish()
|
||||
}
|
||||
}
|
||||
}
|
||||
@get:Bindable
|
||||
var progress by observable(0, BR.progress)
|
||||
@get:Bindable
|
||||
var data by observable(null as Uri?, BR.data)
|
||||
@get:Bindable
|
||||
var notes by observable("", BR.notes)
|
||||
|
||||
init {
|
||||
RemoteFileService.reset()
|
||||
@ -42,29 +57,18 @@ class InstallViewModel(
|
||||
if (subject !is DownloadSubject.Magisk) {
|
||||
return@observeForever
|
||||
}
|
||||
this.progress.value = progress.times(100).roundToInt()
|
||||
if (this.progress.value >= 100) {
|
||||
this.progress = progress.times(100).roundToInt()
|
||||
if (this.progress >= 100) {
|
||||
state = State.LOADED
|
||||
}
|
||||
}
|
||||
viewModelScope.launch {
|
||||
notes.value = stringRepo.getString(Info.remote.magisk.note)
|
||||
}
|
||||
method.addOnPropertyChangedCallback {
|
||||
when (it!!) {
|
||||
R.id.method_patch -> {
|
||||
Utils.toast(R.string.patch_file_msg, Toast.LENGTH_LONG)
|
||||
RequestFileEvent().publish()
|
||||
}
|
||||
R.id.method_inactive_slot -> {
|
||||
SecondSlotWarningDialog().publish()
|
||||
}
|
||||
}
|
||||
notes = stringRepo.getString(Info.remote.magisk.note)
|
||||
}
|
||||
}
|
||||
|
||||
fun step(nextStep: Int) {
|
||||
step.value = nextStep
|
||||
step = nextStep
|
||||
}
|
||||
|
||||
fun install() = DownloadService(get()) {
|
||||
@ -73,9 +77,9 @@ class InstallViewModel(
|
||||
|
||||
// ---
|
||||
|
||||
private fun resolveConfiguration() = when (method.value) {
|
||||
private fun resolveConfiguration() = when (method) {
|
||||
R.id.method_download -> Configuration.Download
|
||||
R.id.method_patch -> Configuration.Patch(data.value!!)
|
||||
R.id.method_patch -> Configuration.Patch(data!!)
|
||||
R.id.method_direct -> Configuration.Flash.Primary
|
||||
R.id.method_inactive_slot -> Configuration.Flash.Secondary
|
||||
else -> throw IllegalArgumentException("Unknown value")
|
||||
|
@ -1,21 +1,23 @@
|
||||
package com.topjohnwu.magisk.ui.log
|
||||
|
||||
import androidx.databinding.ObservableField
|
||||
import androidx.databinding.Bindable
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.topjohnwu.magisk.BR
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.core.Config
|
||||
import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.data.repository.LogRepository
|
||||
import com.topjohnwu.magisk.ktx.value
|
||||
import com.topjohnwu.magisk.model.entity.recycler.LogItem
|
||||
import com.topjohnwu.magisk.model.entity.recycler.TextItem
|
||||
import com.topjohnwu.magisk.model.events.SnackbarEvent
|
||||
import com.topjohnwu.magisk.ui.base.BaseViewModel
|
||||
import com.topjohnwu.magisk.ui.base.diffListOf
|
||||
import com.topjohnwu.magisk.ui.base.itemBindingOf
|
||||
import com.topjohnwu.magisk.utils.observable
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
@ -38,19 +40,15 @@ class LogViewModel(
|
||||
}
|
||||
|
||||
// --- magisk log
|
||||
|
||||
val consoleText = ObservableField(" ")
|
||||
@get:Bindable
|
||||
var consoleText by observable(" ", BR.consoleText)
|
||||
|
||||
override fun refresh() = viewModelScope.launch {
|
||||
consoleText.value = repo.fetchMagiskLogs()
|
||||
val deferred = withContext(Dispatchers.Default) {
|
||||
async {
|
||||
val suLogs = repo.fetchSuLogs().map { LogItem(it) }
|
||||
suLogs to items.calculateDiff(suLogs)
|
||||
}
|
||||
consoleText = repo.fetchMagiskLogs()
|
||||
val (suLogs, diff) = withContext(Dispatchers.Default) {
|
||||
val suLogs = repo.fetchSuLogs().map { LogItem(it) }
|
||||
suLogs to items.calculateDiff(suLogs)
|
||||
}
|
||||
delay(500)
|
||||
val (suLogs, diff) = deferred.await()
|
||||
items.firstOrNull()?.isTop = false
|
||||
items.lastOrNull()?.isBottom = false
|
||||
items.update(suLogs, diff)
|
||||
|
@ -15,7 +15,7 @@ import com.topjohnwu.magisk.ktx.hideKeyboard
|
||||
import com.topjohnwu.magisk.model.events.InstallExternalModuleEvent
|
||||
import com.topjohnwu.magisk.model.events.ViewEvent
|
||||
import com.topjohnwu.magisk.ui.MainActivity
|
||||
import com.topjohnwu.magisk.ui.ReselectionTarget
|
||||
import com.topjohnwu.magisk.ui.base.ReselectionTarget
|
||||
import com.topjohnwu.magisk.ui.base.BaseUIFragment
|
||||
import com.topjohnwu.magisk.utils.EndlessRecyclerScrollListener
|
||||
import com.topjohnwu.magisk.utils.MotionRevealHelper
|
||||
|
@ -2,7 +2,6 @@ package com.topjohnwu.magisk.ui.module
|
||||
|
||||
import androidx.databinding.Bindable
|
||||
import androidx.databinding.ObservableArrayList
|
||||
import androidx.databinding.ObservableField
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.topjohnwu.magisk.BR
|
||||
import com.topjohnwu.magisk.R
|
||||
@ -25,6 +24,7 @@ import com.topjohnwu.magisk.model.events.SnackbarEvent
|
||||
import com.topjohnwu.magisk.model.events.dialog.ModuleInstallDialog
|
||||
import com.topjohnwu.magisk.ui.base.*
|
||||
import com.topjohnwu.magisk.utils.EndlessRecyclerScrollListener
|
||||
import com.topjohnwu.magisk.utils.observable
|
||||
import kotlinx.coroutines.*
|
||||
import me.tatarka.bindingcollectionadapter2.collections.MergeObservableList
|
||||
import kotlin.math.roundToInt
|
||||
@ -53,18 +53,18 @@ class ModuleViewModel(
|
||||
private var queryJob: Job? = null
|
||||
private var remoteJob: Job? = null
|
||||
|
||||
var query = ""
|
||||
@Bindable get
|
||||
set(value) {
|
||||
if (field == value) return
|
||||
field = value
|
||||
notifyPropertyChanged(BR.query)
|
||||
submitQuery()
|
||||
// Yes we do lie about the search being loaded
|
||||
searchLoading.value = true
|
||||
}
|
||||
@get:Bindable
|
||||
var isRemoteLoading by observable(false, BR.remoteLoading)
|
||||
|
||||
val searchLoading = ObservableField(false)
|
||||
@get:Bindable
|
||||
var query by observable("", BR.query) {
|
||||
submitQuery()
|
||||
// Yes we do lie about the search being loaded
|
||||
searchLoading = true
|
||||
}
|
||||
|
||||
@get:Bindable
|
||||
var searchLoading by observable(false, BR.searchLoading)
|
||||
val itemsSearch = diffListOf<RepoItem>()
|
||||
val itemSearchBinding = itemBindingOf<RepoItem> {
|
||||
it.bindExtra(BR.viewModel, this)
|
||||
@ -80,13 +80,6 @@ class ModuleViewModel(
|
||||
private val itemsUpdatable = diffListOf<RepoItem.Update>()
|
||||
private val itemsRemote = diffListOf<RepoItem.Remote>()
|
||||
|
||||
var isRemoteLoading = false
|
||||
@Bindable get
|
||||
private set(value) {
|
||||
field = value
|
||||
notifyPropertyChanged(BR.remoteLoading)
|
||||
}
|
||||
|
||||
val adapter = adapterOf<ComparableRvItem<*>>()
|
||||
val items = MergeObservableList<ComparableRvItem<*>>()
|
||||
.insertItem(InstallModule)
|
||||
@ -261,7 +254,7 @@ class ModuleViewModel(
|
||||
val diff = withContext(Dispatchers.Default) {
|
||||
itemsSearch.calculateDiff(searched)
|
||||
}
|
||||
searchLoading.value = false
|
||||
searchLoading = false
|
||||
itemsSearch.update(searched, diff)
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,12 @@
|
||||
package com.topjohnwu.magisk.ui.safetynet
|
||||
|
||||
import androidx.databinding.Bindable
|
||||
import androidx.databinding.ObservableField
|
||||
import com.topjohnwu.magisk.BR
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.ktx.value
|
||||
import com.topjohnwu.magisk.model.events.CheckSafetyNetEvent
|
||||
import com.topjohnwu.magisk.ui.base.BaseViewModel
|
||||
import com.topjohnwu.magisk.ui.safetynet.SafetyNetState.*
|
||||
import com.topjohnwu.magisk.utils.observable
|
||||
import org.json.JSONObject
|
||||
|
||||
enum class SafetyNetState {
|
||||
@ -21,19 +20,23 @@ data class SafetyNetResult(
|
||||
|
||||
class SafetynetViewModel : BaseViewModel() {
|
||||
|
||||
private var currentState = IDLE
|
||||
set(value) {
|
||||
field = value
|
||||
notifyStateChanged()
|
||||
}
|
||||
val safetyNetTitle = ObservableField(R.string.empty)
|
||||
val ctsState = ObservableField(false)
|
||||
val basicIntegrityState = ObservableField(false)
|
||||
val evalType = ObservableField("")
|
||||
@get:Bindable
|
||||
var safetyNetTitle by observable(R.string.empty, BR.safetyNetTitle)
|
||||
@get:Bindable
|
||||
var ctsState by observable(false, BR.ctsState)
|
||||
@get:Bindable
|
||||
var basicIntegrityState by observable(false, BR.basicIntegrityState)
|
||||
@get:Bindable
|
||||
var evalType by observable("")
|
||||
|
||||
val isChecking @Bindable get() = currentState == LOADING
|
||||
val isFailed @Bindable get() = currentState == FAILED
|
||||
val isSuccess @Bindable get() = currentState == PASS
|
||||
@get:Bindable
|
||||
val isChecking get() = currentState == LOADING
|
||||
@get:Bindable
|
||||
val isFailed get() = currentState == FAILED
|
||||
@get:Bindable
|
||||
val isSuccess get() = currentState == PASS
|
||||
|
||||
private var currentState by observable(IDLE, BR.checking, BR.failed, BR.success)
|
||||
|
||||
init {
|
||||
cachedResult?.also {
|
||||
@ -41,13 +44,6 @@ class SafetynetViewModel : BaseViewModel() {
|
||||
} ?: attest()
|
||||
}
|
||||
|
||||
override fun notifyStateChanged() {
|
||||
super.notifyStateChanged()
|
||||
notifyPropertyChanged(BR.loading)
|
||||
notifyPropertyChanged(BR.failed)
|
||||
notifyPropertyChanged(BR.success)
|
||||
}
|
||||
|
||||
private fun attest() {
|
||||
currentState = LOADING
|
||||
CheckSafetyNetEvent() {
|
||||
@ -70,26 +66,26 @@ class SafetynetViewModel : BaseViewModel() {
|
||||
val eval = optString("evaluationType")
|
||||
val result = cts && basic
|
||||
cachedResult = this
|
||||
ctsState.value = cts
|
||||
basicIntegrityState.value = basic
|
||||
evalType.value = if (eval.contains("HARDWARE")) "HARDWARE" else "BASIC"
|
||||
ctsState = cts
|
||||
basicIntegrityState = basic
|
||||
evalType = if (eval.contains("HARDWARE")) "HARDWARE" else "BASIC"
|
||||
currentState = if (result) PASS else FAILED
|
||||
safetyNetTitle.value =
|
||||
safetyNetTitle =
|
||||
if (result) R.string.safetynet_attest_success
|
||||
else R.string.safetynet_attest_failure
|
||||
}.onFailure {
|
||||
currentState = FAILED
|
||||
ctsState.value = false
|
||||
basicIntegrityState.value = false
|
||||
evalType.value = "N/A"
|
||||
safetyNetTitle.value = R.string.safetynet_res_invalid
|
||||
ctsState = false
|
||||
basicIntegrityState = false
|
||||
evalType = "N/A"
|
||||
safetyNetTitle = R.string.safetynet_res_invalid
|
||||
}
|
||||
} ?: {
|
||||
currentState = FAILED
|
||||
ctsState.value = false
|
||||
basicIntegrityState.value = false
|
||||
evalType.value = "N/A"
|
||||
safetyNetTitle.value = R.string.safetynet_api_error
|
||||
ctsState = false
|
||||
basicIntegrityState = false
|
||||
evalType = "N/A"
|
||||
safetyNetTitle = R.string.safetynet_api_error
|
||||
}()
|
||||
}
|
||||
|
||||
|
@ -46,7 +46,7 @@ object Language : SettingsItem.Selector() {
|
||||
entryValues = values
|
||||
val selectedLocale = currentLocale.getDisplayName(currentLocale)
|
||||
value = names.indexOfFirst { it == selectedLocale }.let { if (it == -1) 0 else it }
|
||||
notifyChange(BR.selectedEntry)
|
||||
notifyPropertyChanged(BR.selectedEntry)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -79,8 +79,8 @@ object Hide : SettingsItem.Input() {
|
||||
override var value: String = resources.getString(R.string.re_app_name)
|
||||
set(value) {
|
||||
field = value
|
||||
notifyChange(BR.value)
|
||||
notifyChange(BR.error)
|
||||
notifyPropertyChanged(BR.value)
|
||||
notifyPropertyChanged(BR.error)
|
||||
}
|
||||
|
||||
@get:Bindable
|
||||
@ -112,8 +112,8 @@ object DownloadPath : SettingsItem.Input() {
|
||||
var result = value
|
||||
set(value) {
|
||||
field = value
|
||||
notifyChange(BR.result)
|
||||
notifyChange(BR.path)
|
||||
notifyPropertyChanged(BR.result)
|
||||
notifyPropertyChanged(BR.path)
|
||||
}
|
||||
|
||||
@get:Bindable
|
||||
@ -143,7 +143,7 @@ object UpdateChannelUrl : SettingsItem.Input() {
|
||||
var result = value
|
||||
set(value) {
|
||||
field = value
|
||||
notifyChange(BR.result)
|
||||
notifyPropertyChanged(BR.result)
|
||||
}
|
||||
|
||||
override fun refresh() {
|
||||
|
@ -6,8 +6,9 @@ import android.content.pm.PackageManager
|
||||
import android.content.res.Resources
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.CountDownTimer
|
||||
import androidx.databinding.ObservableField
|
||||
import androidx.databinding.Bindable
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.topjohnwu.magisk.BR
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.core.Config
|
||||
import com.topjohnwu.magisk.core.magiskdb.PolicyDao
|
||||
@ -15,10 +16,10 @@ import com.topjohnwu.magisk.core.model.MagiskPolicy.Companion.ALLOW
|
||||
import com.topjohnwu.magisk.core.model.MagiskPolicy.Companion.DENY
|
||||
import com.topjohnwu.magisk.core.su.SuRequestHandler
|
||||
import com.topjohnwu.magisk.core.utils.BiometricHelper
|
||||
import com.topjohnwu.magisk.ktx.value
|
||||
import com.topjohnwu.magisk.model.entity.recycler.SpinnerRvItem
|
||||
import com.topjohnwu.magisk.model.events.DieEvent
|
||||
import com.topjohnwu.magisk.ui.base.BaseViewModel
|
||||
import com.topjohnwu.magisk.utils.observable
|
||||
import com.topjohnwu.superuser.internal.UiThreadHandler
|
||||
import kotlinx.coroutines.launch
|
||||
import me.tatarka.bindingcollectionadapter2.BindingListViewAdapter
|
||||
@ -32,16 +33,20 @@ class SuRequestViewModel(
|
||||
private val res: Resources
|
||||
) : BaseViewModel() {
|
||||
|
||||
val icon = ObservableField<Drawable?>(null)
|
||||
val title = ObservableField("")
|
||||
val packageName = ObservableField("")
|
||||
|
||||
val denyText = ObservableField(res.getString(R.string.deny))
|
||||
val warningText = ObservableField<CharSequence>(res.getString(R.string.su_warning))
|
||||
|
||||
val selectedItemPosition = ObservableField(0)
|
||||
|
||||
val grantEnabled = ObservableField(false)
|
||||
@get:Bindable
|
||||
var icon by observable(null as Drawable?, BR.icon)
|
||||
@get:Bindable
|
||||
var title by observable("", BR.title)
|
||||
@get:Bindable
|
||||
var packageName by observable("", BR.packageName)
|
||||
@get:Bindable
|
||||
var denyText by observable(res.getString(R.string.deny), BR.denyText)
|
||||
@get:Bindable
|
||||
var warningText by observable(res.getString(R.string.su_warning), BR.warningText)
|
||||
@get:Bindable
|
||||
var selectedItemPosition by observable(0, BR.selectedItemPosition)
|
||||
@get:Bindable
|
||||
var grantEnabled by observable(false, BR.grantEnabled)
|
||||
|
||||
private val items = res.getStringArray(R.array.allow_timeout).map { SpinnerRvItem(it) }
|
||||
val adapter = BindingListViewAdapter<SpinnerRvItem>(1).apply {
|
||||
@ -89,7 +94,7 @@ class SuRequestViewModel(
|
||||
fun respond(action: Int) {
|
||||
timer.cancel()
|
||||
|
||||
val pos = selectedItemPosition.value
|
||||
val pos = selectedItemPosition
|
||||
timeoutPrefs.edit().putInt(policy.packageName, pos).apply()
|
||||
respond(action, Config.Value.TIMEOUT_LIST[pos])
|
||||
|
||||
@ -99,16 +104,16 @@ class SuRequestViewModel(
|
||||
|
||||
fun cancelTimer() {
|
||||
timer.cancel()
|
||||
denyText.value = res.getString(R.string.deny)
|
||||
denyText = res.getString(R.string.deny)
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
icon.value = policy.applicationInfo.loadIcon(pm)
|
||||
title.value = policy.appName
|
||||
packageName.value = policy.packageName
|
||||
icon = policy.applicationInfo.loadIcon(pm)
|
||||
title = policy.appName
|
||||
packageName = policy.packageName
|
||||
UiThreadHandler.handler.post {
|
||||
// Delay is required to properly do selection
|
||||
selectedItemPosition.value = timeoutPrefs.getInt(policy.packageName, 0)
|
||||
selectedItemPosition = timeoutPrefs.getInt(policy.packageName, 0)
|
||||
}
|
||||
|
||||
// Set timer
|
||||
@ -122,14 +127,14 @@ class SuRequestViewModel(
|
||||
) : CountDownTimer(millis, interval) {
|
||||
|
||||
override fun onTick(remains: Long) {
|
||||
if (!grantEnabled.value && remains <= millis - 1000) {
|
||||
grantEnabled.value = true
|
||||
if (!grantEnabled && remains <= millis - 1000) {
|
||||
grantEnabled = true
|
||||
}
|
||||
denyText.value = "${res.getString(R.string.deny)} (${(remains / 1000) + 1})"
|
||||
denyText = "${res.getString(R.string.deny)} (${(remains / 1000) + 1})"
|
||||
}
|
||||
|
||||
override fun onFinish() {
|
||||
denyText.value = res.getString(R.string.deny)
|
||||
denyText = res.getString(R.string.deny)
|
||||
respond(DENY)
|
||||
}
|
||||
|
||||
|
170
app/src/main/java/com/topjohnwu/magisk/utils/ObservableHost.kt
Normal file
170
app/src/main/java/com/topjohnwu/magisk/utils/ObservableHost.kt
Normal file
@ -0,0 +1,170 @@
|
||||
package com.topjohnwu.magisk.utils
|
||||
|
||||
import androidx.databinding.Observable
|
||||
import androidx.databinding.PropertyChangeRegistry
|
||||
import kotlin.properties.ReadWriteProperty
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
/**
|
||||
* Modified from https://github.com/skoumalcz/teanity/blob/1.2/core/src/main/java/com/skoumal/teanity/observable/Notifyable.kt
|
||||
*
|
||||
* Interface that allows user to be observed via DataBinding or manually by assigning listeners.
|
||||
*
|
||||
* @see [androidx.databinding.Observable]
|
||||
* */
|
||||
interface ObservableHost : Observable {
|
||||
|
||||
/**
|
||||
* Notifies all observers that something has changed. By default implementation this method is
|
||||
* synchronous, hence observers will never be notified in undefined order. Observers might
|
||||
* choose to refresh the view completely, which is beyond the scope of this function.
|
||||
* */
|
||||
fun notifyChange(host: Observable = this)
|
||||
|
||||
/**
|
||||
* Notifies all observers about field with [fieldId] has been changed. This will happen
|
||||
* synchronously before or after [notifyChange] has been called. It will never be called during
|
||||
* the execution of aforementioned method.
|
||||
* */
|
||||
fun notifyPropertyChanged(fieldId: Int, host: Observable = this)
|
||||
|
||||
companion object {
|
||||
|
||||
val impl: ObservableHost get() = ObservableHostImpl()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private class ObservableHostImpl : ObservableHost {
|
||||
|
||||
private var callbacks: PropertyChangeRegistry? = null
|
||||
|
||||
override fun addOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback) {
|
||||
synchronized(this) {
|
||||
callbacks ?: PropertyChangeRegistry().also { callbacks = it }
|
||||
}.add(callback)
|
||||
}
|
||||
|
||||
override fun removeOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback) {
|
||||
synchronized(this) {
|
||||
callbacks ?: return
|
||||
}.remove(callback)
|
||||
}
|
||||
|
||||
override fun notifyChange(host: Observable) {
|
||||
synchronized(this) {
|
||||
callbacks ?: return
|
||||
}.notifyCallbacks(host, 0, null)
|
||||
}
|
||||
|
||||
override fun notifyPropertyChanged(fieldId: Int, host: Observable) {
|
||||
synchronized(this) {
|
||||
callbacks ?: return
|
||||
}.notifyCallbacks(host, fieldId, null)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Declares delegated property in [ObservableHost] parent. This property is available for DataBinding
|
||||
* to be observed as usual. The only caveat is that in order for binding to generate the [fieldId]
|
||||
* it has to be annotated accordingly.
|
||||
*
|
||||
* The annotation however give very strict control over your internal fields and overall reduce
|
||||
* overhead in notifying observers. (In comparison to [androidx.databinding.ObservableField])
|
||||
* It helps the kotlin code to feel more,... _native_, while respecting the original functionality.
|
||||
*
|
||||
* # Examples:
|
||||
*
|
||||
* ## The most basic usage would probably be:
|
||||
* ```kotlin
|
||||
* @get:Bindable
|
||||
* var myField by observable(defaultValue, BR.myField)
|
||||
* private set
|
||||
* ```
|
||||
*
|
||||
* ## You can use the field as public read/write, of course:
|
||||
* ```kotlin
|
||||
* @get:Bindable
|
||||
* var myField by observable(defaultValue, BR.myField)
|
||||
* ```
|
||||
*
|
||||
* ## Please beware that delegated property instantiates one class per property
|
||||
* We discourage using simple getters via delegated properties. Instead you can do something like
|
||||
* this:
|
||||
*
|
||||
* ```kotlin
|
||||
* @get:Bindable
|
||||
* var myField by observable(defaultValue, BR.myField, BR.myTransformedField)
|
||||
*
|
||||
* var myTransformedField
|
||||
* @Bindable get() {
|
||||
* return myField.transform()
|
||||
* }
|
||||
* set(value) {
|
||||
* myField = value.transform()
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* */
|
||||
|
||||
// Optimize for the most common use case
|
||||
// Generic type is reified to optimize primitive types
|
||||
inline fun <reified T> ObservableHost.observable(
|
||||
initialValue: T,
|
||||
fieldId: Int
|
||||
) = object : ReadWriteProperty<ObservableHost, T> {
|
||||
private var field = initialValue
|
||||
|
||||
override fun getValue(thisRef: ObservableHost, property: KProperty<*>): T {
|
||||
return field
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun setValue(thisRef: ObservableHost, property: KProperty<*>, value: T) {
|
||||
if (field != value) {
|
||||
field = value
|
||||
notifyPropertyChanged(fieldId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <reified T> ObservableHost.observable(
|
||||
initialValue: T,
|
||||
vararg fieldIds: Int
|
||||
) = object : ReadWriteProperty<ObservableHost, T> {
|
||||
private var field = initialValue
|
||||
|
||||
override fun getValue(thisRef: ObservableHost, property: KProperty<*>): T {
|
||||
return field
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun setValue(thisRef: ObservableHost, property: KProperty<*>, value: T) {
|
||||
if (field != value) {
|
||||
field = value
|
||||
fieldIds.forEach { notifyPropertyChanged(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <reified T> ObservableHost.observable(
|
||||
initialValue: T,
|
||||
vararg fieldIds: Int,
|
||||
crossinline afterChanged: (T) -> Unit
|
||||
) = object : ReadWriteProperty<ObservableHost, T> {
|
||||
private var field = initialValue
|
||||
|
||||
override fun getValue(thisRef: ObservableHost, property: KProperty<*>): T {
|
||||
return field
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun setValue(thisRef: ObservableHost, property: KProperty<*>, value: T) {
|
||||
if (field != value) {
|
||||
field = value
|
||||
fieldIds.forEach { notifyPropertyChanged(it) }
|
||||
afterChanged(value)
|
||||
}
|
||||
}
|
||||
}
|
@ -132,7 +132,7 @@
|
||||
|
||||
<TextView
|
||||
style="@style/W.Home.ItemContent.Right"
|
||||
android:text="@{viewModel.isConnected ? viewModel.stateMagiskRemoteVersion : @string/not_available}"
|
||||
android:text="@{viewModel.isConnected ? viewModel.magiskRemoteVersion : @string/not_available}"
|
||||
tools:text="20.1 (12345)" />
|
||||
|
||||
</LinearLayout>
|
||||
@ -149,7 +149,7 @@
|
||||
|
||||
<TextView
|
||||
style="@style/W.Home.ItemContent.Right"
|
||||
android:text="@{Info.env.isActive ? viewModel.stateMagiskInstalledVersion : @string/not_available}"
|
||||
android:text="@{Info.env.isActive ? viewModel.magiskInstalledVersion : @string/not_available}"
|
||||
tools:text="20.1 (12345)" />
|
||||
|
||||
</LinearLayout>
|
||||
@ -166,7 +166,7 @@
|
||||
|
||||
<TextView
|
||||
style="@style/W.Home.ItemContent.Right"
|
||||
android:text="@{Info.env.isActive ? viewModel.stateMagiskMode : @string/not_available}"
|
||||
android:text="@{Info.env.isActive ? viewModel.magiskMode : @string/not_available}"
|
||||
tools:text="Normal" />
|
||||
|
||||
</LinearLayout>
|
||||
|
@ -121,7 +121,7 @@
|
||||
|
||||
<TextView
|
||||
style="@style/W.Home.ItemContent.Right"
|
||||
android:text="@{viewModel.isConnected ? viewModel.stateManagerRemoteVersion : @string/not_available}"
|
||||
android:text="@{viewModel.isConnected ? viewModel.managerRemoteVersion : @string/not_available}"
|
||||
tools:text="8.0.0 (123) (10)" />
|
||||
|
||||
</LinearLayout>
|
||||
@ -138,7 +138,7 @@
|
||||
|
||||
<TextView
|
||||
style="@style/W.Home.ItemContent.Right"
|
||||
android:text="@{viewModel.stateManagerInstalledVersion}"
|
||||
android:text="@{viewModel.managerInstalledVersion}"
|
||||
tools:text="8.0.0 (123) (10)" />
|
||||
|
||||
</LinearLayout>
|
||||
|
@ -25,6 +25,7 @@ org.gradle.daemon=true
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
android.enableR8.fullMode=true
|
||||
android.databinding.incremental=true
|
||||
|
||||
android.injected.testOnly=false
|
||||
kapt.incremental.apt=true
|
||||
kapt.incremental.apt=true
|
||||
|
Loading…
x
Reference in New Issue
Block a user