mirror of
https://github.com/topjohnwu/Magisk.git
synced 2024-12-25 12:47:38 +00:00
Added navigation delegation to bypass default one
By making a delegate like such we protect ourselves against intrusions in views' logic
This commit is contained in:
parent
2daa131fb2
commit
0eb28c3265
@ -1,8 +1,15 @@
|
|||||||
package com.topjohnwu.magisk.model.events
|
package com.topjohnwu.magisk.model.events
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import com.karumi.dexter.Dexter
|
||||||
|
import com.karumi.dexter.MultiplePermissionsReport
|
||||||
|
import com.karumi.dexter.PermissionToken
|
||||||
|
import com.karumi.dexter.listener.PermissionRequest
|
||||||
|
import com.karumi.dexter.listener.multi.MultiplePermissionsListener
|
||||||
import com.skoumal.teanity.viewevents.ViewEvent
|
import com.skoumal.teanity.viewevents.ViewEvent
|
||||||
import com.topjohnwu.magisk.model.entity.module.Repo
|
import com.topjohnwu.magisk.model.entity.module.Repo
|
||||||
|
import com.topjohnwu.magisk.model.permissions.PermissionRequestBuilder
|
||||||
import io.reactivex.subjects.PublishSubject
|
import io.reactivex.subjects.PublishSubject
|
||||||
|
|
||||||
|
|
||||||
@ -19,7 +26,9 @@ class EnvFixEvent : ViewEvent()
|
|||||||
|
|
||||||
class UpdateSafetyNetEvent : ViewEvent()
|
class UpdateSafetyNetEvent : ViewEvent()
|
||||||
|
|
||||||
class ViewActionEvent(val action: Activity.() -> Unit) : ViewEvent()
|
class ViewActionEvent(val action: Activity.() -> Unit) : ViewEvent(), ActivityExecutor {
|
||||||
|
override fun invoke(activity: AppCompatActivity) = activity.run(action)
|
||||||
|
}
|
||||||
|
|
||||||
class OpenFilePickerEvent : ViewEvent()
|
class OpenFilePickerEvent : ViewEvent()
|
||||||
|
|
||||||
@ -31,8 +40,40 @@ class PageChangedEvent : ViewEvent()
|
|||||||
class PermissionEvent(
|
class PermissionEvent(
|
||||||
val permissions: List<String>,
|
val permissions: List<String>,
|
||||||
val callback: PublishSubject<Boolean>
|
val callback: PublishSubject<Boolean>
|
||||||
) : ViewEvent()
|
) : ViewEvent(), ActivityExecutor {
|
||||||
|
|
||||||
class BackPressEvent : ViewEvent()
|
private val permissionRequest = PermissionRequestBuilder().apply {
|
||||||
|
onSuccess {
|
||||||
|
callback.onNext(true)
|
||||||
|
}
|
||||||
|
onFailure {
|
||||||
|
callback.onNext(false)
|
||||||
|
callback.onError(SecurityException("User refused permissions"))
|
||||||
|
}
|
||||||
|
}.build()
|
||||||
|
|
||||||
|
override fun invoke(activity: AppCompatActivity) = Dexter.withActivity(activity)
|
||||||
|
.withPermissions(permissions)
|
||||||
|
.withListener(object : MultiplePermissionsListener {
|
||||||
|
override fun onPermissionRationaleShouldBeShown(
|
||||||
|
permissions: MutableList<PermissionRequest>,
|
||||||
|
token: PermissionToken
|
||||||
|
) = token.continuePermissionRequest()
|
||||||
|
|
||||||
|
override fun onPermissionsChecked(
|
||||||
|
report: MultiplePermissionsReport
|
||||||
|
) = if (report.areAllPermissionsGranted()) {
|
||||||
|
permissionRequest.onSuccess()
|
||||||
|
} else {
|
||||||
|
permissionRequest.onFailure()
|
||||||
|
}
|
||||||
|
}).check()
|
||||||
|
}
|
||||||
|
|
||||||
|
class BackPressEvent : ViewEvent(), ActivityExecutor {
|
||||||
|
override fun invoke(activity: AppCompatActivity) {
|
||||||
|
activity.onBackPressed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class DieEvent : ViewEvent()
|
class DieEvent : ViewEvent()
|
@ -3,21 +3,29 @@ package com.topjohnwu.magisk.model.navigation
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.annotation.AnimRes
|
import androidx.annotation.AnimRes
|
||||||
import androidx.annotation.AnimatorRes
|
import androidx.annotation.AnimatorRes
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import com.skoumal.teanity.viewevents.NavigationDslMarker
|
import com.skoumal.teanity.viewevents.NavigationDslMarker
|
||||||
import com.skoumal.teanity.viewevents.ViewEvent
|
import com.skoumal.teanity.viewevents.ViewEvent
|
||||||
|
import com.topjohnwu.magisk.model.events.ActivityExecutor
|
||||||
|
import com.topjohnwu.magisk.redesign.compat.CompatActivity
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
class MagiskNavigationEvent(
|
class MagiskNavigationEvent(
|
||||||
val navDirections: MagiskNavDirectionsBuilder,
|
val navDirections: MagiskNavDirectionsBuilder,
|
||||||
val navOptions: MagiskNavOptions,
|
val navOptions: MagiskNavOptions,
|
||||||
val animOptions: MagiskAnimBuilder
|
val animOptions: MagiskAnimBuilder
|
||||||
) : ViewEvent() {
|
) : ViewEvent(), ActivityExecutor {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
operator fun invoke(builder: Builder.() -> Unit) = Builder().apply(builder).build()
|
operator fun invoke(builder: Builder.() -> Unit) = Builder().apply(builder).build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun invoke(activity: AppCompatActivity) {
|
||||||
|
if (activity !is CompatActivity<*, *>) return
|
||||||
|
activity.navigation.navigateTo(this)
|
||||||
|
}
|
||||||
|
|
||||||
@NavigationDslMarker
|
@NavigationDslMarker
|
||||||
class Builder {
|
class Builder {
|
||||||
|
|
||||||
|
@ -29,6 +29,7 @@ open class MainActivity : CompatActivity<MainViewModel, ActivityMainMd2Binding>(
|
|||||||
override val layoutRes = R.layout.activity_main_md2
|
override val layoutRes = R.layout.activity_main_md2
|
||||||
override val viewModel by viewModel<MainViewModel>()
|
override val viewModel by viewModel<MainViewModel>()
|
||||||
override val navHostId: Int = R.id.main_nav_host
|
override val navHostId: Int = R.id.main_nav_host
|
||||||
|
override val navHost: Int = R.id.main_nav_host
|
||||||
override val defaultPosition: Int = 0
|
override val defaultPosition: Int = 0
|
||||||
|
|
||||||
override val baseFragments: List<KClass<out Fragment>> = listOf(
|
override val baseFragments: List<KClass<out Fragment>> = listOf(
|
||||||
@ -38,7 +39,6 @@ open class MainActivity : CompatActivity<MainViewModel, ActivityMainMd2Binding>(
|
|||||||
LogFragment::class,
|
LogFragment::class,
|
||||||
SettingsFragment::class
|
SettingsFragment::class
|
||||||
)
|
)
|
||||||
|
|
||||||
//This temporarily fixes unwanted feature of BottomNavigationView - where the view applies
|
//This temporarily fixes unwanted feature of BottomNavigationView - where the view applies
|
||||||
//padding on itself given insets are not consumed beforehand. Unfortunately the listener
|
//padding on itself given insets are not consumed beforehand. Unfortunately the listener
|
||||||
//implementation doesn't favor us against the design library, so on re-create it's often given
|
//implementation doesn't favor us against the design library, so on re-create it's often given
|
||||||
|
@ -10,13 +10,19 @@ abstract class CompatActivity<ViewModel : CompatViewModel, Binding : ViewDataBin
|
|||||||
MagiskActivity<ViewModel, Binding>(), CompatView<ViewModel> {
|
MagiskActivity<ViewModel, Binding>(), CompatView<ViewModel> {
|
||||||
|
|
||||||
override val viewRoot: View get() = binding.root
|
override val viewRoot: View get() = binding.root
|
||||||
|
override val navigation: CompatNavigationDelegate<CompatActivity<ViewModel, Binding>>? by lazy {
|
||||||
|
CompatNavigationDelegate(this)
|
||||||
|
}
|
||||||
|
|
||||||
private val delegate by lazy { CompatDelegate(this) }
|
private val delegate by lazy { CompatDelegate(this) }
|
||||||
|
|
||||||
|
internal abstract val navHost: Int
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
delegate.ensureInsets()
|
delegate.onCreate()
|
||||||
|
navigation?.onCreate(savedInstanceState)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
@ -25,12 +31,23 @@ abstract class CompatActivity<ViewModel : CompatViewModel, Binding : ViewDataBin
|
|||||||
delegate.onResume()
|
delegate.onResume()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
super.onSaveInstanceState(outState)
|
||||||
|
navigation?.onSaveInstanceState(outState)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onEventDispatched(event: ViewEvent) {
|
override fun onEventDispatched(event: ViewEvent) {
|
||||||
super.onEventDispatched(event)
|
super.onEventDispatched(event)
|
||||||
|
|
||||||
delegate.onEventExecute(event, this)
|
delegate.onEventExecute(event, this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onBackPressed() {
|
||||||
|
if (navigation?.onBackPressed()?.not() == true) {
|
||||||
|
super.onBackPressed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected fun ViewEvent.dispatchOnSelf() = onEventDispatched(this)
|
protected fun ViewEvent.dispatchOnSelf() = onEventDispatched(this)
|
||||||
|
|
||||||
}
|
}
|
@ -15,6 +15,11 @@ class CompatDelegate internal constructor(
|
|||||||
private val view: CompatView<*>
|
private val view: CompatView<*>
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
fun onCreate() {
|
||||||
|
ensureInsets()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
fun onResume() {
|
fun onResume() {
|
||||||
view.viewModel.requestRefresh()
|
view.viewModel.requestRefresh()
|
||||||
}
|
}
|
||||||
@ -33,7 +38,7 @@ class CompatDelegate internal constructor(
|
|||||||
(event as? ActivityExecutor)?.invoke(fragment.requireActivity() as AppCompatActivity)
|
(event as? ActivityExecutor)?.invoke(fragment.requireActivity() as AppCompatActivity)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ensureInsets() {
|
private fun ensureInsets() {
|
||||||
ViewCompat.setOnApplyWindowInsetsListener(view.viewRoot) { _, insets ->
|
ViewCompat.setOnApplyWindowInsetsListener(view.viewRoot) { _, insets ->
|
||||||
insets.asInsets()
|
insets.asInsets()
|
||||||
.also { view.peekSystemWindowInsets(it) }
|
.also { view.peekSystemWindowInsets(it) }
|
||||||
|
@ -10,13 +10,16 @@ abstract class CompatFragment<ViewModel : CompatViewModel, Binding : ViewDataBin
|
|||||||
: MagiskFragment<ViewModel, Binding>(), CompatView<ViewModel> {
|
: MagiskFragment<ViewModel, Binding>(), CompatView<ViewModel> {
|
||||||
|
|
||||||
override val viewRoot: View get() = binding.root
|
override val viewRoot: View get() = binding.root
|
||||||
|
override val navigation by lazy { compatActivity.navigation }
|
||||||
|
|
||||||
private val delegate by lazy { CompatDelegate(this) }
|
private val delegate by lazy { CompatDelegate(this) }
|
||||||
|
|
||||||
|
private val compatActivity get() = requireActivity() as CompatActivity<*, *>
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
delegate.ensureInsets()
|
delegate.onCreate()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
|
@ -0,0 +1,112 @@
|
|||||||
|
package com.topjohnwu.magisk.redesign.compat
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.fragment.app.FragmentTransaction
|
||||||
|
import com.ncapdevi.fragnav.FragNavController
|
||||||
|
import com.ncapdevi.fragnav.FragNavTransactionOptions
|
||||||
|
import com.topjohnwu.magisk.model.navigation.MagiskAnimBuilder
|
||||||
|
import com.topjohnwu.magisk.model.navigation.MagiskNavigationEvent
|
||||||
|
import com.topjohnwu.magisk.model.navigation.Navigator
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
class CompatNavigationDelegate<Source>(
|
||||||
|
private val source: Source,
|
||||||
|
private val listener: FragNavController.TransactionListener? = null
|
||||||
|
) : FragNavController.RootFragmentListener where Source : CompatActivity<*, *>, Source : Navigator {
|
||||||
|
|
||||||
|
private val controller by lazy {
|
||||||
|
check(source.navHost != 0) { "Did you forget to override \"navHostId\"?" }
|
||||||
|
FragNavController(source.supportFragmentManager, source.navHost)
|
||||||
|
}
|
||||||
|
|
||||||
|
val isRoot get() = controller.isRootFragment
|
||||||
|
|
||||||
|
|
||||||
|
//region Listener
|
||||||
|
override val numberOfRootFragments: Int
|
||||||
|
get() = source.baseFragments.size
|
||||||
|
|
||||||
|
override fun getRootFragment(index: Int) =
|
||||||
|
source.baseFragments[index].java.newInstance()
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
fun onCreate(savedInstanceState: Bundle?) = controller.run {
|
||||||
|
rootFragmentListener = source
|
||||||
|
transactionListener = listener
|
||||||
|
initialize(0, savedInstanceState)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onSaveInstanceState(outState: Bundle) =
|
||||||
|
controller.onSaveInstanceState(outState)
|
||||||
|
|
||||||
|
fun onBackPressed(): Boolean {
|
||||||
|
val fragment = controller.currentFrag as? CompatFragment<*, *>
|
||||||
|
|
||||||
|
if (fragment?.onBackPressed() == true) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return runCatching { controller.popFragment() }.fold({ true }, { false })
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---
|
||||||
|
|
||||||
|
fun navigateTo(event: MagiskNavigationEvent) {
|
||||||
|
val directions = event.navDirections
|
||||||
|
|
||||||
|
controller.defaultTransactionOptions = FragNavTransactionOptions.newBuilder()
|
||||||
|
.customAnimations(event.animOptions)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
controller.currentStack
|
||||||
|
?.indexOfFirst { it.javaClass == event.navOptions.popUpTo }
|
||||||
|
?.takeIf { it != -1 } // invalidate if class is not found
|
||||||
|
?.let { if (event.navOptions.inclusive) it + 1 else it }
|
||||||
|
?.let { controller.popFragments(it) }
|
||||||
|
|
||||||
|
when (directions.isActivity) {
|
||||||
|
true -> navigateToActivity(event)
|
||||||
|
else -> navigateToFragment(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun navigateToActivity(event: MagiskNavigationEvent) {
|
||||||
|
val destination = event.navDirections.destination?.java ?: let {
|
||||||
|
Timber.e("Cannot navigate to null destination")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val options = event.navOptions
|
||||||
|
|
||||||
|
Intent(source, destination)
|
||||||
|
.putExtras(event.navDirections.args)
|
||||||
|
.apply {
|
||||||
|
if (options.singleTop) addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
||||||
|
if (options.clearTask) addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
||||||
|
}
|
||||||
|
.let { source.startActivity(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun navigateToFragment(event: MagiskNavigationEvent) {
|
||||||
|
val destination = event.navDirections.destination?.java ?: let {
|
||||||
|
Timber.e("Cannot navigate to null destination")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
source.baseFragments
|
||||||
|
.indexOfFirst { it.java.name == destination.name }
|
||||||
|
.takeIf { it > 0 }
|
||||||
|
?.let { controller.switchTab(it) } ?: destination.newInstance()
|
||||||
|
.also { it.arguments = event.navDirections.args }
|
||||||
|
.let { controller.pushFragment(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun FragNavTransactionOptions.Builder.customAnimations(options: MagiskAnimBuilder) =
|
||||||
|
customAnimations(options.enter, options.exit, options.popEnter, options.popExit).apply {
|
||||||
|
if (!options.anySet) {
|
||||||
|
transition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -7,6 +7,7 @@ internal interface CompatView<ViewModel : CompatViewModel> {
|
|||||||
|
|
||||||
val viewRoot: View
|
val viewRoot: View
|
||||||
val viewModel: ViewModel
|
val viewModel: ViewModel
|
||||||
|
val navigation: CompatNavigationDelegate<*>?
|
||||||
|
|
||||||
fun peekSystemWindowInsets(insets: Insets) = Unit
|
fun peekSystemWindowInsets(insets: Insets) = Unit
|
||||||
fun consumeSystemWindowInsets(insets: Insets) = Insets.NONE
|
fun consumeSystemWindowInsets(insets: Insets) = Insets.NONE
|
||||||
|
Loading…
x
Reference in New Issue
Block a user