You've already forked Magisk
mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-09-06 06:36:58 +00:00
Redesign is now the new norm
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
package com.topjohnwu.magisk.ui.hide
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.databinding.FragmentHideMd2Binding
|
||||
import com.topjohnwu.magisk.ui.compat.CompatFragment
|
||||
import com.topjohnwu.magisk.ui.compat.hideKeyboard
|
||||
import com.topjohnwu.magisk.utils.MotionRevealHelper
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
|
||||
class HideFragment : CompatFragment<HideViewModel, FragmentHideMd2Binding>() {
|
||||
|
||||
override val layoutRes = R.layout.fragment_hide_md2
|
||||
override val viewModel by viewModel<HideViewModel>()
|
||||
|
||||
private var isFilterVisible
|
||||
get() = binding.hideFilter.isVisible
|
||||
set(value) {
|
||||
if (!value) hideKeyboard()
|
||||
MotionRevealHelper.withViews(binding.hideFilter, binding.hideFilterToggle, value)
|
||||
}
|
||||
|
||||
override fun consumeSystemWindowInsets(insets: Insets) = insets
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
activity.setTitle(R.string.magiskhide)
|
||||
setHasOptionsMenu(true)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
binding.hideFilterToggle.setOnClickListener {
|
||||
isFilterVisible = true
|
||||
}
|
||||
binding.hideFilterInclude.hideFilterDone.setOnClickListener {
|
||||
isFilterVisible = false
|
||||
}
|
||||
binding.hideContent.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
|
||||
if (newState != RecyclerView.SCROLL_STATE_IDLE) hideKeyboard()
|
||||
}
|
||||
})
|
||||
|
||||
val lama = binding.hideContent.layoutManager ?: return
|
||||
lama.isAutoMeasureEnabled = false
|
||||
}
|
||||
|
||||
override fun onPreBind(binding: FragmentHideMd2Binding) = Unit
|
||||
|
||||
override fun onBackPressed(): Boolean {
|
||||
if (isFilterVisible) {
|
||||
isFilterVisible = false
|
||||
return true
|
||||
}
|
||||
return super.onBackPressed()
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.menu_hide_md2, menu)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.action_focus_up -> binding.hideContent
|
||||
.takeIf { (it.layoutManager as? LinearLayoutManager)?.findFirstVisibleItemPosition() ?: 0 > 10 }
|
||||
?.also { it.scrollToPosition(10) }
|
||||
.let { binding.hideContent }
|
||||
.also { it.post { it.smoothScrollToPosition(0) } }
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package com.topjohnwu.magisk.ui.hide
|
||||
|
||||
import android.content.pm.ApplicationInfo
|
||||
import androidx.databinding.Bindable
|
||||
import com.topjohnwu.magisk.BR
|
||||
import com.topjohnwu.magisk.data.repository.MagiskRepository
|
||||
import com.topjohnwu.magisk.extensions.subscribeK
|
||||
import com.topjohnwu.magisk.extensions.toggle
|
||||
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.ui.compat.CompatViewModel
|
||||
import com.topjohnwu.magisk.ui.compat.Queryable
|
||||
import com.topjohnwu.magisk.ui.compat.filterableListOf
|
||||
import com.topjohnwu.magisk.ui.compat.itemBindingOf
|
||||
import com.topjohnwu.magisk.utils.KObservableField
|
||||
import com.topjohnwu.magisk.utils.currentLocale
|
||||
|
||||
class HideViewModel(
|
||||
private val magiskRepo: MagiskRepository
|
||||
) : CompatViewModel(), Queryable by Queryable.impl(1000) {
|
||||
|
||||
override val queryRunnable = Runnable { query() }
|
||||
|
||||
var isShowSystem = false
|
||||
@Bindable get
|
||||
set(value) {
|
||||
field = value
|
||||
notifyPropertyChanged(BR.showSystem)
|
||||
query()
|
||||
}
|
||||
|
||||
var query = ""
|
||||
@Bindable get
|
||||
set(value) {
|
||||
field = value
|
||||
notifyPropertyChanged(BR.query)
|
||||
submitQuery()
|
||||
}
|
||||
val items = filterableListOf<HideItem>()
|
||||
val itemBinding = itemBindingOf<HideItem> {
|
||||
it.bindExtra(BR.viewModel, this)
|
||||
}
|
||||
val itemInternalBinding = itemBindingOf<HideProcessItem> {
|
||||
it.bindExtra(BR.viewModel, this)
|
||||
}
|
||||
|
||||
val isFilterExpanded = KObservableField(false)
|
||||
|
||||
override fun refresh() = magiskRepo.fetchApps()
|
||||
.map { it to magiskRepo.fetchHideTargets().blockingGet() }
|
||||
.map { pair -> pair.first.map { mergeAppTargets(it, pair.second) } }
|
||||
.flattenAsFlowable { it }
|
||||
.map { HideItem(it) }
|
||||
.toList()
|
||||
.map { it.sort() }
|
||||
.map { it to items.calculateDiff(it) }
|
||||
.applyViewModel(this)
|
||||
.subscribeK {
|
||||
items.update(it.first, it.second)
|
||||
submitQuery()
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
private fun mergeAppTargets(a: HideAppInfo, ts: List<HideTarget>): ProcessHideApp {
|
||||
val relevantTargets = ts.filter { it.packageName == a.info.packageName }
|
||||
val packageName = a.info.packageName
|
||||
val processes = a.processes
|
||||
.map { StatefulProcess(it, packageName, relevantTargets.any { i -> it == i.process }) }
|
||||
return ProcessHideApp(a, processes)
|
||||
}
|
||||
|
||||
private fun List<HideItem>.sort() = compareByDescending<HideItem> { it.itemsChecked.value }
|
||||
.thenBy { it.item.info.name.toLowerCase(currentLocale) }
|
||||
.thenBy { it.item.info.info.packageName }
|
||||
.let { sortedWith(it) }
|
||||
|
||||
// ---
|
||||
|
||||
override fun submitQuery() {
|
||||
queryHandler.removeCallbacks(queryRunnable)
|
||||
queryHandler.postDelayed(queryRunnable, queryDelay)
|
||||
}
|
||||
|
||||
private fun query(
|
||||
query: String = this.query,
|
||||
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) }
|
||||
return inName || inPackage || inProcesses
|
||||
}
|
||||
|
||||
filterSystem() && filterQuery()
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
fun toggleItem(item: HideProcessItem) = magiskRepo
|
||||
.toggleHide(item.isHidden.value, item.item.packageName, item.item.name)
|
||||
|
||||
fun toggle(item: KObservableField<Boolean>) = item.toggle()
|
||||
|
||||
fun resetQuery() {
|
||||
query = ""
|
||||
}
|
||||
|
||||
fun hideFilter() {
|
||||
isFilterExpanded.value = false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user