From 6aa22267f46e499555c4f16d196d3425cb34efdf Mon Sep 17 00:00:00 2001 From: Viktor De Pasquale Date: Thu, 31 Oct 2019 20:34:07 +0100 Subject: [PATCH] Updated Hide screen to be fully functioning ...although still misses search :( --- .../com/topjohnwu/magisk/di/RedesignModule.kt | 2 +- .../magisk/model/entity/HideAppInfo.kt | 1 + .../model/entity/recycler/HideRvItem.kt | 24 +++- .../magisk/redesign/hide/HideFragment.kt | 14 ++- .../magisk/redesign/hide/HideViewModel.kt | 105 ++++++++---------- .../magisk/utils/DataBindingAdapters.kt | 5 + .../magisk/utils/DiffObservableList.kt | 6 +- .../utils/FilterableDiffObservableList.kt | 85 ++++++++++++++ app/src/main/res/layout/item_hide_md2.xml | 5 + .../main/res/layout/item_hide_process_md2.xml | 6 +- app/src/main/res/menu/menu_hide_item.xml | 9 ++ app/src/main/res/menu/menu_hide_md2.xml | 9 ++ 12 files changed, 202 insertions(+), 69 deletions(-) create mode 100644 app/src/main/java/com/topjohnwu/magisk/utils/FilterableDiffObservableList.kt create mode 100644 app/src/main/res/menu/menu_hide_item.xml create mode 100644 app/src/main/res/menu/menu_hide_md2.xml diff --git a/app/src/main/java/com/topjohnwu/magisk/di/RedesignModule.kt b/app/src/main/java/com/topjohnwu/magisk/di/RedesignModule.kt index 36effd49a..631e7b0dc 100644 --- a/app/src/main/java/com/topjohnwu/magisk/di/RedesignModule.kt +++ b/app/src/main/java/com/topjohnwu/magisk/di/RedesignModule.kt @@ -17,7 +17,7 @@ import org.koin.dsl.module val redesignModule = module { viewModel { FlashViewModel() } - viewModel { HideViewModel(get(), get()) } + viewModel { HideViewModel(get()) } viewModel { HomeViewModel(get()) } viewModel { LogViewModel() } viewModel { ModuleViewModel() } diff --git a/app/src/main/java/com/topjohnwu/magisk/model/entity/HideAppInfo.kt b/app/src/main/java/com/topjohnwu/magisk/model/entity/HideAppInfo.kt index 6cbc24c5c..73d1410ea 100644 --- a/app/src/main/java/com/topjohnwu/magisk/model/entity/HideAppInfo.kt +++ b/app/src/main/java/com/topjohnwu/magisk/model/entity/HideAppInfo.kt @@ -17,6 +17,7 @@ class HideAppInfo( data class StatefulProcess( val name: String, + val packageName: String, val isHidden: Boolean ) diff --git a/app/src/main/java/com/topjohnwu/magisk/model/entity/recycler/HideRvItem.kt b/app/src/main/java/com/topjohnwu/magisk/model/entity/recycler/HideRvItem.kt index ceb0390d1..9e50a8cc9 100644 --- a/app/src/main/java/com/topjohnwu/magisk/model/entity/recycler/HideRvItem.kt +++ b/app/src/main/java/com/topjohnwu/magisk/model/entity/recycler/HideRvItem.kt @@ -1,5 +1,6 @@ package com.topjohnwu.magisk.model.entity.recycler +import android.view.MenuItem import android.view.View import android.view.ViewGroup import com.topjohnwu.magisk.R @@ -14,6 +15,7 @@ import com.topjohnwu.magisk.model.entity.ProcessHideApp import com.topjohnwu.magisk.model.entity.StatefulProcess import com.topjohnwu.magisk.model.entity.state.IndeterminateState import com.topjohnwu.magisk.model.events.HideProcessEvent +import com.topjohnwu.magisk.redesign.hide.HideViewModel import com.topjohnwu.magisk.utils.DiffObservableList import com.topjohnwu.magisk.utils.KObservableField import com.topjohnwu.magisk.utils.RxBus @@ -22,14 +24,18 @@ class HideItem(val item: ProcessHideApp) : ComparableRvItem() { override val layoutRes = R.layout.item_hide_md2 + val packageName = item.info.info.packageName.orEmpty() val items = item.processes.map { HideProcessItem(it) } val isExpanded = KObservableField(false) val itemsChecked = KObservableField(0) - val isHidden get() = itemsChecked.value == items.size + + /** [toggle] depends on this functionality */ + private val isHidden get() = itemsChecked.value == items.size init { items.forEach { it.isHidden.addOnPropertyChangedCallback { recalculateChecked() } } + recalculateChecked() } fun collapse(v: View) { @@ -42,6 +48,17 @@ class HideItem(val item: ProcessHideApp) : ComparableRvItem() { isExpanded.value = true } + fun toggle(menuItem: MenuItem, viewModel: HideViewModel): Boolean { + if (menuItem.itemId != R.id.action_toggle) return false + // contract implies that isHidden == all checked + if (!isHidden) { + items.filterNot { it.isHidden.value } + } else { + items + }.forEach { it.toggle(viewModel) } + return true + } + private fun recalculateChecked() { itemsChecked.value = items.count { it.isHidden.value } } @@ -57,7 +74,10 @@ class HideProcessItem(val item: StatefulProcess) : ComparableRvItem() { override fun onAttach(context: Context) { super.onAttach(context) - activity.setTitle(R.string.magiskhide) + setHasOptionsMenu(true) + } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + inflater.inflate(R.menu.menu_hide_md2, menu) + menu.findItem(R.id.action_show_system)?.isChecked = viewModel.isShowSystem + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return viewModel.menuItemPressed(item) } } \ No newline at end of file diff --git a/app/src/main/java/com/topjohnwu/magisk/redesign/hide/HideViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/redesign/hide/HideViewModel.kt index a9be0ade7..f78cb65f0 100644 --- a/app/src/main/java/com/topjohnwu/magisk/redesign/hide/HideViewModel.kt +++ b/app/src/main/java/com/topjohnwu/magisk/redesign/hide/HideViewModel.kt @@ -1,46 +1,37 @@ package com.topjohnwu.magisk.redesign.hide import android.content.pm.ApplicationInfo +import android.view.MenuItem import com.topjohnwu.magisk.BR +import com.topjohnwu.magisk.R import com.topjohnwu.magisk.data.repository.MagiskRepository +import com.topjohnwu.magisk.databinding.ComparableRvItem import com.topjohnwu.magisk.extensions.subscribeK -import com.topjohnwu.magisk.extensions.toSingle import com.topjohnwu.magisk.model.entity.HideAppInfo import com.topjohnwu.magisk.model.entity.HideTarget import com.topjohnwu.magisk.model.entity.ProcessHideApp import com.topjohnwu.magisk.model.entity.StatefulProcess import com.topjohnwu.magisk.model.entity.recycler.HideItem import com.topjohnwu.magisk.model.entity.recycler.HideProcessItem -import com.topjohnwu.magisk.model.entity.recycler.HideProcessRvItem -import com.topjohnwu.magisk.model.events.HideProcessEvent import com.topjohnwu.magisk.redesign.compat.CompatViewModel import com.topjohnwu.magisk.redesign.home.itemBindingOf -import com.topjohnwu.magisk.redesign.superuser.diffListOf +import com.topjohnwu.magisk.utils.DiffObservableList +import com.topjohnwu.magisk.utils.FilterableDiffObservableList import com.topjohnwu.magisk.utils.KObservableField -import com.topjohnwu.magisk.utils.RxBus import com.topjohnwu.magisk.utils.currentLocale -import io.reactivex.disposables.Disposable -import java.util.* class HideViewModel( - private val magiskRepo: MagiskRepository, - rxBus: RxBus + private val magiskRepo: MagiskRepository ) : CompatViewModel() { - @Volatile - private var cache = listOf() + var isShowSystem = false set(value) { - field = Collections.synchronizedList(value) - } - private var queryJob: Disposable? = null - set(value) { - field?.dispose() field = value + query() } val query = KObservableField("") - val isShowSystem = KObservableField(true) - val items = diffListOf() + val items = filterableListOf() val itemBinding = itemBindingOf { it.bindExtra(BR.viewModel, this) } @@ -48,12 +39,6 @@ class HideViewModel( it.bindExtra(BR.viewModel, this) } - init { - rxBus.register() - .subscribeK { toggleItem(it.item) } - .add() - } - override fun refresh() = magiskRepo.fetchApps() .map { it to magiskRepo.fetchHideTargets().blockingGet() } .map { pair -> pair.first.map { mergeAppTargets(it, pair.second) } } @@ -61,64 +46,66 @@ class HideViewModel( .map { HideItem(it) } .toList() .map { it.sort() } + .map { it to items.calculateDiff(it) } .subscribeK { - cache = it - queryIfNecessary() + items.update(it.first, it.second) + query() } - override fun onCleared() { - queryJob?.dispose() - super.onCleared() - } - // --- private fun mergeAppTargets(a: HideAppInfo, ts: List): ProcessHideApp { val relevantTargets = ts.filter { it.packageName == a.info.packageName } + val packageName = a.info.packageName val processes = a.processes - .map { StatefulProcess(it, relevantTargets.any { i -> it == i.process }) } + .map { StatefulProcess(it, packageName, relevantTargets.any { i -> it == i.process }) } return ProcessHideApp(a, processes) } - private fun List.sort() = sortedWith(compareBy( - { it.isHidden }, - { it.item.info.name.toLowerCase(currentLocale) }, - { it.item.info.info.packageName } - )) + private fun List.sort() = compareByDescending { it.itemsChecked.value } + .thenBy { it.item.info.name.toLowerCase(currentLocale) } + .thenBy { it.item.info.info.packageName } + .let { sortedWith(it) } // --- - /** We don't need to re-query when the app count matches. */ - private fun queryIfNecessary() { - if (items.size != cache.size) { - query() - } - } - private fun query( query: String = this.query.value, - showSystem: Boolean = isShowSystem.value - ) = cache.toSingle() - .flattenAsFlowable { it } - .parallel() - .filter { showSystem || it.item.info.info.flags and ApplicationInfo.FLAG_SYSTEM == 0 } - .filter { + showSystem: Boolean = isShowSystem + ) = items.filter { + fun filterSystem(): Boolean { + return showSystem || it.item.info.info.flags and ApplicationInfo.FLAG_SYSTEM == 0 + } + + fun filterQuery(): Boolean { val inName = it.item.info.name.contains(query, true) val inPackage = it.item.info.info.packageName.contains(query, true) val inProcesses = it.item.processes.any { it.name.contains(query, true) } - inName || inPackage || inProcesses + return inName || inPackage || inProcesses } - .sequential() - .toList() - .map { it to items.calculateDiff(it) } - .subscribeK { items.update(it.first, it.second) } - .let { queryJob = it } + + filterSystem() && filterQuery() + } // --- - private fun toggleItem(item: HideProcessRvItem) = magiskRepo - .toggleHide(item.isHidden.value, item.packageName, item.process) + fun menuItemPressed(menuItem: MenuItem) = when (menuItem.itemId) { + R.id.action_show_system -> isShowSystem = (!menuItem.isChecked) + .also { menuItem.isChecked = it } + else -> null + }?.let { true } ?: false + + fun toggleItem(item: HideProcessItem) = magiskRepo + .toggleHide(item.isHidden.value, item.item.packageName, item.item.name) + // might wanna reorder the list to display the item at the top .subscribeK() .add() -} \ No newline at end of file +} + +inline fun > filterableListOf( + vararg newItems: T +) = FilterableDiffObservableList(object : DiffObservableList.Callback { + override fun areItemsTheSame(oldItem: T, newItem: T) = oldItem.genericItemSameAs(newItem) + override fun areContentsTheSame(oldItem: T, newItem: T) = oldItem.genericContentSameAs(newItem) +}).also { it.update(newItems.toList()) } \ No newline at end of file diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/DataBindingAdapters.kt b/app/src/main/java/com/topjohnwu/magisk/utils/DataBindingAdapters.kt index b65a58fff..9d8c0d3d5 100644 --- a/app/src/main/java/com/topjohnwu/magisk/utils/DataBindingAdapters.kt +++ b/app/src/main/java/com/topjohnwu/magisk/utils/DataBindingAdapters.kt @@ -403,4 +403,9 @@ fun MaterialCardView.setCardElevationBound(elevation: Float) { @BindingAdapter("strokeWidth") fun MaterialCardView.setCardStrokeWidthBound(stroke: Float) { strokeWidth = stroke.roundToInt() +} + +@BindingAdapter("onMenuClick") +fun Toolbar.setOnMenuClickListener(listener: Toolbar.OnMenuItemClickListener) { + setOnMenuItemClickListener(listener) } \ No newline at end of file diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/DiffObservableList.kt b/app/src/main/java/com/topjohnwu/magisk/utils/DiffObservableList.kt index c7c61d968..b658d08fe 100644 --- a/app/src/main/java/com/topjohnwu/magisk/utils/DiffObservableList.kt +++ b/app/src/main/java/com/topjohnwu/magisk/utils/DiffObservableList.kt @@ -18,9 +18,9 @@ open class DiffObservableList( ) : AbstractList(), ObservableList { private val LIST_LOCK = Object() - private var list: MutableList = ArrayList() + protected var list: MutableList = ArrayList() private val listeners = ListChangeRegistry() - private val listCallback = ObservableListUpdateCallback() + protected val listCallback = ObservableListUpdateCallback() override val size: Int get() = list.size @@ -38,7 +38,7 @@ open class DiffObservableList( return doCalculateDiff(frozenList, newItems) } - private fun doCalculateDiff(oldItems: List, newItems: List?): DiffUtil.DiffResult { + protected fun doCalculateDiff(oldItems: List, newItems: List?): DiffUtil.DiffResult { return DiffUtil.calculateDiff(object : DiffUtil.Callback() { override fun getOldListSize() = oldItems.size diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/FilterableDiffObservableList.kt b/app/src/main/java/com/topjohnwu/magisk/utils/FilterableDiffObservableList.kt new file mode 100644 index 000000000..80e10f968 --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/utils/FilterableDiffObservableList.kt @@ -0,0 +1,85 @@ +package com.topjohnwu.magisk.utils + +import android.os.Handler +import android.os.HandlerThread +import android.os.Looper +import java.util.* + +class FilterableDiffObservableList( + callback: Callback +) : DiffObservableList(callback) { + + var filter: ((T) -> Boolean)? = null + set(value) { + field = value + queueUpdate() + } + @Volatile + private var sublist: MutableList = super.list + + // --- + + private val ui by lazy { Handler(Looper.getMainLooper()) } + private val handler = Handler(HandlerThread("List${hashCode()}").apply { start() }.looper) + private val updater = Runnable { + val filter = filter ?: { true } + val newList = super.list.filter(filter) + val diff = synchronized(this) { doCalculateDiff(sublist, newList) } + ui.post { + sublist = Collections.synchronizedList(newList) + diff.dispatchUpdatesTo(listCallback) + } + } + + private fun queueUpdate() { + handler.removeCallbacks(updater) + handler.post(updater) + } + + fun hasFilter() = filter != null + + fun filter(switch: (T) -> Boolean) { + filter = switch + } + + fun reset() { + filter = null + } + + // --- + + override fun get(index: Int): T { + return sublist.get(index) + } + + override fun add(element: T): Boolean { + return sublist.add(element) + } + + override fun add(index: Int, element: T) { + sublist.add(index, element) + } + + override fun addAll(elements: Collection): Boolean { + return sublist.addAll(elements) + } + + override fun addAll(index: Int, elements: Collection): Boolean { + return sublist.addAll(index, elements) + } + + override fun remove(element: T): Boolean { + return sublist.remove(element) + } + + override fun removeAt(index: Int): T { + return sublist.removeAt(index) + } + + override fun set(index: Int, element: T): T { + return sublist.set(index, element) + } + + override val size: Int + get() = sublist.size +} \ No newline at end of file diff --git a/app/src/main/res/layout/item_hide_md2.xml b/app/src/main/res/layout/item_hide_md2.xml index f076fdb93..dd02ade2f 100644 --- a/app/src/main/res/layout/item_hide_md2.xml +++ b/app/src/main/res/layout/item_hide_md2.xml @@ -59,6 +59,8 @@ diff --git a/app/src/main/res/layout/item_hide_process_md2.xml b/app/src/main/res/layout/item_hide_process_md2.xml index fb08d69f9..79be5fc5a 100644 --- a/app/src/main/res/layout/item_hide_process_md2.xml +++ b/app/src/main/res/layout/item_hide_process_md2.xml @@ -17,6 +17,8 @@ @@ -41,10 +43,8 @@ style="?styleImageSmall" isSelected="@{item.isHidden}" android:layout_marginTop="@dimen/l_50" - android:layout_marginEnd="@dimen/l1" + android:layout_marginEnd="@dimen/l_75" android:layout_marginBottom="@dimen/l_50" - android:background="?selectableItemBackgroundBorderless" - android:onClick="@{() -> item.toggle()}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" diff --git a/app/src/main/res/menu/menu_hide_item.xml b/app/src/main/res/menu/menu_hide_item.xml new file mode 100644 index 000000000..6973f2634 --- /dev/null +++ b/app/src/main/res/menu/menu_hide_item.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_hide_md2.xml b/app/src/main/res/menu/menu_hide_md2.xml new file mode 100644 index 000000000..9514ec2bc --- /dev/null +++ b/app/src/main/res/menu/menu_hide_md2.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file