Cleanup more databinding implementation

This commit is contained in:
topjohnwu 2023-03-31 00:05:33 -07:00
parent 1620e15f99
commit 0b987dd0b0
19 changed files with 124 additions and 176 deletions

View File

@ -1,31 +1,26 @@
package com.topjohnwu.magisk.databinding package com.topjohnwu.magisk.databinding
import androidx.annotation.MainThread import androidx.annotation.MainThread
import androidx.annotation.WorkerThread
import androidx.databinding.ListChangeRegistry import androidx.databinding.ListChangeRegistry
import androidx.databinding.ObservableList import androidx.databinding.ObservableList
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListUpdateCallback import androidx.recyclerview.widget.ListUpdateCallback
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.util.AbstractList import java.util.AbstractList
open class DiffObservableList<T>( open class DiffObservableList<T : DiffItem<*>>
private val callback: Callback<T> : AbstractList<T>(), ObservableList<T>, ListUpdateCallback {
) : AbstractList<T>(), ObservableList<T> {
protected var list: List<T> = emptyList() protected var list: List<T> = emptyList()
private set
private val listeners = ListChangeRegistry() private val listeners = ListChangeRegistry()
protected val listCallback = ObservableListUpdateCallback()
override val size: Int get() = list.size override val size: Int get() = list.size
override fun get(index: Int) = list[index] override fun get(index: Int) = list[index]
/**
* Calculates the list of update operations that can convert this list into the given one.
*
* @param newItems The items that this list will be set to.
* @return A DiffResult that contains the information about the edit sequence to covert this
* list into the given one.
*/
fun calculateDiff(newItems: List<T>): DiffUtil.DiffResult { fun calculateDiff(newItems: List<T>): DiffUtil.DiffResult {
return doCalculateDiff(list, newItems) return doCalculateDiff(list, newItems)
} }
@ -36,31 +31,34 @@ open class DiffObservableList<T>(
override fun getNewListSize() = newItems.size override fun getNewListSize() = newItems.size
@Suppress("UNCHECKED_CAST")
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldItem = oldItems[oldItemPosition] val oldItem = oldItems[oldItemPosition]
val newItem = newItems[newItemPosition] val newItem = newItems[newItemPosition]
return callback.areItemsTheSame(oldItem, newItem) return (oldItem as DiffItem<Any>).itemSameAs(newItem)
} }
@Suppress("UNCHECKED_CAST")
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldItem = oldItems[oldItemPosition] val oldItem = oldItems[oldItemPosition]
val newItem = newItems[newItemPosition] val newItem = newItems[newItemPosition]
return callback.areContentsTheSame(oldItem, newItem) return (oldItem as DiffItem<Any>).contentSameAs(newItem)
} }
}, true) }, true)
} }
/**
* Updates the contents of this list to the given one using the DiffResults to dispatch change
* notifications.
*
* @param newItems The items to set this list to.
* @param diffResult The diff results to dispatch change notifications.
*/
@MainThread @MainThread
fun update(newItems: List<T>, diffResult: DiffUtil.DiffResult) { fun update(newItems: List<T>, diffResult: DiffUtil.DiffResult) {
list = ArrayList(newItems) list = ArrayList(newItems)
diffResult.dispatchUpdatesTo(listCallback) diffResult.dispatchUpdatesTo(this)
}
@WorkerThread
suspend fun update(newItems: List<T>) {
val diffResult = calculateDiff(newItems)
withContext(Dispatchers.Main) {
update(newItems, diffResult)
}
} }
override fun addOnListChangedCallback(listener: ObservableList.OnListChangedCallback<out ObservableList<T>>) { override fun addOnListChangedCallback(listener: ObservableList.OnListChangedCallback<out ObservableList<T>>) {
@ -71,36 +69,21 @@ open class DiffObservableList<T>(
listeners.remove(listener) listeners.remove(listener)
} }
private fun notifyAdd(start: Int, count: Int) { override fun onChanged(position: Int, count: Int, payload: Any?) {
listeners.notifyInserted(this, start, count) listeners.notifyChanged(this, position, count)
} }
private fun notifyRemove(start: Int, count: Int) { override fun onMoved(fromPosition: Int, toPosition: Int) {
listeners.notifyRemoved(this, start, count) listeners.notifyMoved(this, fromPosition, toPosition, 1)
} }
interface Callback<T> { override fun onInserted(position: Int, count: Int) {
fun areItemsTheSame(oldItem: T, newItem: T): Boolean modCount += 1
fun areContentsTheSame(oldItem: T, newItem: T): Boolean listeners.notifyInserted(this, position, count)
} }
inner class ObservableListUpdateCallback : ListUpdateCallback { override fun onRemoved(position: Int, count: Int) {
override fun onChanged(position: Int, count: Int, payload: Any?) { modCount += 1
listeners.notifyChanged(this@DiffObservableList, position, count) listeners.notifyRemoved(this, position, count)
}
override fun onMoved(fromPosition: Int, toPosition: Int) {
listeners.notifyMoved(this@DiffObservableList, fromPosition, toPosition, 1)
}
override fun onInserted(position: Int, count: Int) {
modCount += 1
listeners.notifyInserted(this@DiffObservableList, position, count)
}
override fun onRemoved(position: Int, count: Int) {
modCount += 1
listeners.notifyRemoved(this@DiffObservableList, position, count)
}
} }
} }

View File

@ -6,10 +6,9 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
open class FilterableDiffObservableList<T>( open class FilterableDiffObservableList<T : DiffItem<*>>(
callback: Callback<T>,
private val scope: CoroutineScope private val scope: CoroutineScope
) : DiffObservableList<T>(callback) { ) : DiffObservableList<T>() {
private var sublist: List<T> = emptyList() private var sublist: List<T> = emptyList()
private var job: Job? = null private var job: Job? = null
@ -24,7 +23,7 @@ open class FilterableDiffObservableList<T>(
val diff = doCalculateDiff(oldList, newList) val diff = doCalculateDiff(oldList, newList)
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
sublist = newList sublist = newList
diff.dispatchUpdatesTo(listCallback) diff.dispatchUpdatesTo(this@FilterableDiffObservableList)
} }
} }
} }

View File

@ -3,69 +3,33 @@ package com.topjohnwu.magisk.databinding
import androidx.databinding.PropertyChangeRegistry import androidx.databinding.PropertyChangeRegistry
import androidx.databinding.ViewDataBinding import androidx.databinding.ViewDataBinding
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.CoroutineScope
abstract class RvItem { abstract class RvItem {
abstract val layoutRes: Int abstract val layoutRes: Int
} }
interface RvContainer<E> {
val item: E
}
interface ViewAwareRvItem {
fun onBind(binding: ViewDataBinding, recyclerView: RecyclerView)
}
interface ComparableRv<T> : Comparable<T> {
@Suppress("UNCHECKED_CAST")
fun comparableEqual(o: Any?) =
o != null && o::class == this::class && compareTo(o as T) == 0
}
abstract class DiffRvItem<T> : RvItem() {
// Defer to contentSameAs by default
open fun itemSameAs(other: T) = true
open fun contentSameAs(other: T) =
when (this) {
is RvContainer<*> -> item == (other as RvContainer<*>).item
is ComparableRv<*> -> comparableEqual(other)
else -> this == other
}
}
typealias AnyDiffRvItem = DiffRvItem<*>
abstract class ObservableDiffRvItem<T> : DiffRvItem<T>(), ObservableHost {
override var callbacks: PropertyChangeRegistry? = null
}
abstract class ObservableRvItem : RvItem(), ObservableHost { abstract class ObservableRvItem : RvItem(), ObservableHost {
override var callbacks: PropertyChangeRegistry? = null override var callbacks: PropertyChangeRegistry? = null
} }
private object DiffRvItemCallback : DiffObservableList.Callback<DiffRvItem<Any>> { interface ItemWrapper<E> {
override fun areItemsTheSame( val item: E
oldItem: DiffRvItem<Any>,
newItem: DiffRvItem<Any>
): Boolean {
return oldItem::class == newItem::class && oldItem.itemSameAs(newItem)
}
override fun areContentsTheSame(
oldItem: DiffRvItem<Any>,
newItem: DiffRvItem<Any>
): Boolean {
return oldItem.contentSameAs(newItem)
}
} }
@Suppress("UNCHECKED_CAST") interface ViewAwareItem {
class DiffRvItemList<T: AnyDiffRvItem> : DiffObservableList<T>(DiffRvItemCallback as Callback<T>) fun onBind(binding: ViewDataBinding, recyclerView: RecyclerView)
}
@Suppress("UNCHECKED_CAST") interface DiffItem<T : Any> {
class DiffRvItemFilterList<T: AnyDiffRvItem>(
scope: CoroutineScope fun itemSameAs(other: T): Boolean {
) : FilterableDiffObservableList<T>(DiffRvItemCallback as Callback<T>, scope) if (this === other) return true
return when (this) {
is ItemWrapper<*> -> item == (other as ItemWrapper<*>).item
is Comparable<*> -> compareValues(this, other as Comparable<*>) == 0
else -> this == other
}
}
fun contentSameAs(other: T) = true
}

View File

@ -53,7 +53,7 @@ class RvItemAdapter<T: RvItem>(
holder.binding.lifecycleOwner = lifecycleOwner holder.binding.lifecycleOwner = lifecycleOwner
holder.binding.executePendingBindings() holder.binding.executePendingBindings()
recyclerView?.let { recyclerView?.let {
if (item is ViewAwareRvItem) if (item is ViewAwareItem)
item.onBind(holder.binding, it) item.onBind(holder.binding, it)
} }
} }

View File

@ -5,8 +5,8 @@ import android.view.ViewGroup
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.ComparableRv import com.topjohnwu.magisk.databinding.DiffItem
import com.topjohnwu.magisk.databinding.ObservableDiffRvItem import com.topjohnwu.magisk.databinding.ObservableRvItem
import com.topjohnwu.magisk.databinding.addOnPropertyChangedCallback import com.topjohnwu.magisk.databinding.addOnPropertyChangedCallback
import com.topjohnwu.magisk.databinding.set import com.topjohnwu.magisk.databinding.set
import com.topjohnwu.magisk.ktx.startAnimations import com.topjohnwu.magisk.ktx.startAnimations
@ -15,7 +15,7 @@ import kotlin.math.roundToInt
class DenyListRvItem( class DenyListRvItem(
val info: AppProcessInfo val info: AppProcessInfo
) : ObservableDiffRvItem<DenyListRvItem>(), ComparableRv<DenyListRvItem> { ) : ObservableRvItem(), DiffItem<DenyListRvItem>, Comparable<DenyListRvItem> {
override val layoutRes get() = R.layout.item_hide_md2 override val layoutRes get() = R.layout.item_hide_md2
@ -100,7 +100,7 @@ class DenyListRvItem(
class ProcessRvItem( class ProcessRvItem(
val process: ProcessInfo val process: ProcessInfo
) : ObservableDiffRvItem<ProcessRvItem>() { ) : ObservableRvItem(), DiffItem<ProcessRvItem> {
override val layoutRes get() = R.layout.item_hide_process_md2 override val layoutRes get() = R.layout.item_hide_process_md2
@ -122,10 +122,9 @@ class ProcessRvItem(
val defaultSelection get() = val defaultSelection get() =
process.isIsolated || process.isAppZygote || process.name == process.packageName process.isIsolated || process.isAppZygote || process.name == process.packageName
override fun contentSameAs(other: ProcessRvItem) =
process.isEnabled == other.process.isEnabled
override fun itemSameAs(other: ProcessRvItem) = override fun itemSameAs(other: ProcessRvItem) =
process.name == other.process.name && process.packageName == other.process.packageName process.name == other.process.name && process.packageName == other.process.packageName
override fun contentSameAs(other: ProcessRvItem) =
process.isEnabled == other.process.isEnabled
} }

View File

@ -7,7 +7,7 @@ import androidx.lifecycle.viewModelScope
import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.arch.AsyncLoadViewModel import com.topjohnwu.magisk.arch.AsyncLoadViewModel
import com.topjohnwu.magisk.core.di.AppContext import com.topjohnwu.magisk.core.di.AppContext
import com.topjohnwu.magisk.databinding.DiffRvItemFilterList import com.topjohnwu.magisk.databinding.FilterableDiffObservableList
import com.topjohnwu.magisk.databinding.bindExtra import com.topjohnwu.magisk.databinding.bindExtra
import com.topjohnwu.magisk.databinding.set import com.topjohnwu.magisk.databinding.set
import com.topjohnwu.magisk.ktx.concurrentMap import com.topjohnwu.magisk.ktx.concurrentMap
@ -38,7 +38,7 @@ class DenyListViewModel : AsyncLoadViewModel() {
query() query()
} }
val items = DiffRvItemFilterList<DenyListRvItem>(viewModelScope) val items = FilterableDiffObservableList<DenyListRvItem>(viewModelScope)
val extraBindings = bindExtra { val extraBindings = bindExtra {
it.put(BR.viewModel, this) it.put(BR.viewModel, this)
} }
@ -50,7 +50,7 @@ class DenyListViewModel : AsyncLoadViewModel() {
@SuppressLint("InlinedApi") @SuppressLint("InlinedApi")
override suspend fun doLoadWork() { override suspend fun doLoadWork() {
loading = true loading = true
val (apps, diff) = withContext(Dispatchers.Default) { withContext(Dispatchers.Default) {
val pm = AppContext.packageManager val pm = AppContext.packageManager
val denyList = Shell.cmd("magisk --denylist ls").exec().out val denyList = Shell.cmd("magisk --denylist ls").exec().out
.map { CmdlineListItem(it) } .map { CmdlineListItem(it) }
@ -63,9 +63,8 @@ class DenyListViewModel : AsyncLoadViewModel() {
.toCollection(ArrayList(size)) .toCollection(ArrayList(size))
} }
apps.sort() apps.sort()
apps to items.calculateDiff(apps) items.update(apps)
} }
items.update(apps, diff)
query() query()
} }

View File

@ -6,14 +6,15 @@ import androidx.core.view.updateLayoutParams
import androidx.databinding.ViewDataBinding import androidx.databinding.ViewDataBinding
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.DiffRvItem import com.topjohnwu.magisk.databinding.DiffItem
import com.topjohnwu.magisk.databinding.RvContainer import com.topjohnwu.magisk.databinding.ItemWrapper
import com.topjohnwu.magisk.databinding.ViewAwareRvItem import com.topjohnwu.magisk.databinding.RvItem
import com.topjohnwu.magisk.databinding.ViewAwareItem
import kotlin.math.max import kotlin.math.max
class ConsoleItem( class ConsoleItem(
override val item: String override val item: String
) : DiffRvItem<ConsoleItem>(), ViewAwareRvItem, RvContainer<String> { ) : RvItem(), ViewAwareItem, DiffItem<ConsoleItem>, ItemWrapper<String> {
override val layoutRes = R.layout.item_console_md2 override val layoutRes = R.layout.item_console_md2
private var parentWidth = -1 private var parentWidth = -1

View File

@ -4,18 +4,17 @@ import androidx.databinding.ViewDataBinding
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.textview.MaterialTextView import com.google.android.material.textview.MaterialTextView
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.ObservableDiffRvItem import com.topjohnwu.magisk.databinding.DiffItem
import com.topjohnwu.magisk.databinding.RvContainer import com.topjohnwu.magisk.databinding.ItemWrapper
import com.topjohnwu.magisk.databinding.ViewAwareRvItem import com.topjohnwu.magisk.databinding.ObservableRvItem
import com.topjohnwu.magisk.databinding.ViewAwareItem
class LogRvItem( class LogRvItem(
override val item: String override val item: String
) : ObservableDiffRvItem<LogRvItem>(), RvContainer<String>, ViewAwareRvItem { ) : ObservableRvItem(), DiffItem<LogRvItem>, ItemWrapper<String>, ViewAwareItem {
override val layoutRes = R.layout.item_log_textview override val layoutRes = R.layout.item_log_textview
override fun itemSameAs(other: LogRvItem) = item == other.item
override fun onBind(binding: ViewDataBinding, recyclerView: RecyclerView) { override fun onBind(binding: ViewDataBinding, recyclerView: RecyclerView) {
val view = binding.root as MaterialTextView val view = binding.root as MaterialTextView
view.measure(0, 0) view.measure(0, 0)

View File

@ -11,7 +11,7 @@ import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.repository.LogRepository import com.topjohnwu.magisk.core.repository.LogRepository
import com.topjohnwu.magisk.core.utils.MediaStoreUtils import com.topjohnwu.magisk.core.utils.MediaStoreUtils
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
import com.topjohnwu.magisk.databinding.DiffRvItemList import com.topjohnwu.magisk.databinding.DiffObservableList
import com.topjohnwu.magisk.databinding.bindExtra import com.topjohnwu.magisk.databinding.bindExtra
import com.topjohnwu.magisk.databinding.set import com.topjohnwu.magisk.databinding.set
import com.topjohnwu.magisk.events.SnackbarEvent import com.topjohnwu.magisk.events.SnackbarEvent
@ -37,28 +37,26 @@ class LogViewModel(
// --- su log // --- su log
val items = DiffRvItemList<SuLogRvItem>() val items = DiffObservableList<SuLogRvItem>()
val extraBindings = bindExtra { val extraBindings = bindExtra {
it.put(BR.viewModel, this) it.put(BR.viewModel, this)
} }
// --- magisk log // --- magisk log
val logs = DiffRvItemList<LogRvItem>() val logs = DiffObservableList<LogRvItem>()
var magiskLogRaw = " " var magiskLogRaw = " "
override suspend fun doLoadWork() { override suspend fun doLoadWork() {
loading = true loading = true
val (logs, logDiff) = withContext(Dispatchers.Default) {
magiskLogRaw = repo.fetchMagiskLogs()
val logs = magiskLogRaw.split('\n').map { LogRvItem(it) }
logs to this@LogViewModel.logs.calculateDiff(logs)
}
this.logs.update(logs, logDiff)
val (suLogs, suDiff) = withContext(Dispatchers.Default) { val (suLogs, suDiff) = withContext(Dispatchers.Default) {
magiskLogRaw = repo.fetchMagiskLogs()
val newLogs = magiskLogRaw.split('\n').map { LogRvItem(it) }
logs.update(newLogs)
val suLogs = repo.fetchSuLogs().map { SuLogRvItem(it) } val suLogs = repo.fetchSuLogs().map { SuLogRvItem(it) }
suLogs to items.calculateDiff(suLogs) suLogs to items.calculateDiff(suLogs)
} }
items.firstOrNull()?.isTop = false items.firstOrNull()?.isTop = false
items.lastOrNull()?.isBottom = false items.lastOrNull()?.isBottom = false
items.update(suLogs, suDiff) items.update(suLogs, suDiff)

View File

@ -4,19 +4,17 @@ 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.core.model.su.SuLog import com.topjohnwu.magisk.core.model.su.SuLog
import com.topjohnwu.magisk.databinding.ObservableDiffRvItem import com.topjohnwu.magisk.databinding.DiffItem
import com.topjohnwu.magisk.databinding.RvContainer import com.topjohnwu.magisk.databinding.ObservableRvItem
import com.topjohnwu.magisk.databinding.set import com.topjohnwu.magisk.databinding.set
import com.topjohnwu.magisk.ktx.timeDateFormat import com.topjohnwu.magisk.ktx.timeDateFormat
import com.topjohnwu.magisk.ktx.toTime import com.topjohnwu.magisk.ktx.toTime
class SuLogRvItem( class SuLogRvItem(val log: SuLog) : ObservableRvItem(), DiffItem<SuLogRvItem> {
override val item: SuLog
) : ObservableDiffRvItem<SuLogRvItem>(), RvContainer<SuLog> {
override val layoutRes = R.layout.item_log_access_md2 override val layoutRes = R.layout.item_log_access_md2
val date = item.time.toTime(timeDateFormat) val date = log.time.toTime(timeDateFormat)
@get:Bindable @get:Bindable
var isTop = false var isTop = false
@ -26,5 +24,5 @@ class SuLogRvItem(
var isBottom = false var isBottom = false
set(value) = set(value, field, { field = it }, BR.bottom) set(value) = set(value, field, { field = it }, BR.bottom)
override fun itemSameAs(other: SuLogRvItem) = item.appName == other.item.appName override fun itemSameAs(other: SuLogRvItem) = log.appName == other.log.appName
} }

View File

@ -5,20 +5,21 @@ 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.model.module.LocalModule import com.topjohnwu.magisk.core.model.module.LocalModule
import com.topjohnwu.magisk.databinding.DiffRvItem import com.topjohnwu.magisk.databinding.DiffItem
import com.topjohnwu.magisk.databinding.ObservableDiffRvItem import com.topjohnwu.magisk.databinding.ItemWrapper
import com.topjohnwu.magisk.databinding.RvContainer import com.topjohnwu.magisk.databinding.ObservableRvItem
import com.topjohnwu.magisk.databinding.RvItem
import com.topjohnwu.magisk.databinding.set import com.topjohnwu.magisk.databinding.set
import com.topjohnwu.magisk.utils.TextHolder import com.topjohnwu.magisk.utils.TextHolder
import com.topjohnwu.magisk.utils.asText import com.topjohnwu.magisk.utils.asText
object InstallModule : DiffRvItem<InstallModule>() { object InstallModule : RvItem(), DiffItem<InstallModule> {
override val layoutRes = R.layout.item_module_download override val layoutRes = R.layout.item_module_download
} }
class LocalModuleRvItem( class LocalModuleRvItem(
override val item: LocalModule override val item: LocalModule
) : ObservableDiffRvItem<LocalModuleRvItem>(), RvContainer<LocalModule> { ) : ObservableRvItem(), DiffItem<LocalModuleRvItem>, ItemWrapper<LocalModule> {
override val layoutRes = R.layout.item_module_md2 override val layoutRes = R.layout.item_module_md2

View File

@ -10,7 +10,7 @@ import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.base.ContentResultCallback import com.topjohnwu.magisk.core.base.ContentResultCallback
import com.topjohnwu.magisk.core.model.module.LocalModule import com.topjohnwu.magisk.core.model.module.LocalModule
import com.topjohnwu.magisk.core.model.module.OnlineModule import com.topjohnwu.magisk.core.model.module.OnlineModule
import com.topjohnwu.magisk.databinding.DiffRvItemList import com.topjohnwu.magisk.databinding.DiffObservableList
import com.topjohnwu.magisk.databinding.MergeObservableList import com.topjohnwu.magisk.databinding.MergeObservableList
import com.topjohnwu.magisk.databinding.RvItem import com.topjohnwu.magisk.databinding.RvItem
import com.topjohnwu.magisk.databinding.bindExtra import com.topjohnwu.magisk.databinding.bindExtra
@ -26,7 +26,7 @@ class ModuleViewModel : AsyncLoadViewModel() {
val bottomBarBarrierIds = intArrayOf(R.id.module_update, R.id.module_remove) val bottomBarBarrierIds = intArrayOf(R.id.module_update, R.id.module_remove)
private val itemsInstalled = DiffRvItemList<LocalModuleRvItem>() private val itemsInstalled = DiffObservableList<LocalModuleRvItem>()
val items = MergeObservableList<RvItem>() val items = MergeObservableList<RvItem>()
val extraBindings = bindExtra { val extraBindings = bindExtra {
@ -57,11 +57,10 @@ class ModuleViewModel : AsyncLoadViewModel() {
override fun onNetworkChanged(network: Boolean) = startLoading() override fun onNetworkChanged(network: Boolean) = startLoading()
private suspend fun loadInstalled() { private suspend fun loadInstalled() {
val installed = LocalModule.installed().map { LocalModuleRvItem(it) } withContext(Dispatchers.Default) {
val diff = withContext(Dispatchers.Default) { val installed = LocalModule.installed().map { LocalModuleRvItem(it) }
itemsInstalled.calculateDiff(installed) itemsInstalled.update(installed)
} }
itemsInstalled.update(installed, diff)
} }
private suspend fun loadUpdateInfo() { private suspend fun loadUpdateInfo() {

View File

@ -5,8 +5,9 @@ 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.core.model.su.SuPolicy import com.topjohnwu.magisk.core.model.su.SuPolicy
import com.topjohnwu.magisk.databinding.ObservableDiffRvItem import com.topjohnwu.magisk.databinding.DiffItem
import com.topjohnwu.magisk.databinding.RvContainer import com.topjohnwu.magisk.databinding.ItemWrapper
import com.topjohnwu.magisk.databinding.ObservableRvItem
import com.topjohnwu.magisk.databinding.set import com.topjohnwu.magisk.databinding.set
class PolicyRvItem( class PolicyRvItem(
@ -16,7 +17,7 @@ class PolicyRvItem(
private val isSharedUid: Boolean, private val isSharedUid: Boolean,
val icon: Drawable, val icon: Drawable,
val appName: String val appName: String
) : ObservableDiffRvItem<PolicyRvItem>(), RvContainer<SuPolicy> { ) : ObservableRvItem(), DiffItem<PolicyRvItem>, ItemWrapper<SuPolicy> {
override val layoutRes = R.layout.item_policy_md2 override val layoutRes = R.layout.item_policy_md2

View File

@ -34,9 +34,9 @@ class SuperuserViewModel(
private val itemNoData = TextItem(R.string.superuser_policy_none) private val itemNoData = TextItem(R.string.superuser_policy_none)
private val itemsHelpers = ObservableArrayList<TextItem>() private val itemsHelpers = ObservableArrayList<TextItem>()
private val itemsPolicies = DiffRvItemList<PolicyRvItem>() private val itemsPolicies = DiffObservableList<PolicyRvItem>()
val items = MergeObservableList<AnyDiffRvItem>() val items = MergeObservableList<RvItem>()
.insertList(itemsHelpers) .insertList(itemsHelpers)
.insertList(itemsPolicies) .insertList(itemsPolicies)
val extraBindings = bindExtra { val extraBindings = bindExtra {
@ -54,7 +54,7 @@ class SuperuserViewModel(
return return
} }
loading = true loading = true
val (policies, diff) = withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
db.deleteOutdated() db.deleteOutdated()
db.delete(AppContext.applicationInfo.uid) db.delete(AppContext.applicationInfo.uid)
val policies = ArrayList<PolicyRvItem>() val policies = ArrayList<PolicyRvItem>()
@ -91,9 +91,8 @@ class SuperuserViewModel(
{ it.appName.lowercase(currentLocale) }, { it.appName.lowercase(currentLocale) },
{ it.packageName } { it.packageName }
)) ))
policies to itemsPolicies.calculateDiff(policies) itemsPolicies.update(policies)
} }
itemsPolicies.update(policies, diff)
if (itemsPolicies.isNotEmpty()) if (itemsPolicies.isNotEmpty())
itemsHelpers.clear() itemsHelpers.clear()
else if (itemsHelpers.isEmpty()) else if (itemsHelpers.isEmpty())

View File

@ -22,7 +22,14 @@ import com.google.android.material.shape.MaterialShapeDrawable
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.base.BaseActivity import com.topjohnwu.magisk.core.base.BaseActivity
import com.topjohnwu.magisk.databinding.* import com.topjohnwu.magisk.databinding.DialogMagiskBaseBinding
import com.topjohnwu.magisk.databinding.DiffItem
import com.topjohnwu.magisk.databinding.ItemWrapper
import com.topjohnwu.magisk.databinding.ObservableHost
import com.topjohnwu.magisk.databinding.RvItem
import com.topjohnwu.magisk.databinding.bindExtra
import com.topjohnwu.magisk.databinding.set
import com.topjohnwu.magisk.databinding.setAdapter
import com.topjohnwu.magisk.view.MagiskDialog.DialogClickListener import com.topjohnwu.magisk.view.MagiskDialog.DialogClickListener
typealias DialogButtonClickListener = (DialogInterface) -> Unit typealias DialogButtonClickListener = (DialogInterface) -> Unit
@ -166,7 +173,7 @@ class MagiskDialog(
class DialogItem( class DialogItem(
override val item: CharSequence, override val item: CharSequence,
val position: Int val position: Int
) : DiffRvItem<DialogItem>(), RvContainer<CharSequence> { ) : RvItem(), DiffItem<DialogItem>, ItemWrapper<CharSequence> {
override val layoutRes = R.layout.item_list_single_line override val layoutRes = R.layout.item_list_single_line
} }

View File

@ -1,9 +1,10 @@
package com.topjohnwu.magisk.view package com.topjohnwu.magisk.view
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.DiffRvItem import com.topjohnwu.magisk.databinding.DiffItem
import com.topjohnwu.magisk.databinding.RvItem
sealed class TappableHeadlineItem : DiffRvItem<TappableHeadlineItem>() { sealed class TappableHeadlineItem : RvItem(), DiffItem<TappableHeadlineItem> {
abstract val title: Int abstract val title: Int
abstract val icon: Int abstract val icon: Int

View File

@ -1,10 +1,10 @@
package com.topjohnwu.magisk.view package com.topjohnwu.magisk.view
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.DiffRvItem import com.topjohnwu.magisk.databinding.DiffItem
import com.topjohnwu.magisk.databinding.ItemWrapper
import com.topjohnwu.magisk.databinding.RvItem
class TextItem(val text: Int) : DiffRvItem<TextItem>() { class TextItem(override val item: Int) : RvItem(), DiffItem<TextItem>, ItemWrapper<Int> {
override val layoutRes = R.layout.item_text override val layoutRes = R.layout.item_text
override fun contentSameAs(other: TextItem) = text == other.text
} }

View File

@ -25,9 +25,9 @@
<include <include
android:id="@+id/log_track_container" android:id="@+id/log_track_container"
bullet="@{item.item.action ? R.drawable.ic_check_md2 : R.drawable.ic_close_md2}" bullet="@{item.log.action ? R.drawable.ic_check_md2 : R.drawable.ic_close_md2}"
isBottom="@{item.isBottom}" isBottom="@{item.isBottom}"
isSelected="@{!item.item.action}" isSelected="@{!item.log.action}"
isTop="@{item.isTop}" isTop="@{item.isTop}"
layout="@layout/item_log_track_md2" layout="@layout/item_log_track_md2"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -51,7 +51,7 @@
android:id="@+id/log_app_name" android:id="@+id/log_app_name"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@{item.item.appName}" android:text="@{item.log.appName}"
android:textAppearance="@style/AppearanceFoundation.Body" android:textAppearance="@style/AppearanceFoundation.Body"
android:textStyle="bold" android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/log_date" app:layout_constraintBottom_toTopOf="@+id/log_date"
@ -76,7 +76,7 @@
android:id="@+id/log_app_details" android:id="@+id/log_app_details"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@{String.format(`%s %s`, @string/pid(item.item.fromPid), @string/target_uid(item.item.toUid))}" android:text="@{String.format(`%s %s`, @string/pid(item.log.fromPid), @string/target_uid(item.log.toUid))}"
android:textAppearance="@style/AppearanceFoundation.Caption.Variant" android:textAppearance="@style/AppearanceFoundation.Caption.Variant"
app:layout_constraintBottom_toTopOf="@id/log_command" app:layout_constraintBottom_toTopOf="@id/log_command"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
@ -89,7 +89,7 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:fontFamily="monospace" android:fontFamily="monospace"
android:text="@{item.item.command}" android:text="@{item.log.command}"
android:textAppearance="@style/AppearanceFoundation.Caption.Variant" android:textAppearance="@style/AppearanceFoundation.Caption.Variant"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"

View File

@ -15,7 +15,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center" android:gravity="center"
android:padding="@dimen/l1" android:padding="@dimen/l1"
android:text="@{item.text}" android:text="@{item.item}"
android:textAppearance="@style/AppearanceFoundation.Tiny.Variant" android:textAppearance="@style/AppearanceFoundation.Tiny.Variant"
tools:text="@tools:sample/lorem/random" /> tools:text="@tools:sample/lorem/random" />