mirror of
https://github.com/topjohnwu/Magisk.git
synced 2024-12-22 16:07: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 androidx.databinding.ViewDataBinding
|
||||||
import com.topjohnwu.magisk.BR
|
import com.topjohnwu.magisk.BR
|
||||||
import com.topjohnwu.magisk.utils.DiffObservableList
|
import com.topjohnwu.magisk.utils.DiffObservableList
|
||||||
|
import com.topjohnwu.magisk.utils.ObservableHost
|
||||||
import me.tatarka.bindingcollectionadapter2.ItemBinding
|
import me.tatarka.bindingcollectionadapter2.ItemBinding
|
||||||
|
|
||||||
abstract class RvItem {
|
abstract class RvItem {
|
||||||
@ -46,3 +47,5 @@ abstract class ComparableRvItem<in T> : RvItem() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 androidx.databinding.Bindable
|
||||||
import com.topjohnwu.magisk.BR
|
import com.topjohnwu.magisk.BR
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.databinding.ObservableItem
|
||||||
import com.topjohnwu.magisk.ktx.timeDateFormat
|
import com.topjohnwu.magisk.ktx.timeDateFormat
|
||||||
import com.topjohnwu.magisk.ktx.toTime
|
import com.topjohnwu.magisk.ktx.toTime
|
||||||
import com.topjohnwu.magisk.model.entity.MagiskLog
|
import com.topjohnwu.magisk.model.entity.MagiskLog
|
||||||
@ -16,13 +17,13 @@ class LogItem(val item: MagiskLog) : ObservableItem<LogItem>() {
|
|||||||
@Bindable get
|
@Bindable get
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
notifyChange(BR.top)
|
notifyPropertyChanged(BR.top)
|
||||||
}
|
}
|
||||||
var isBottom = false
|
var isBottom = false
|
||||||
@Bindable get
|
@Bindable get
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
notifyChange(BR.bottom)
|
notifyPropertyChanged(BR.bottom)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun itemSameAs(other: LogItem) = item.appName == other.item.appName
|
override fun itemSameAs(other: LogItem) = item.appName == other.item.appName
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
package com.topjohnwu.magisk.model.entity.recycler
|
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 androidx.recyclerview.widget.StaggeredGridLayoutManager
|
||||||
import com.topjohnwu.magisk.BR
|
import com.topjohnwu.magisk.BR
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.core.model.module.Module
|
import com.topjohnwu.magisk.core.model.module.Module
|
||||||
import com.topjohnwu.magisk.core.model.module.Repo
|
import com.topjohnwu.magisk.core.model.module.Repo
|
||||||
import com.topjohnwu.magisk.databinding.ComparableRvItem
|
import com.topjohnwu.magisk.databinding.ComparableRvItem
|
||||||
|
import com.topjohnwu.magisk.databinding.ObservableItem
|
||||||
import com.topjohnwu.magisk.ui.module.ModuleViewModel
|
import com.topjohnwu.magisk.ui.module.ModuleViewModel
|
||||||
|
|
||||||
object InstallModule : ComparableRvItem<InstallModule>() {
|
object InstallModule : ComparableRvItem<InstallModule>() {
|
||||||
@ -33,19 +37,19 @@ class SectionTitle(
|
|||||||
@Bindable get
|
@Bindable get
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
notifyChange(BR.button)
|
notifyPropertyChanged(BR.button)
|
||||||
}
|
}
|
||||||
var icon = _icon
|
var icon = _icon
|
||||||
@Bindable get
|
@Bindable get
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
notifyChange(BR.icon)
|
notifyPropertyChanged(BR.icon)
|
||||||
}
|
}
|
||||||
var hasButton = button != 0 || icon != 0
|
var hasButton = button != 0 || icon != 0
|
||||||
@Bindable get
|
@Bindable get
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
notifyChange(BR.hasButton)
|
notifyPropertyChanged(BR.hasButton)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindingBound(binding: ViewDataBinding) {
|
override fun onBindingBound(binding: ViewDataBinding) {
|
||||||
@ -66,7 +70,7 @@ sealed class RepoItem(val item: Repo) : ObservableItem<RepoItem>() {
|
|||||||
@Bindable get
|
@Bindable get
|
||||||
protected set(value) {
|
protected set(value) {
|
||||||
field = value
|
field = value
|
||||||
notifyChange(BR.update)
|
notifyPropertyChanged(BR.update)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun contentSameAs(other: RepoItem): Boolean = item == other.item
|
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
|
var repo: Repo? = null
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
notifyChange(BR.repo)
|
notifyPropertyChanged(BR.repo)
|
||||||
}
|
}
|
||||||
|
|
||||||
@get:Bindable
|
@get:Bindable
|
||||||
@ -97,7 +101,7 @@ class ModuleItem(val item: Module) : ObservableItem<ModuleItem>(), Observable {
|
|||||||
get() = item.enable
|
get() = item.enable
|
||||||
set(value) {
|
set(value) {
|
||||||
item.enable = value
|
item.enable = value
|
||||||
notifyChange(BR.enabled)
|
notifyPropertyChanged(BR.enabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
@get:Bindable
|
@get:Bindable
|
||||||
@ -105,7 +109,7 @@ class ModuleItem(val item: Module) : ObservableItem<ModuleItem>(), Observable {
|
|||||||
get() = item.remove
|
get() = item.remove
|
||||||
set(value) {
|
set(value) {
|
||||||
item.remove = value
|
item.remove = value
|
||||||
notifyChange(BR.removed)
|
notifyPropertyChanged(BR.removed)
|
||||||
}
|
}
|
||||||
|
|
||||||
val isUpdated get() = item.updated
|
val isUpdated get() = item.updated
|
||||||
@ -126,21 +130,5 @@ class ModuleItem(val item: Module) : ObservableItem<ModuleItem>(), Observable {
|
|||||||
&& item.name == other.item.name
|
&& item.name == other.item.name
|
||||||
|
|
||||||
override fun itemSameAs(other: ModuleItem): Boolean = item.id == other.item.id
|
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 androidx.recyclerview.widget.StaggeredGridLayoutManager
|
||||||
import com.topjohnwu.magisk.BR
|
import com.topjohnwu.magisk.BR
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.databinding.ObservableItem
|
||||||
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.KoinComponent
|
||||||
@ -37,7 +38,7 @@ sealed class SettingsItem : ObservableItem<SettingsItem>() {
|
|||||||
|
|
||||||
// notify only after the callback invocation; callback can invalidate the backing data,
|
// notify only after the callback invocation; callback can invalidate the backing data,
|
||||||
// which wouldn't be recognized with reverse approach
|
// which wouldn't be recognized with reverse approach
|
||||||
notifyChange(BR.description)
|
notifyPropertyChanged(BR.description)
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun refresh() {}
|
open fun refresh() {}
|
||||||
@ -60,7 +61,7 @@ sealed class SettingsItem : ObservableItem<SettingsItem>() {
|
|||||||
) = object : ObservableProperty<T>(initialValue) {
|
) = object : ObservableProperty<T>(initialValue) {
|
||||||
override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) {
|
override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) {
|
||||||
setter(newValue)
|
setter(newValue)
|
||||||
notifyChange(fieldId)
|
notifyPropertyChanged(fieldId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,7 +170,7 @@ sealed class SettingsItem : ObservableItem<SettingsItem>() {
|
|||||||
}
|
}
|
||||||
.applyAdapter(entries) {
|
.applyAdapter(entries) {
|
||||||
value = it
|
value = it
|
||||||
notifyChange(BR.selectedEntry)
|
notifyPropertyChanged(BR.selectedEntry)
|
||||||
super.onPressed(view, callback)
|
super.onPressed(view, callback)
|
||||||
}
|
}
|
||||||
.reveal()
|
.reveal()
|
||||||
|
@ -18,6 +18,8 @@ import com.topjohnwu.magisk.core.Info
|
|||||||
import com.topjohnwu.magisk.databinding.ActivityMainMd2Binding
|
import com.topjohnwu.magisk.databinding.ActivityMainMd2Binding
|
||||||
import com.topjohnwu.magisk.ktx.startAnimations
|
import com.topjohnwu.magisk.ktx.startAnimations
|
||||||
import com.topjohnwu.magisk.ui.base.BaseUIActivity
|
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.ui.home.HomeFragmentDirections
|
||||||
import com.topjohnwu.magisk.utils.HideBottomViewOnScrollBehavior
|
import com.topjohnwu.magisk.utils.HideBottomViewOnScrollBehavior
|
||||||
import com.topjohnwu.magisk.utils.HideTopViewOnScrollBehavior
|
import com.topjohnwu.magisk.utils.HideTopViewOnScrollBehavior
|
||||||
@ -26,6 +28,8 @@ import com.topjohnwu.magisk.view.MagiskDialog
|
|||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||||
|
|
||||||
|
class MainViewModel : BaseViewModel()
|
||||||
|
|
||||||
open class MainActivity : BaseUIActivity<MainViewModel, ActivityMainMd2Binding>() {
|
open class MainActivity : BaseUIActivity<MainViewModel, ActivityMainMd2Binding>() {
|
||||||
|
|
||||||
override val layoutRes = R.layout.activity_main_md2
|
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.core.base.BaseActivity
|
||||||
import com.topjohnwu.magisk.ktx.snackbar
|
import com.topjohnwu.magisk.ktx.snackbar
|
||||||
import com.topjohnwu.magisk.ktx.startAnimations
|
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.EventHandler
|
||||||
import com.topjohnwu.magisk.model.events.SnackbarEvent
|
import com.topjohnwu.magisk.model.events.SnackbarEvent
|
||||||
import com.topjohnwu.magisk.model.events.ViewEvent
|
import com.topjohnwu.magisk.model.events.ViewEvent
|
||||||
@ -116,7 +115,7 @@ abstract class BaseUIActivity<ViewModel : BaseViewModel, Binding : ViewDataBindi
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun peekSystemWindowInsets(insets: Insets) {
|
override fun peekSystemWindowInsets(insets: Insets) {
|
||||||
viewModel.insets.value = insets
|
viewModel.insets = insets
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun ViewEvent.dispatchOnSelf() = onEventDispatched(this)
|
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.core.graphics.Insets
|
||||||
import androidx.databinding.Bindable
|
import androidx.databinding.Bindable
|
||||||
import androidx.databinding.Observable
|
import androidx.databinding.Observable
|
||||||
import androidx.databinding.ObservableField
|
|
||||||
import androidx.databinding.PropertyChangeRegistry
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
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.core.base.BaseActivity
|
||||||
import com.topjohnwu.magisk.model.events.*
|
import com.topjohnwu.magisk.model.events.*
|
||||||
import com.topjohnwu.magisk.model.navigation.NavigationWrapper
|
import com.topjohnwu.magisk.model.navigation.NavigationWrapper
|
||||||
|
import com.topjohnwu.magisk.utils.ObservableHost
|
||||||
|
import com.topjohnwu.magisk.utils.observable
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import org.koin.core.KoinComponent
|
import org.koin.core.KoinComponent
|
||||||
|
|
||||||
abstract class BaseViewModel(
|
abstract class BaseViewModel(
|
||||||
initialState: State = State.LOADING
|
initialState: State = State.LOADING
|
||||||
) : ViewModel(), Observable, KoinComponent {
|
) : ViewModel(), ObservableHost by ObservableHost.impl, KoinComponent {
|
||||||
|
|
||||||
enum class State {
|
enum class State {
|
||||||
LOADED, LOADING, LOADING_FAILED
|
LOADED, LOADING, LOADING_FAILED
|
||||||
}
|
}
|
||||||
|
|
||||||
val loading @Bindable get() = state == State.LOADING
|
@get:Bindable
|
||||||
val loaded @Bindable get() = state == State.LOADED
|
val loading get() = state == State.LOADING
|
||||||
val loadingFailed @Bindable get() = state == State.LOADING_FAILED
|
@get:Bindable
|
||||||
|
val loaded get() = state == State.LOADED
|
||||||
|
@get:Bindable
|
||||||
|
val loadFailed get() = state == State.LOADING_FAILED
|
||||||
|
|
||||||
val isConnected get() = Info.isConnected
|
val isConnected get() = Info.isConnected
|
||||||
val viewEvents: LiveData<ViewEvent> get() = _viewEvents
|
val viewEvents: LiveData<ViewEvent> get() = _viewEvents
|
||||||
val insets = ObservableField(Insets.NONE)
|
|
||||||
|
|
||||||
var state: State = initialState
|
@get:Bindable
|
||||||
set(value) {
|
var insets by observable(Insets.NONE, BR.insets)
|
||||||
field = value
|
|
||||||
notifyStateChanged()
|
var state by observable(initialState, BR.loading, BR.loaded, BR.loadFailed)
|
||||||
}
|
|
||||||
|
|
||||||
private val _viewEvents = MutableLiveData<ViewEvent>()
|
private val _viewEvents = MutableLiveData<ViewEvent>()
|
||||||
private var runningJob: Job? = null
|
private var runningJob: Job? = null
|
||||||
@ -65,12 +66,6 @@ abstract class BaseViewModel(
|
|||||||
|
|
||||||
protected open fun refresh(): Job? = null
|
protected open fun refresh(): Job? = null
|
||||||
|
|
||||||
open fun notifyStateChanged() {
|
|
||||||
notifyPropertyChanged(BR.loading)
|
|
||||||
notifyPropertyChanged(BR.loaded)
|
|
||||||
notifyPropertyChanged(BR.loadingFailed)
|
|
||||||
}
|
|
||||||
|
|
||||||
@CallSuper
|
@CallSuper
|
||||||
override fun onCleared() {
|
override fun onCleared() {
|
||||||
isConnected.removeOnPropertyChangedCallback(refreshCallback)
|
isConnected.removeOnPropertyChangedCallback(refreshCallback)
|
||||||
@ -108,41 +103,4 @@ abstract class BaseViewModel(
|
|||||||
_viewEvents.postValue(NavigationWrapper(this))
|
_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.ViewCompat
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import com.topjohnwu.magisk.ktx.value
|
|
||||||
import com.topjohnwu.magisk.model.events.ActivityExecutor
|
import com.topjohnwu.magisk.model.events.ActivityExecutor
|
||||||
import com.topjohnwu.magisk.model.events.ContextExecutor
|
import com.topjohnwu.magisk.model.events.ContextExecutor
|
||||||
import com.topjohnwu.magisk.model.events.FragmentExecutor
|
import com.topjohnwu.magisk.model.events.FragmentExecutor
|
||||||
@ -44,7 +43,7 @@ class CompatDelegate internal constructor(
|
|||||||
insets.asInsets()
|
insets.asInsets()
|
||||||
.also { view.peekSystemWindowInsets(it) }
|
.also { view.peekSystemWindowInsets(it) }
|
||||||
.let { view.consumeSystemWindowInsets(it) }
|
.let { view.consumeSystemWindowInsets(it) }
|
||||||
?.also { view.viewModel.insets.value = it }
|
?.also { view.viewModel.insets = it }
|
||||||
?.subtractBy(insets) ?: insets
|
?.subtractBy(insets) ?: insets
|
||||||
}
|
}
|
||||||
if (ViewCompat.isAttachedToWindow(view.viewRoot)) {
|
if (ViewCompat.isAttachedToWindow(view.viewRoot)) {
|
||||||
|
@ -3,9 +3,10 @@ package com.topjohnwu.magisk.ui.flash
|
|||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
|
import androidx.databinding.Bindable
|
||||||
import androidx.databinding.ObservableArrayList
|
import androidx.databinding.ObservableArrayList
|
||||||
import androidx.databinding.ObservableField
|
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.topjohnwu.magisk.BR
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.core.Config
|
import com.topjohnwu.magisk.core.Config
|
||||||
import com.topjohnwu.magisk.core.Const
|
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.BaseViewModel
|
||||||
import com.topjohnwu.magisk.ui.base.diffListOf
|
import com.topjohnwu.magisk.ui.base.diffListOf
|
||||||
import com.topjohnwu.magisk.ui.base.itemBindingOf
|
import com.topjohnwu.magisk.ui.base.itemBindingOf
|
||||||
|
import com.topjohnwu.magisk.utils.observable
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@ -31,8 +33,10 @@ class FlashViewModel(
|
|||||||
private val resources: Resources
|
private val resources: Resources
|
||||||
) : BaseViewModel() {
|
) : BaseViewModel() {
|
||||||
|
|
||||||
val showReboot = ObservableField(Shell.rootAccess())
|
@get:Bindable
|
||||||
val behaviorText = ObservableField(resources.getString(R.string.flashing))
|
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 adapter = BindingAdapter<ConsoleItem>()
|
||||||
val items = diffListOf<ConsoleItem>()
|
val items = diffListOf<ConsoleItem>()
|
||||||
@ -59,7 +63,7 @@ class FlashViewModel(
|
|||||||
FlashZip(installer, outItems, logItems).exec()
|
FlashZip(installer, outItems, logItems).exec()
|
||||||
}
|
}
|
||||||
Const.Value.UNINSTALL -> {
|
Const.Value.UNINSTALL -> {
|
||||||
showReboot.value = false
|
showReboot = false
|
||||||
FlashZip.Uninstall(installer, outItems, logItems).exec()
|
FlashZip.Uninstall(installer, outItems, logItems).exec()
|
||||||
}
|
}
|
||||||
Const.Value.FLASH_MAGISK -> {
|
Const.Value.FLASH_MAGISK -> {
|
||||||
@ -70,7 +74,7 @@ class FlashViewModel(
|
|||||||
}
|
}
|
||||||
Const.Value.PATCH_FILE -> {
|
Const.Value.PATCH_FILE -> {
|
||||||
uri ?: return@launch
|
uri ?: return@launch
|
||||||
showReboot.value = false
|
showReboot = false
|
||||||
MagiskInstaller.Patch(installer, uri, outItems, logItems).exec()
|
MagiskInstaller.Patch(installer, uri, outItems, logItems).exec()
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
@ -84,7 +88,7 @@ class FlashViewModel(
|
|||||||
|
|
||||||
private fun onResult(success: Boolean) {
|
private fun onResult(success: Boolean) {
|
||||||
state = if (success) State.LOADED else State.LOADING_FAILED
|
state = if (success) State.LOADED else State.LOADING_FAILED
|
||||||
behaviorText.value = when {
|
behaviorText = when {
|
||||||
success -> resources.getString(R.string.done)
|
success -> resources.getString(R.string.done)
|
||||||
else -> resources.getString(R.string.failure)
|
else -> resources.getString(R.string.failure)
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ package com.topjohnwu.magisk.ui.hide
|
|||||||
|
|
||||||
import android.content.pm.ApplicationInfo
|
import android.content.pm.ApplicationInfo
|
||||||
import androidx.databinding.Bindable
|
import androidx.databinding.Bindable
|
||||||
import androidx.databinding.ObservableField
|
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.topjohnwu.magisk.BR
|
import com.topjohnwu.magisk.BR
|
||||||
import com.topjohnwu.magisk.core.utils.currentLocale
|
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.Queryable
|
||||||
import com.topjohnwu.magisk.ui.base.filterableListOf
|
import com.topjohnwu.magisk.ui.base.filterableListOf
|
||||||
import com.topjohnwu.magisk.ui.base.itemBindingOf
|
import com.topjohnwu.magisk.ui.base.itemBindingOf
|
||||||
|
import com.topjohnwu.magisk.utils.observable
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
@ -28,21 +28,16 @@ class HideViewModel(
|
|||||||
|
|
||||||
override val queryDelay = 1000L
|
override val queryDelay = 1000L
|
||||||
|
|
||||||
var isShowSystem = false
|
@get:Bindable
|
||||||
@Bindable get
|
var isShowSystem by observable(false, BR.showSystem) {
|
||||||
set(value) {
|
submitQuery()
|
||||||
field = value
|
}
|
||||||
notifyPropertyChanged(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 items = filterableListOf<HideItem>()
|
||||||
val itemBinding = itemBindingOf<HideItem> {
|
val itemBinding = itemBindingOf<HideItem> {
|
||||||
it.bindExtra(BR.viewModel, this)
|
it.bindExtra(BR.viewModel, this)
|
||||||
@ -51,8 +46,6 @@ class HideViewModel(
|
|||||||
it.bindExtra(BR.viewModel, this)
|
it.bindExtra(BR.viewModel, this)
|
||||||
}
|
}
|
||||||
|
|
||||||
val isFilterExpanded = ObservableField(false)
|
|
||||||
|
|
||||||
override fun refresh() = viewModelScope.launch {
|
override fun refresh() = viewModelScope.launch {
|
||||||
state = State.LOADING
|
state = State.LOADING
|
||||||
val apps = magiskRepo.fetchApps()
|
val apps = magiskRepo.fetchApps()
|
||||||
@ -106,10 +99,5 @@ class HideViewModel(
|
|||||||
fun resetQuery() {
|
fun resetQuery() {
|
||||||
query = ""
|
query = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hideFilter() {
|
|
||||||
isFilterExpanded.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package com.topjohnwu.magisk.ui.home
|
package com.topjohnwu.magisk.ui.home
|
||||||
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.databinding.ObservableField
|
import androidx.databinding.Bindable
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.topjohnwu.magisk.BuildConfig
|
import com.topjohnwu.magisk.BuildConfig
|
||||||
import com.topjohnwu.magisk.R
|
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.model.events.dialog.UninstallDialog
|
||||||
import com.topjohnwu.magisk.ui.base.BaseViewModel
|
import com.topjohnwu.magisk.ui.base.BaseViewModel
|
||||||
import com.topjohnwu.magisk.ui.base.itemBindingOf
|
import com.topjohnwu.magisk.ui.base.itemBindingOf
|
||||||
|
import com.topjohnwu.magisk.utils.observable
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import me.tatarka.bindingcollectionadapter2.BR
|
import me.tatarka.bindingcollectionadapter2.BR
|
||||||
@ -40,22 +41,26 @@ class HomeViewModel(
|
|||||||
private val repoMagisk: MagiskRepository
|
private val repoMagisk: MagiskRepository
|
||||||
) : BaseViewModel() {
|
) : BaseViewModel() {
|
||||||
|
|
||||||
val isNoticeVisible = ObservableField(Config.safetyNotice)
|
@get:Bindable
|
||||||
|
var isNoticeVisible by observable(Config.safetyNotice, BR.noticeVisible)
|
||||||
val stateMagisk = ObservableField(MagiskState.LOADING)
|
@get:Bindable
|
||||||
val stateManager = ObservableField(MagiskState.LOADING)
|
var stateMagisk by observable(MagiskState.LOADING, BR.stateMagisk)
|
||||||
|
@get:Bindable
|
||||||
val stateMagiskRemoteVersion = ObservableField(R.string.loading.res())
|
var stateManager by observable(MagiskState.LOADING, BR.stateManager)
|
||||||
val stateMagiskInstalledVersion get() =
|
@get:Bindable
|
||||||
|
var magiskRemoteVersion by observable(R.string.loading.res(), BR.magiskRemoteVersion)
|
||||||
|
val magiskInstalledVersion get() =
|
||||||
"${Info.env.magiskVersionString} (${Info.env.magiskVersionCode})"
|
"${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())
|
@get:Bindable
|
||||||
val stateManagerInstalledVersion = Info.stub?.let {
|
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}) (${it.version})"
|
||||||
} ?: "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})"
|
} ?: "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})"
|
||||||
val statePackageName = packageName
|
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 items = listOf(DeveloperItem.Mainline, DeveloperItem.App, DeveloperItem.Project)
|
||||||
val itemBinding = itemBindingOf<HomeItem> {
|
val itemBinding = itemBindingOf<HomeItem> {
|
||||||
@ -70,28 +75,28 @@ class HomeViewModel(
|
|||||||
init {
|
init {
|
||||||
RemoteFileService.progressBroadcast.observeForever {
|
RemoteFileService.progressBroadcast.observeForever {
|
||||||
when (it?.second) {
|
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 {
|
override fun refresh() = viewModelScope.launch {
|
||||||
repoMagisk.fetchUpdate()?.apply {
|
repoMagisk.fetchUpdate()?.apply {
|
||||||
stateMagisk.value = when {
|
stateMagisk = when {
|
||||||
!Info.env.isActive -> MagiskState.NOT_INSTALLED
|
!Info.env.isActive -> MagiskState.NOT_INSTALLED
|
||||||
magisk.isObsolete -> MagiskState.OBSOLETE
|
magisk.isObsolete -> MagiskState.OBSOLETE
|
||||||
else -> MagiskState.UP_TO_DATE
|
else -> MagiskState.UP_TO_DATE
|
||||||
}
|
}
|
||||||
|
|
||||||
stateManager.value = when {
|
stateManager = when {
|
||||||
!app.isUpdateChannelCorrect && isConnected.value -> MagiskState.NOT_INSTALLED
|
!app.isUpdateChannelCorrect && isConnected.value -> MagiskState.NOT_INSTALLED
|
||||||
app.isObsolete -> MagiskState.OBSOLETE
|
app.isObsolete -> MagiskState.OBSOLETE
|
||||||
else -> MagiskState.UP_TO_DATE
|
else -> MagiskState.UP_TO_DATE
|
||||||
}
|
}
|
||||||
|
|
||||||
stateMagiskRemoteVersion.value =
|
magiskRemoteVersion =
|
||||||
"${magisk.version} (${magisk.versionCode})"
|
"${magisk.version} (${magisk.versionCode})"
|
||||||
stateManagerRemoteVersion.value =
|
managerRemoteVersion =
|
||||||
"${app.version} (${app.versionCode}) (${stub.versionCode})"
|
"${app.version} (${app.versionCode}) (${stub.versionCode})"
|
||||||
|
|
||||||
launch {
|
launch {
|
||||||
@ -122,7 +127,7 @@ class HomeViewModel(
|
|||||||
|
|
||||||
fun hideNotice() {
|
fun hideNotice() {
|
||||||
Config.safetyNotice = false
|
Config.safetyNotice = false
|
||||||
isNoticeVisible.value = false
|
isNoticeVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun ensureEnv() {
|
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
|
// Don't bother checking env when magisk is not installed, loading or already has been shown
|
||||||
if (
|
if (
|
||||||
invalidStates.any { it == stateMagisk.value } ||
|
invalidStates.any { it == stateMagisk } ||
|
||||||
shownDialog ||
|
shownDialog ||
|
||||||
// don't care for emulators either
|
// don't care for emulators either
|
||||||
Build.DEVICE.orEmpty().contains("generic") ||
|
Build.DEVICE.orEmpty().contains("generic") ||
|
||||||
|
@ -4,7 +4,6 @@ import android.content.Intent
|
|||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.databinding.FragmentInstallMd2Binding
|
import com.topjohnwu.magisk.databinding.FragmentInstallMd2Binding
|
||||||
import com.topjohnwu.magisk.ktx.value
|
|
||||||
import com.topjohnwu.magisk.model.events.RequestFileEvent
|
import com.topjohnwu.magisk.model.events.RequestFileEvent
|
||||||
import com.topjohnwu.magisk.ui.base.BaseUIFragment
|
import com.topjohnwu.magisk.ui.base.BaseUIFragment
|
||||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
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?) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
super.onActivityResult(requestCode, resultCode, data)
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
viewModel.data.value = RequestFileEvent.resolve(requestCode, resultCode, data)
|
viewModel.data = RequestFileEvent.resolve(requestCode, resultCode, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
|
@ -2,21 +2,21 @@ package com.topjohnwu.magisk.ui.install
|
|||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.databinding.ObservableField
|
import androidx.databinding.Bindable
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.topjohnwu.magisk.BR
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.core.Info
|
import com.topjohnwu.magisk.core.Info
|
||||||
import com.topjohnwu.magisk.core.download.DownloadService
|
import com.topjohnwu.magisk.core.download.DownloadService
|
||||||
import com.topjohnwu.magisk.core.download.RemoteFileService
|
import com.topjohnwu.magisk.core.download.RemoteFileService
|
||||||
import com.topjohnwu.magisk.core.utils.Utils
|
import com.topjohnwu.magisk.core.utils.Utils
|
||||||
import com.topjohnwu.magisk.data.repository.StringRepository
|
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.Configuration
|
||||||
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
|
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
|
||||||
import com.topjohnwu.magisk.model.events.RequestFileEvent
|
import com.topjohnwu.magisk.model.events.RequestFileEvent
|
||||||
import com.topjohnwu.magisk.model.events.dialog.SecondSlotWarningDialog
|
import com.topjohnwu.magisk.model.events.dialog.SecondSlotWarningDialog
|
||||||
import com.topjohnwu.magisk.ui.base.BaseViewModel
|
import com.topjohnwu.magisk.ui.base.BaseViewModel
|
||||||
|
import com.topjohnwu.magisk.utils.observable
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.koin.core.get
|
import org.koin.core.get
|
||||||
@ -29,11 +29,26 @@ class InstallViewModel(
|
|||||||
val isRooted get() = Shell.rootAccess()
|
val isRooted get() = Shell.rootAccess()
|
||||||
val isAB get() = Info.isAB
|
val isAB get() = Info.isAB
|
||||||
|
|
||||||
val step = ObservableField(0)
|
@get:Bindable
|
||||||
val method = ObservableField(-1)
|
var step by observable(0, BR.step)
|
||||||
val progress = ObservableField(0)
|
@get:Bindable
|
||||||
val data = ObservableField(null as Uri?)
|
var method by observable(-1, BR.method) {
|
||||||
val notes = ObservableField("")
|
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 {
|
init {
|
||||||
RemoteFileService.reset()
|
RemoteFileService.reset()
|
||||||
@ -42,29 +57,18 @@ class InstallViewModel(
|
|||||||
if (subject !is DownloadSubject.Magisk) {
|
if (subject !is DownloadSubject.Magisk) {
|
||||||
return@observeForever
|
return@observeForever
|
||||||
}
|
}
|
||||||
this.progress.value = progress.times(100).roundToInt()
|
this.progress = progress.times(100).roundToInt()
|
||||||
if (this.progress.value >= 100) {
|
if (this.progress >= 100) {
|
||||||
state = State.LOADED
|
state = State.LOADED
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
notes.value = stringRepo.getString(Info.remote.magisk.note)
|
notes = 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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun step(nextStep: Int) {
|
fun step(nextStep: Int) {
|
||||||
step.value = nextStep
|
step = nextStep
|
||||||
}
|
}
|
||||||
|
|
||||||
fun install() = DownloadService(get()) {
|
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_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_direct -> Configuration.Flash.Primary
|
||||||
R.id.method_inactive_slot -> Configuration.Flash.Secondary
|
R.id.method_inactive_slot -> Configuration.Flash.Secondary
|
||||||
else -> throw IllegalArgumentException("Unknown value")
|
else -> throw IllegalArgumentException("Unknown value")
|
||||||
|
@ -1,21 +1,23 @@
|
|||||||
package com.topjohnwu.magisk.ui.log
|
package com.topjohnwu.magisk.ui.log
|
||||||
|
|
||||||
import androidx.databinding.ObservableField
|
import androidx.databinding.Bindable
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.topjohnwu.magisk.BR
|
import com.topjohnwu.magisk.BR
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.core.Config
|
import com.topjohnwu.magisk.core.Config
|
||||||
import com.topjohnwu.magisk.core.Const
|
import com.topjohnwu.magisk.core.Const
|
||||||
import com.topjohnwu.magisk.data.repository.LogRepository
|
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.LogItem
|
||||||
import com.topjohnwu.magisk.model.entity.recycler.TextItem
|
import com.topjohnwu.magisk.model.entity.recycler.TextItem
|
||||||
import com.topjohnwu.magisk.model.events.SnackbarEvent
|
import com.topjohnwu.magisk.model.events.SnackbarEvent
|
||||||
import com.topjohnwu.magisk.ui.base.BaseViewModel
|
import com.topjohnwu.magisk.ui.base.BaseViewModel
|
||||||
import com.topjohnwu.magisk.ui.base.diffListOf
|
import com.topjohnwu.magisk.ui.base.diffListOf
|
||||||
import com.topjohnwu.magisk.ui.base.itemBindingOf
|
import com.topjohnwu.magisk.ui.base.itemBindingOf
|
||||||
|
import com.topjohnwu.magisk.utils.observable
|
||||||
import com.topjohnwu.superuser.Shell
|
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 timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
@ -38,19 +40,15 @@ class LogViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --- magisk log
|
// --- magisk log
|
||||||
|
@get:Bindable
|
||||||
val consoleText = ObservableField(" ")
|
var consoleText by observable(" ", BR.consoleText)
|
||||||
|
|
||||||
override fun refresh() = viewModelScope.launch {
|
override fun refresh() = viewModelScope.launch {
|
||||||
consoleText.value = repo.fetchMagiskLogs()
|
consoleText = repo.fetchMagiskLogs()
|
||||||
val deferred = withContext(Dispatchers.Default) {
|
val (suLogs, diff) = withContext(Dispatchers.Default) {
|
||||||
async {
|
val suLogs = repo.fetchSuLogs().map { LogItem(it) }
|
||||||
val suLogs = repo.fetchSuLogs().map { LogItem(it) }
|
suLogs to items.calculateDiff(suLogs)
|
||||||
suLogs to items.calculateDiff(suLogs)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
delay(500)
|
|
||||||
val (suLogs, diff) = deferred.await()
|
|
||||||
items.firstOrNull()?.isTop = false
|
items.firstOrNull()?.isTop = false
|
||||||
items.lastOrNull()?.isBottom = false
|
items.lastOrNull()?.isBottom = false
|
||||||
items.update(suLogs, diff)
|
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.InstallExternalModuleEvent
|
||||||
import com.topjohnwu.magisk.model.events.ViewEvent
|
import com.topjohnwu.magisk.model.events.ViewEvent
|
||||||
import com.topjohnwu.magisk.ui.MainActivity
|
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.ui.base.BaseUIFragment
|
||||||
import com.topjohnwu.magisk.utils.EndlessRecyclerScrollListener
|
import com.topjohnwu.magisk.utils.EndlessRecyclerScrollListener
|
||||||
import com.topjohnwu.magisk.utils.MotionRevealHelper
|
import com.topjohnwu.magisk.utils.MotionRevealHelper
|
||||||
|
@ -2,7 +2,6 @@ package com.topjohnwu.magisk.ui.module
|
|||||||
|
|
||||||
import androidx.databinding.Bindable
|
import androidx.databinding.Bindable
|
||||||
import androidx.databinding.ObservableArrayList
|
import androidx.databinding.ObservableArrayList
|
||||||
import androidx.databinding.ObservableField
|
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.topjohnwu.magisk.BR
|
import com.topjohnwu.magisk.BR
|
||||||
import com.topjohnwu.magisk.R
|
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.model.events.dialog.ModuleInstallDialog
|
||||||
import com.topjohnwu.magisk.ui.base.*
|
import com.topjohnwu.magisk.ui.base.*
|
||||||
import com.topjohnwu.magisk.utils.EndlessRecyclerScrollListener
|
import com.topjohnwu.magisk.utils.EndlessRecyclerScrollListener
|
||||||
|
import com.topjohnwu.magisk.utils.observable
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import me.tatarka.bindingcollectionadapter2.collections.MergeObservableList
|
import me.tatarka.bindingcollectionadapter2.collections.MergeObservableList
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
@ -53,18 +53,18 @@ class ModuleViewModel(
|
|||||||
private var queryJob: Job? = null
|
private var queryJob: Job? = null
|
||||||
private var remoteJob: Job? = null
|
private var remoteJob: Job? = null
|
||||||
|
|
||||||
var query = ""
|
@get:Bindable
|
||||||
@Bindable get
|
var isRemoteLoading by observable(false, BR.remoteLoading)
|
||||||
set(value) {
|
|
||||||
if (field == value) return
|
|
||||||
field = value
|
|
||||||
notifyPropertyChanged(BR.query)
|
|
||||||
submitQuery()
|
|
||||||
// Yes we do lie about the search being loaded
|
|
||||||
searchLoading.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
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 itemsSearch = diffListOf<RepoItem>()
|
||||||
val itemSearchBinding = itemBindingOf<RepoItem> {
|
val itemSearchBinding = itemBindingOf<RepoItem> {
|
||||||
it.bindExtra(BR.viewModel, this)
|
it.bindExtra(BR.viewModel, this)
|
||||||
@ -80,13 +80,6 @@ class ModuleViewModel(
|
|||||||
private val itemsUpdatable = diffListOf<RepoItem.Update>()
|
private val itemsUpdatable = diffListOf<RepoItem.Update>()
|
||||||
private val itemsRemote = diffListOf<RepoItem.Remote>()
|
private val itemsRemote = diffListOf<RepoItem.Remote>()
|
||||||
|
|
||||||
var isRemoteLoading = false
|
|
||||||
@Bindable get
|
|
||||||
private set(value) {
|
|
||||||
field = value
|
|
||||||
notifyPropertyChanged(BR.remoteLoading)
|
|
||||||
}
|
|
||||||
|
|
||||||
val adapter = adapterOf<ComparableRvItem<*>>()
|
val adapter = adapterOf<ComparableRvItem<*>>()
|
||||||
val items = MergeObservableList<ComparableRvItem<*>>()
|
val items = MergeObservableList<ComparableRvItem<*>>()
|
||||||
.insertItem(InstallModule)
|
.insertItem(InstallModule)
|
||||||
@ -261,7 +254,7 @@ class ModuleViewModel(
|
|||||||
val diff = withContext(Dispatchers.Default) {
|
val diff = withContext(Dispatchers.Default) {
|
||||||
itemsSearch.calculateDiff(searched)
|
itemsSearch.calculateDiff(searched)
|
||||||
}
|
}
|
||||||
searchLoading.value = false
|
searchLoading = false
|
||||||
itemsSearch.update(searched, diff)
|
itemsSearch.update(searched, diff)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
package com.topjohnwu.magisk.ui.safetynet
|
package com.topjohnwu.magisk.ui.safetynet
|
||||||
|
|
||||||
import androidx.databinding.Bindable
|
import androidx.databinding.Bindable
|
||||||
import androidx.databinding.ObservableField
|
|
||||||
import com.topjohnwu.magisk.BR
|
import com.topjohnwu.magisk.BR
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.ktx.value
|
|
||||||
import com.topjohnwu.magisk.model.events.CheckSafetyNetEvent
|
import com.topjohnwu.magisk.model.events.CheckSafetyNetEvent
|
||||||
import com.topjohnwu.magisk.ui.base.BaseViewModel
|
import com.topjohnwu.magisk.ui.base.BaseViewModel
|
||||||
import com.topjohnwu.magisk.ui.safetynet.SafetyNetState.*
|
import com.topjohnwu.magisk.ui.safetynet.SafetyNetState.*
|
||||||
|
import com.topjohnwu.magisk.utils.observable
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
|
||||||
enum class SafetyNetState {
|
enum class SafetyNetState {
|
||||||
@ -21,19 +20,23 @@ data class SafetyNetResult(
|
|||||||
|
|
||||||
class SafetynetViewModel : BaseViewModel() {
|
class SafetynetViewModel : BaseViewModel() {
|
||||||
|
|
||||||
private var currentState = IDLE
|
@get:Bindable
|
||||||
set(value) {
|
var safetyNetTitle by observable(R.string.empty, BR.safetyNetTitle)
|
||||||
field = value
|
@get:Bindable
|
||||||
notifyStateChanged()
|
var ctsState by observable(false, BR.ctsState)
|
||||||
}
|
@get:Bindable
|
||||||
val safetyNetTitle = ObservableField(R.string.empty)
|
var basicIntegrityState by observable(false, BR.basicIntegrityState)
|
||||||
val ctsState = ObservableField(false)
|
@get:Bindable
|
||||||
val basicIntegrityState = ObservableField(false)
|
var evalType by observable("")
|
||||||
val evalType = ObservableField("")
|
|
||||||
|
|
||||||
val isChecking @Bindable get() = currentState == LOADING
|
@get:Bindable
|
||||||
val isFailed @Bindable get() = currentState == FAILED
|
val isChecking get() = currentState == LOADING
|
||||||
val isSuccess @Bindable get() = currentState == PASS
|
@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 {
|
init {
|
||||||
cachedResult?.also {
|
cachedResult?.also {
|
||||||
@ -41,13 +44,6 @@ class SafetynetViewModel : BaseViewModel() {
|
|||||||
} ?: attest()
|
} ?: attest()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun notifyStateChanged() {
|
|
||||||
super.notifyStateChanged()
|
|
||||||
notifyPropertyChanged(BR.loading)
|
|
||||||
notifyPropertyChanged(BR.failed)
|
|
||||||
notifyPropertyChanged(BR.success)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun attest() {
|
private fun attest() {
|
||||||
currentState = LOADING
|
currentState = LOADING
|
||||||
CheckSafetyNetEvent() {
|
CheckSafetyNetEvent() {
|
||||||
@ -70,26 +66,26 @@ class SafetynetViewModel : BaseViewModel() {
|
|||||||
val eval = optString("evaluationType")
|
val eval = optString("evaluationType")
|
||||||
val result = cts && basic
|
val result = cts && basic
|
||||||
cachedResult = this
|
cachedResult = this
|
||||||
ctsState.value = cts
|
ctsState = cts
|
||||||
basicIntegrityState.value = basic
|
basicIntegrityState = basic
|
||||||
evalType.value = if (eval.contains("HARDWARE")) "HARDWARE" else "BASIC"
|
evalType = if (eval.contains("HARDWARE")) "HARDWARE" else "BASIC"
|
||||||
currentState = if (result) PASS else FAILED
|
currentState = if (result) PASS else FAILED
|
||||||
safetyNetTitle.value =
|
safetyNetTitle =
|
||||||
if (result) R.string.safetynet_attest_success
|
if (result) R.string.safetynet_attest_success
|
||||||
else R.string.safetynet_attest_failure
|
else R.string.safetynet_attest_failure
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
currentState = FAILED
|
currentState = FAILED
|
||||||
ctsState.value = false
|
ctsState = false
|
||||||
basicIntegrityState.value = false
|
basicIntegrityState = false
|
||||||
evalType.value = "N/A"
|
evalType = "N/A"
|
||||||
safetyNetTitle.value = R.string.safetynet_res_invalid
|
safetyNetTitle = R.string.safetynet_res_invalid
|
||||||
}
|
}
|
||||||
} ?: {
|
} ?: {
|
||||||
currentState = FAILED
|
currentState = FAILED
|
||||||
ctsState.value = false
|
ctsState = false
|
||||||
basicIntegrityState.value = false
|
basicIntegrityState = false
|
||||||
evalType.value = "N/A"
|
evalType = "N/A"
|
||||||
safetyNetTitle.value = R.string.safetynet_api_error
|
safetyNetTitle = R.string.safetynet_api_error
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ object Language : SettingsItem.Selector() {
|
|||||||
entryValues = values
|
entryValues = values
|
||||||
val selectedLocale = currentLocale.getDisplayName(currentLocale)
|
val selectedLocale = currentLocale.getDisplayName(currentLocale)
|
||||||
value = names.indexOfFirst { it == selectedLocale }.let { if (it == -1) 0 else it }
|
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)
|
override var value: String = resources.getString(R.string.re_app_name)
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
notifyChange(BR.value)
|
notifyPropertyChanged(BR.value)
|
||||||
notifyChange(BR.error)
|
notifyPropertyChanged(BR.error)
|
||||||
}
|
}
|
||||||
|
|
||||||
@get:Bindable
|
@get:Bindable
|
||||||
@ -112,8 +112,8 @@ object DownloadPath : SettingsItem.Input() {
|
|||||||
var result = value
|
var result = value
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
notifyChange(BR.result)
|
notifyPropertyChanged(BR.result)
|
||||||
notifyChange(BR.path)
|
notifyPropertyChanged(BR.path)
|
||||||
}
|
}
|
||||||
|
|
||||||
@get:Bindable
|
@get:Bindable
|
||||||
@ -143,7 +143,7 @@ object UpdateChannelUrl : SettingsItem.Input() {
|
|||||||
var result = value
|
var result = value
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
notifyChange(BR.result)
|
notifyPropertyChanged(BR.result)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun refresh() {
|
override fun refresh() {
|
||||||
|
@ -6,8 +6,9 @@ import android.content.pm.PackageManager
|
|||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.os.CountDownTimer
|
import android.os.CountDownTimer
|
||||||
import androidx.databinding.ObservableField
|
import androidx.databinding.Bindable
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.topjohnwu.magisk.BR
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.core.Config
|
import com.topjohnwu.magisk.core.Config
|
||||||
import com.topjohnwu.magisk.core.magiskdb.PolicyDao
|
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.model.MagiskPolicy.Companion.DENY
|
||||||
import com.topjohnwu.magisk.core.su.SuRequestHandler
|
import com.topjohnwu.magisk.core.su.SuRequestHandler
|
||||||
import com.topjohnwu.magisk.core.utils.BiometricHelper
|
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.entity.recycler.SpinnerRvItem
|
||||||
import com.topjohnwu.magisk.model.events.DieEvent
|
import com.topjohnwu.magisk.model.events.DieEvent
|
||||||
import com.topjohnwu.magisk.ui.base.BaseViewModel
|
import com.topjohnwu.magisk.ui.base.BaseViewModel
|
||||||
|
import com.topjohnwu.magisk.utils.observable
|
||||||
import com.topjohnwu.superuser.internal.UiThreadHandler
|
import com.topjohnwu.superuser.internal.UiThreadHandler
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import me.tatarka.bindingcollectionadapter2.BindingListViewAdapter
|
import me.tatarka.bindingcollectionadapter2.BindingListViewAdapter
|
||||||
@ -32,16 +33,20 @@ class SuRequestViewModel(
|
|||||||
private val res: Resources
|
private val res: Resources
|
||||||
) : BaseViewModel() {
|
) : BaseViewModel() {
|
||||||
|
|
||||||
val icon = ObservableField<Drawable?>(null)
|
@get:Bindable
|
||||||
val title = ObservableField("")
|
var icon by observable(null as Drawable?, BR.icon)
|
||||||
val packageName = ObservableField("")
|
@get:Bindable
|
||||||
|
var title by observable("", BR.title)
|
||||||
val denyText = ObservableField(res.getString(R.string.deny))
|
@get:Bindable
|
||||||
val warningText = ObservableField<CharSequence>(res.getString(R.string.su_warning))
|
var packageName by observable("", BR.packageName)
|
||||||
|
@get:Bindable
|
||||||
val selectedItemPosition = ObservableField(0)
|
var denyText by observable(res.getString(R.string.deny), BR.denyText)
|
||||||
|
@get:Bindable
|
||||||
val grantEnabled = ObservableField(false)
|
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) }
|
private val items = res.getStringArray(R.array.allow_timeout).map { SpinnerRvItem(it) }
|
||||||
val adapter = BindingListViewAdapter<SpinnerRvItem>(1).apply {
|
val adapter = BindingListViewAdapter<SpinnerRvItem>(1).apply {
|
||||||
@ -89,7 +94,7 @@ class SuRequestViewModel(
|
|||||||
fun respond(action: Int) {
|
fun respond(action: Int) {
|
||||||
timer.cancel()
|
timer.cancel()
|
||||||
|
|
||||||
val pos = selectedItemPosition.value
|
val pos = selectedItemPosition
|
||||||
timeoutPrefs.edit().putInt(policy.packageName, pos).apply()
|
timeoutPrefs.edit().putInt(policy.packageName, pos).apply()
|
||||||
respond(action, Config.Value.TIMEOUT_LIST[pos])
|
respond(action, Config.Value.TIMEOUT_LIST[pos])
|
||||||
|
|
||||||
@ -99,16 +104,16 @@ class SuRequestViewModel(
|
|||||||
|
|
||||||
fun cancelTimer() {
|
fun cancelTimer() {
|
||||||
timer.cancel()
|
timer.cancel()
|
||||||
denyText.value = res.getString(R.string.deny)
|
denyText = res.getString(R.string.deny)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
icon.value = policy.applicationInfo.loadIcon(pm)
|
icon = policy.applicationInfo.loadIcon(pm)
|
||||||
title.value = policy.appName
|
title = policy.appName
|
||||||
packageName.value = policy.packageName
|
packageName = policy.packageName
|
||||||
UiThreadHandler.handler.post {
|
UiThreadHandler.handler.post {
|
||||||
// Delay is required to properly do selection
|
// Delay is required to properly do selection
|
||||||
selectedItemPosition.value = timeoutPrefs.getInt(policy.packageName, 0)
|
selectedItemPosition = timeoutPrefs.getInt(policy.packageName, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set timer
|
// Set timer
|
||||||
@ -122,14 +127,14 @@ class SuRequestViewModel(
|
|||||||
) : CountDownTimer(millis, interval) {
|
) : CountDownTimer(millis, interval) {
|
||||||
|
|
||||||
override fun onTick(remains: Long) {
|
override fun onTick(remains: Long) {
|
||||||
if (!grantEnabled.value && remains <= millis - 1000) {
|
if (!grantEnabled && remains <= millis - 1000) {
|
||||||
grantEnabled.value = true
|
grantEnabled = true
|
||||||
}
|
}
|
||||||
denyText.value = "${res.getString(R.string.deny)} (${(remains / 1000) + 1})"
|
denyText = "${res.getString(R.string.deny)} (${(remains / 1000) + 1})"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFinish() {
|
override fun onFinish() {
|
||||||
denyText.value = res.getString(R.string.deny)
|
denyText = res.getString(R.string.deny)
|
||||||
respond(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
|
<TextView
|
||||||
style="@style/W.Home.ItemContent.Right"
|
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)" />
|
tools:text="20.1 (12345)" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
@ -149,7 +149,7 @@
|
|||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
style="@style/W.Home.ItemContent.Right"
|
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)" />
|
tools:text="20.1 (12345)" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
@ -166,7 +166,7 @@
|
|||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
style="@style/W.Home.ItemContent.Right"
|
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" />
|
tools:text="Normal" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
@ -121,7 +121,7 @@
|
|||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
style="@style/W.Home.ItemContent.Right"
|
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)" />
|
tools:text="8.0.0 (123) (10)" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
@ -138,7 +138,7 @@
|
|||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
style="@style/W.Home.ItemContent.Right"
|
style="@style/W.Home.ItemContent.Right"
|
||||||
android:text="@{viewModel.stateManagerInstalledVersion}"
|
android:text="@{viewModel.managerInstalledVersion}"
|
||||||
tools:text="8.0.0 (123) (10)" />
|
tools:text="8.0.0 (123) (10)" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
@ -25,6 +25,7 @@ org.gradle.daemon=true
|
|||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
android.enableR8.fullMode=true
|
android.enableR8.fullMode=true
|
||||||
|
android.databinding.incremental=true
|
||||||
|
|
||||||
android.injected.testOnly=false
|
android.injected.testOnly=false
|
||||||
kapt.incremental.apt=true
|
kapt.incremental.apt=true
|
Loading…
x
Reference in New Issue
Block a user