Commit 0eb28c32 authored by Viktor De Pasquale's avatar Viktor De Pasquale

Added navigation delegation to bypass default one

By making a delegate like such we protect ourselves against intrusions in views' logic
parent 2daa131f
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() ...@@ -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() ...@@ -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()
\ No newline at end of file
...@@ -3,21 +3,29 @@ package com.topjohnwu.magisk.model.navigation ...@@ -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>( ...@@ -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>( ...@@ -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 ...@@ -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 ...@@ -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)
} }
\ No newline at end of file
...@@ -15,6 +15,11 @@ class CompatDelegate internal constructor( ...@@ -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( ...@@ -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 ...@@ -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() {
......
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)
}
}
}
\ No newline at end of file
...@@ -7,6 +7,7 @@ internal interface CompatView<ViewModel : CompatViewModel> { ...@@ -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
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment