Updated the app to use navigation components instead of custom solution

Welcome to mid 2018.
This commit is contained in:
Viktor De Pasquale 2020-03-17 17:28:09 +01:00 committed by John Wu
parent 922e36cfb0
commit 1b8813228b
19 changed files with 262 additions and 379 deletions

View File

@ -1,7 +1,10 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
plugins {
id("com.android.application")
id("kotlin-android")
id("kotlin-android-extensions")
id("kotlin-kapt")
id("androidx.navigation.safeargs.kotlin")
}
kapt {
correctErrorTypes = true
@ -22,7 +25,7 @@ android {
javaCompileOptions {
annotationProcessorOptions {
arguments = ["room.incremental":"true"]
arguments = ["room.incremental": "true"]
}
}
}
@ -65,7 +68,6 @@ dependencies {
implementation 'com.github.topjohnwu:jtar:1.0.0'
implementation 'com.jakewharton.timber:timber:4.7.1'
implementation 'com.ncapdevi:frag-nav:3.2.0'
implementation 'com.github.pwittchen:reactivenetwork-rx2:3.0.6'
implementation 'io.reactivex.rxjava2:rxjava:2.2.18'
@ -116,7 +118,6 @@ dependencies {
implementation "androidx.room:room-rxjava2:${vRoom}"
kapt "androidx.room:room-compiler:${vRoom}"
def vNav = '2.2.1'
implementation "androidx.navigation:navigation-fragment-ktx:${vNav}"
implementation "androidx.navigation:navigation-ui-ktx:${vNav}"

View File

@ -6,6 +6,7 @@ import android.content.pm.ActivityInfo
import android.net.Uri
import android.os.Bundle
import androidx.core.net.toUri
import androidx.navigation.NavController
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.intent
@ -16,7 +17,6 @@ import com.topjohnwu.magisk.model.events.PermissionEvent
import com.topjohnwu.magisk.model.events.SnackbarEvent
import com.topjohnwu.magisk.model.events.ViewEvent
import com.topjohnwu.magisk.ui.base.BaseUIActivity
import com.topjohnwu.magisk.ui.base.CompatNavigationDelegate
import org.koin.androidx.viewmodel.ext.android.viewModel
import java.io.File
@ -25,8 +25,7 @@ open class FlashActivity : BaseUIActivity<FlashViewModel, ActivityFlashBinding>(
override val layoutRes: Int = R.layout.activity_flash
override val viewModel: FlashViewModel by viewModel()
override val navigation: CompatNavigationDelegate<BaseUIActivity<FlashViewModel, ActivityFlashBinding>>? =
null
override val navigation: NavController? = null
override fun onCreate(savedInstanceState: Bundle?) {
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR

View File

@ -7,6 +7,7 @@ import android.os.Build
import android.os.Bundle
import android.view.Window
import android.view.WindowManager
import androidx.navigation.NavController
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.su.SuCallbackHandler
import com.topjohnwu.magisk.core.su.SuCallbackHandler.REQUEST
@ -15,7 +16,6 @@ import com.topjohnwu.magisk.model.events.DieEvent
import com.topjohnwu.magisk.model.events.ViewActionEvent
import com.topjohnwu.magisk.model.events.ViewEvent
import com.topjohnwu.magisk.ui.base.BaseUIActivity
import com.topjohnwu.magisk.ui.base.CompatNavigationDelegate
import org.koin.androidx.viewmodel.ext.android.viewModel
open class SuRequestActivity : BaseUIActivity<SuRequestViewModel, ActivityRequestBinding>() {
@ -23,8 +23,7 @@ open class SuRequestActivity : BaseUIActivity<SuRequestViewModel, ActivityReques
override val layoutRes: Int = R.layout.activity_request
override val viewModel: SuRequestViewModel by viewModel()
override val navigation: CompatNavigationDelegate<BaseUIActivity<SuRequestViewModel, ActivityRequestBinding>>? =
null
override val navigation: NavController? = null
override fun onBackPressed() {
viewModel.denyPressed()

View File

@ -1,94 +0,0 @@
package com.topjohnwu.magisk.model.navigation
import android.os.Bundle
import androidx.annotation.AnimRes
import androidx.annotation.AnimatorRes
import androidx.fragment.app.Fragment
import com.topjohnwu.magisk.core.base.BaseActivity
import com.topjohnwu.magisk.model.events.ActivityExecutor
import com.topjohnwu.magisk.model.events.ViewEvent
import com.topjohnwu.magisk.ui.base.BaseUIActivity
import kotlin.reflect.KClass
@DslMarker
annotation class NavigationDslMarker
class MagiskNavigationEvent(
val navDirections: MagiskNavDirectionsBuilder,
val navOptions: MagiskNavOptions,
val animOptions: MagiskAnimBuilder
) : ViewEvent(), ActivityExecutor {
companion object {
operator fun invoke(builder: Builder.() -> Unit) = Builder().apply(builder).build()
}
override fun invoke(activity: BaseActivity) {
if (activity !is BaseUIActivity<*, *>) return
activity.navigation?.navigateTo(this)
}
@NavigationDslMarker
class Builder {
private var animOptions: MagiskAnimBuilder = MagiskAnimBuilder()
private var navOptions: MagiskNavOptions = MagiskNavOptions()
private val directionsBuilder = MagiskNavDirectionsBuilder()
fun args(builder: Bundle.() -> Unit) = directionsBuilder.args(builder)
fun navAnim(builder: MagiskAnimBuilder.() -> Unit) {
animOptions = MagiskAnimBuilder().apply(builder)
}
fun navOptions(builder: MagiskNavOptions.() -> Unit) {
navOptions = MagiskNavOptions().apply(builder)
}
fun navDirections(builder: MagiskNavDirectionsBuilder.() -> Unit) {
directionsBuilder.apply(builder)
}
internal fun build() = MagiskNavigationEvent(directionsBuilder, navOptions, animOptions)
}
}
@NavigationDslMarker
class MagiskNavDirectionsBuilder {
var destination: KClass<out Fragment>? = null
var isActivity: Boolean = false
val args: Bundle = Bundle()
fun args(builder: Bundle.() -> Unit) = args.apply(builder)
}
@NavigationDslMarker
class MagiskNavOptions {
var popUpTo: KClass<*>? = null
var inclusive: Boolean = false
var clearTask: Boolean = false
var singleTop: Boolean = false
}
@NavigationDslMarker
class MagiskAnimBuilder {
@AnimRes
@AnimatorRes
var enter = 0
@AnimRes
@AnimatorRes
var exit = 0
@AnimRes
@AnimatorRes
var popEnter = 0
@AnimRes
@AnimatorRes
var popExit = 0
val anySet: Boolean get() = enter != 0 || exit != 0 || popEnter != 0 || popExit != 0
}

View File

@ -6,80 +6,9 @@ import android.os.Build
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.intent
import com.topjohnwu.magisk.ui.MainActivity
import com.topjohnwu.magisk.ui.hide.HideFragment
import com.topjohnwu.magisk.ui.home.HomeFragment
import com.topjohnwu.magisk.ui.install.InstallFragment
import com.topjohnwu.magisk.ui.log.LogFragment
import com.topjohnwu.magisk.ui.module.ModuleFragment
import com.topjohnwu.magisk.ui.safetynet.SafetynetFragment
import com.topjohnwu.magisk.ui.settings.SettingsFragment
import com.topjohnwu.magisk.ui.superuser.SuperuserFragment
import com.topjohnwu.magisk.ui.theme.ThemeFragment
object Navigation {
fun home() = MagiskNavigationEvent {
navDirections {
destination = HomeFragment::class
}
navOptions {
popUpTo = HomeFragment::class
}
}
fun superuser() = MagiskNavigationEvent {
navDirections {
destination = SuperuserFragment::class
}
}
fun modules() = MagiskNavigationEvent {
navDirections {
destination = ModuleFragment::class
}
}
fun hide() = MagiskNavigationEvent {
navDirections {
destination = HideFragment::class
}
}
fun safetynet() = MagiskNavigationEvent {
navDirections { destination = SafetynetFragment::class }
}
fun log() = MagiskNavigationEvent {
navDirections {
destination = LogFragment::class
}
}
fun settings() = MagiskNavigationEvent {
navDirections {
destination = SettingsFragment::class
}
}
fun install() = MagiskNavigationEvent {
navDirections { destination = InstallFragment::class }
}
fun theme() = MagiskNavigationEvent {
navDirections { destination = ThemeFragment::class }
}
fun fromSection(section: String) = when (section) {
"superuser" -> superuser()
"modules" -> modules()
"magiskhide" -> hide()
"log" -> log()
"settings" -> settings()
else -> home()
}
// redesign starts here
fun start(launchIntent: Intent, context: Context) {
context.intent<MainActivity>()
.putExtra(
@ -92,10 +21,6 @@ object Navigation {
.also { context.startActivity(it) }
}
object Main {
const val OPEN_NAV = 1
}
private val ACTION_APPLICATION_PREFERENCES
get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Intent.ACTION_APPLICATION_PREFERENCES

View File

@ -0,0 +1,18 @@
package com.topjohnwu.magisk.model.navigation
import androidx.navigation.NavDirections
import com.topjohnwu.magisk.core.base.BaseActivity
import com.topjohnwu.magisk.model.events.ActivityExecutor
import com.topjohnwu.magisk.model.events.ViewEvent
import com.topjohnwu.magisk.ui.base.BaseUIActivity
class NavigationWrapper(
private val directions: NavDirections
) : ViewEvent(), ActivityExecutor {
override fun invoke(activity: BaseActivity) {
if (activity !is BaseUIActivity<*, *>) return
activity.apply {
directions.navigate()
}
}
}

View File

@ -7,47 +7,31 @@ import android.view.ViewTreeObserver
import android.view.WindowManager
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.graphics.Insets
import androidx.core.view.forEach
import androidx.core.view.setPadding
import androidx.core.view.updateLayoutParams
import androidx.fragment.app.Fragment
import com.google.android.material.card.MaterialCardView
import com.ncapdevi.fragnav.FragNavController
import com.topjohnwu.magisk.MainDirections
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.databinding.ActivityMainMd2Binding
import com.topjohnwu.magisk.extensions.startAnimations
import com.topjohnwu.magisk.model.navigation.Navigation
import com.topjohnwu.magisk.ui.base.BaseUIActivity
import com.topjohnwu.magisk.ui.base.CompatNavigationDelegate
import com.topjohnwu.magisk.ui.home.HomeFragment
import com.topjohnwu.magisk.ui.log.LogFragment
import com.topjohnwu.magisk.ui.module.ModuleFragment
import com.topjohnwu.magisk.ui.superuser.SuperuserFragment
import com.topjohnwu.magisk.ui.home.HomeFragmentDirections
import com.topjohnwu.magisk.utils.HideBottomViewOnScrollBehavior
import com.topjohnwu.magisk.utils.HideTopViewOnScrollBehavior
import com.topjohnwu.magisk.utils.HideableBehavior
import com.topjohnwu.magisk.view.MagiskDialog
import com.topjohnwu.superuser.Shell
import org.koin.androidx.viewmodel.ext.android.viewModel
import kotlin.reflect.KClass
open class MainActivity : BaseUIActivity<MainViewModel, ActivityMainMd2Binding>(),
FragNavController.TransactionListener {
open class MainActivity : BaseUIActivity<MainViewModel, ActivityMainMd2Binding>() {
override val layoutRes = R.layout.activity_main_md2
override val viewModel by viewModel<MainViewModel>()
override val navHost: Int = R.id.main_nav_host
override val navigation by lazy { CompatNavigationDelegate(this, this) }
override val baseFragments: List<KClass<out Fragment>> = listOf(
HomeFragment::class,
ModuleFragment::class,
SuperuserFragment::class,
LogFragment::class
)
//This temporarily fixes unwanted feature of BottomNavigationView - where the view applies
//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
@ -56,6 +40,9 @@ open class MainActivity : BaseUIActivity<MainViewModel, ActivityMainMd2Binding>(
binding.mainNavigation.setPadding(0)
}
protected var isRoot = true
private set
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -70,6 +57,25 @@ open class MainActivity : BaseUIActivity<MainViewModel, ActivityMainMd2Binding>(
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
navigation?.addOnDestinationChangedListener { controller, destination, arguments ->
isRoot = when (destination.id) {
R.id.homeFragment,
R.id.modulesFragment,
R.id.superuserFragment,
R.id.logFragment -> true
else -> false
}
setDisplayHomeAsUpEnabled(!isRoot)
requestNavigationHidden(!isRoot)
binding.mainNavigation.menu.forEach {
if (it.itemId == destination.id) {
it.isChecked = true
}
}
}
setSupportActionBar(binding.mainToolbar)
binding.mainToolbarWrapper.updateLayoutParams<CoordinatorLayout.LayoutParams> {
@ -80,28 +86,26 @@ open class MainActivity : BaseUIActivity<MainViewModel, ActivityMainMd2Binding>(
}
binding.mainNavigation.setOnNavigationItemSelectedListener {
when (it.itemId) {
R.id.homeFragment -> Navigation.home()
R.id.modulesFragment -> Navigation.modules()
R.id.superuserFragment -> Navigation.superuser()
R.id.logFragment -> Navigation.log()
R.id.homeFragment -> MainDirections.actionHomeFragment()
R.id.modulesFragment -> MainDirections.actionModuleFragment()
R.id.superuserFragment -> MainDirections.actionSuperuserFragment()
R.id.logFragment -> MainDirections.actionLogFragment()
else -> throw NotImplementedError("Id ${it.itemId} is not defined as selectable")
}.dispatchOnSelf()
}.navigate()
true
}
binding.mainNavigation.setOnNavigationItemReselectedListener {
navigation.onReselected()
(currentFragment as? ReselectionTarget)?.onReselected()
}
binding.mainNavigation.viewTreeObserver.addOnGlobalLayoutListener(navObserver)
if (intent.getBooleanExtra(Const.Key.OPEN_SETTINGS, false)) {
Navigation.settings().dispatchOnSelf()
HomeFragmentDirections.actionHomeFragmentToSettingsFragment().navigate()
}
if (savedInstanceState != null) {
onTabTransaction(null, -1)
if (!navigation.isRoot) {
if (!isRoot) {
requestNavigationHidden()
}
}
@ -129,17 +133,6 @@ open class MainActivity : BaseUIActivity<MainViewModel, ActivityMainMd2Binding>(
return true
}
override fun onTabTransaction(fragment: Fragment?, index: Int) =
onFragmentTransaction(fragment, FragNavController.TransactionType.PUSH)
override fun onFragmentTransaction(
fragment: Fragment?,
transactionType: FragNavController.TransactionType
) {
setDisplayHomeAsUpEnabled(!navigation.isRoot)
requestNavigationHidden(!navigation.isRoot)
}
override fun peekSystemWindowInsets(insets: Insets) {
viewModel.insets.value = insets
}

View File

@ -9,7 +9,8 @@ import androidx.core.graphics.Insets
import androidx.databinding.DataBindingUtil
import androidx.databinding.OnRebindCallback
import androidx.databinding.ViewDataBinding
import androidx.fragment.app.Fragment
import androidx.navigation.NavDirections
import androidx.navigation.findNavController
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.base.BaseActivity
@ -19,7 +20,6 @@ import com.topjohnwu.magisk.model.events.EventHandler
import com.topjohnwu.magisk.model.events.SnackbarEvent
import com.topjohnwu.magisk.model.events.ViewEvent
import com.topjohnwu.magisk.ui.theme.Theme
import kotlin.reflect.KClass
abstract class BaseUIActivity<ViewModel : BaseViewModel, Binding : ViewDataBinding> :
BaseActivity(), CompatView<ViewModel>, EventHandler {
@ -28,20 +28,30 @@ abstract class BaseUIActivity<ViewModel : BaseViewModel, Binding : ViewDataBindi
protected abstract val layoutRes: Int
protected open val themeRes: Int = Theme.selected.themeRes
private val navHostFragment get() = supportFragmentManager.findFragmentById(navHost)
private val topFragment get() = navHostFragment?.childFragmentManager?.fragments?.getOrNull(0)
protected val currentFragment get() = topFragment as? BaseUIFragment<*, *>
override val viewRoot: View get() = binding.root
override val navigation by lazy { CompatNavigationDelegate(this) as CompatNavigationDelegate? }
override val navigation by lazy {
kotlin.runCatching { findNavController(navHost) }.getOrNull()
}
private val delegate by lazy { CompatDelegate(this) }
open val navHost: Int = 0
open val snackbarView get() = binding.root
open val baseFragments = listOf<KClass<out Fragment>>()
init {
val theme = Config.darkThemeExtended
AppCompatDelegate.setDefaultNightMode(theme)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
currentFragment?.onActivityResult(requestCode, resultCode, data)
}
override fun onCreate(savedInstanceState: Bundle?) {
setTheme(themeRes)
super.onCreate(savedInstanceState)
@ -61,12 +71,6 @@ abstract class BaseUIActivity<ViewModel : BaseViewModel, Binding : ViewDataBindi
})
delegate.onCreate()
navigation?.onCreate(savedInstanceState)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
navigation?.onActivityResult(requestCode, resultCode, data)
}
override fun onResume() {
@ -74,11 +78,6 @@ abstract class BaseUIActivity<ViewModel : BaseViewModel, Binding : ViewDataBindi
delegate.onResume()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
navigation?.onSaveInstanceState(outState)
}
override fun onEventDispatched(event: ViewEvent) {
delegate.onEventExecute(event, this)
when (event) {
@ -87,7 +86,7 @@ abstract class BaseUIActivity<ViewModel : BaseViewModel, Binding : ViewDataBindi
}
override fun onBackPressed() {
if (navigation == null || navigation?.onBackPressed()?.not() == true) {
if (navigation == null || currentFragment?.onBackPressed()?.not() == true) {
super.onBackPressed()
}
}
@ -97,4 +96,9 @@ abstract class BaseUIActivity<ViewModel : BaseViewModel, Binding : ViewDataBindi
}
protected fun ViewEvent.dispatchOnSelf() = onEventDispatched(this)
fun NavDirections.navigate() {
navigation?.navigate(this)
}
}

View File

@ -9,6 +9,7 @@ import androidx.databinding.DataBindingUtil
import androidx.databinding.OnRebindCallback
import androidx.databinding.ViewDataBinding
import androidx.fragment.app.Fragment
import androidx.navigation.NavDirections
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.extensions.startAnimations
import com.topjohnwu.magisk.model.events.EventHandler
@ -22,7 +23,7 @@ abstract class BaseUIFragment<ViewModel : BaseViewModel, Binding : ViewDataBindi
protected abstract val layoutRes: Int
override val viewRoot: View get() = binding.root
override val navigation by lazy { activity.navigation }
override val navigation get() = activity.navigation
private val delegate by lazy { CompatDelegate(this) }
override fun consumeSystemWindowInsets(insets: Insets) = insets
@ -78,4 +79,8 @@ abstract class BaseUIFragment<ViewModel : BaseViewModel, Binding : ViewDataBindi
protected fun ViewEvent.dispatchOnSelf() = delegate.onEventExecute(this, this@BaseUIFragment)
fun NavDirections.navigate() {
navigation?.navigate(this)
}
}

View File

@ -7,11 +7,13 @@ import androidx.databinding.PropertyChangeRegistry
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.navigation.NavDirections
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.base.BaseActivity
import com.topjohnwu.magisk.extensions.doOnSubscribeUi
import com.topjohnwu.magisk.model.events.*
import com.topjohnwu.magisk.model.navigation.NavigationWrapper
import com.topjohnwu.magisk.model.observer.Observer
import com.topjohnwu.magisk.utils.KObservableField
import io.reactivex.*
@ -99,6 +101,10 @@ abstract class BaseViewModel(
_viewEvents.postValue(SimpleViewEvent(this))
}
fun NavDirections.publish() {
_viewEvents.postValue(NavigationWrapper(this))
}
fun Disposable.add() {
disposables.add(this)
}

View File

@ -1,127 +0,0 @@
package com.topjohnwu.magisk.ui.base
import android.content.Intent
import android.os.Bundle
import com.ncapdevi.fragnav.FragNavController
import com.ncapdevi.fragnav.FragNavTransactionOptions
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.model.navigation.MagiskAnimBuilder
import com.topjohnwu.magisk.model.navigation.MagiskNavigationEvent
import com.topjohnwu.magisk.ui.ReselectionTarget
import timber.log.Timber
class CompatNavigationDelegate<out Source>(
private val source: Source,
private val listener: FragNavController.TransactionListener? = null
) : FragNavController.RootFragmentListener where Source : BaseUIActivity<*, *> {
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 onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
controller.currentFrag?.onActivityResult(requestCode, resultCode, data)
}
fun onCreate(savedInstanceState: Bundle?) = controller.run {
rootFragmentListener = this@CompatNavigationDelegate
transactionListener = listener
initialize(0, savedInstanceState)
}
fun onSaveInstanceState(outState: Bundle) =
controller.onSaveInstanceState(outState)
fun onReselected() {
(controller.currentFrag as? ReselectionTarget)?.onReselected()
}
fun onBackPressed(): Boolean {
val fragment = controller.currentFrag as? BaseUIFragment<*, *>
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 ?: let {
Timber.e("Cannot navigate to null destination")
return
}
source.baseFragments
.indexOfFirst { it == destination }
.takeIf { it >= 0 }
?.let { controller.switchTab(it) } ?: destination.java.newInstance()
.also { it.arguments = event.navDirections.args }
.let { controller.pushFragment(it) }
}
private fun FragNavTransactionOptions.Builder.customAnimations(options: MagiskAnimBuilder) =
apply {
if (!options.anySet) customAnimations(
R.anim.fragment_enter,
R.anim.fragment_exit,
R.anim.fragment_enter_pop,
R.anim.fragment_exit_pop
) else customAnimations(
options.enter,
options.exit,
options.popEnter,
options.popExit
)
}
}

View File

@ -2,12 +2,13 @@ package com.topjohnwu.magisk.ui.base
import android.view.View
import androidx.core.graphics.Insets
import androidx.navigation.NavController
internal interface CompatView<ViewModel : BaseViewModel> {
val viewRoot: View
val viewModel: ViewModel
val navigation: CompatNavigationDelegate<*>?
val navigation: NavController?
fun peekSystemWindowInsets(insets: Insets) = Unit
fun consumeSystemWindowInsets(insets: Insets): Insets? = null

View File

@ -5,7 +5,6 @@ import android.view.*
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.FragmentHomeMd2Binding
import com.topjohnwu.magisk.model.events.RebootEvent
import com.topjohnwu.magisk.model.navigation.Navigation
import com.topjohnwu.magisk.ui.base.BaseUIFragment
import com.topjohnwu.superuser.Shell
import org.koin.androidx.viewmodel.ext.android.viewModel
@ -46,7 +45,8 @@ class HomeFragment : BaseUIFragment<HomeViewModel, FragmentHomeMd2Binding>() {
}
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
R.id.action_settings -> Navigation.settings().dispatchOnSelf()
R.id.action_settings -> HomeFragmentDirections.actionHomeFragmentToSettingsFragment()
.navigate()
R.id.action_reboot -> RebootEvent.inflateMenu(activity).show()
else -> null
}?.let { true } ?: super.onOptionsItemSelected(item)

View File

@ -22,7 +22,6 @@ import com.topjohnwu.magisk.model.events.ViewEvent
import com.topjohnwu.magisk.model.events.dialog.EnvFixDialog
import com.topjohnwu.magisk.model.events.dialog.ManagerInstallDialog
import com.topjohnwu.magisk.model.events.dialog.UninstallDialog
import com.topjohnwu.magisk.model.navigation.Navigation
import com.topjohnwu.magisk.ui.base.BaseViewModel
import com.topjohnwu.magisk.ui.base.itemBindingOf
import com.topjohnwu.magisk.utils.KObservableField
@ -114,7 +113,7 @@ class HomeViewModel(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
).map { check(it);it }
.subscribeK { Navigation.install().publish() }
.subscribeK { HomeFragmentDirections.actionHomeFragmentToInstallFragment().publish() }
.add()
fun toggle(kof: KObservableField<Boolean>) = kof.toggle()

View File

@ -19,7 +19,6 @@ import com.topjohnwu.magisk.model.entity.recycler.SettingsItem
import com.topjohnwu.magisk.model.events.PermissionEvent
import com.topjohnwu.magisk.model.events.RecreateEvent
import com.topjohnwu.magisk.model.events.dialog.BiometricDialog
import com.topjohnwu.magisk.model.navigation.Navigation
import com.topjohnwu.magisk.ui.base.BaseViewModel
import com.topjohnwu.magisk.ui.base.adapterOf
import com.topjohnwu.magisk.ui.base.diffListOf
@ -95,7 +94,7 @@ class SettingsViewModel(
override fun onItemChanged(view: View, item: SettingsItem) = when (item) {
// use only instances you want, don't declare everything
is Theme -> Navigation.theme().publish()
is Theme -> SettingsFragmentDirections.actionSettingsFragmentToThemeFragment().publish()
is Language -> RecreateEvent().publish()
is UpdateChannel -> openUrlIfNecessary(view)

View File

@ -20,7 +20,6 @@ import com.topjohnwu.magisk.model.events.PolicyUpdateEvent
import com.topjohnwu.magisk.model.events.SnackbarEvent
import com.topjohnwu.magisk.model.events.dialog.BiometricDialog
import com.topjohnwu.magisk.model.events.dialog.SuperuserRevokeDialog
import com.topjohnwu.magisk.model.navigation.Navigation
import com.topjohnwu.magisk.ui.base.BaseViewModel
import com.topjohnwu.magisk.ui.base.adapterOf
import com.topjohnwu.magisk.ui.base.diffListOf
@ -85,8 +84,11 @@ class SuperuserViewModel(
else -> Unit
}
private fun safetynetPressed() = Navigation.safetynet().publish()
private fun hidePressed() = Navigation.hide().publish()
private fun safetynetPressed() =
SuperuserFragmentDirections.actionSuperuserFragmentToSafetynetFragment().publish()
private fun hidePressed() =
SuperuserFragmentDirections.actionSuperuserFragmentToHideFragment().publish()
fun deletePressed(item: PolicyItem) {
fun updateState() = deletePolicy(item.item)

View File

@ -19,10 +19,13 @@
android:paddingRight="@{viewModel.insets.right}"
tools:ignore="RtlHardcoded">
<androidx.fragment.app.FragmentContainerView
<fragment
android:id="@+id/main_nav_host"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/main" />
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/main_toolbar_wrapper"

View File

@ -0,0 +1,148 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
app:startDestination="@id/homeFragment">
<fragment
android:id="@+id/hideFragment"
android:name="com.topjohnwu.magisk.ui.hide.HideFragment"
android:label="HideFragment"
tools:layout="@layout/fragment_hide_md2" />
<fragment
android:id="@+id/homeFragment"
android:name="com.topjohnwu.magisk.ui.home.HomeFragment"
android:label="HomeFragment"
tools:layout="@layout/fragment_home_md2">
<action
android:id="@+id/action_homeFragment_to_settingsFragment"
app:destination="@id/settingsFragment"
app:enterAnim="@anim/fragment_enter"
app:exitAnim="@anim/fragment_exit"
app:popEnterAnim="@anim/fragment_enter_pop"
app:popExitAnim="@anim/fragment_exit_pop" />
<action
android:id="@+id/action_homeFragment_to_installFragment"
app:destination="@id/installFragment"
app:enterAnim="@anim/fragment_enter"
app:exitAnim="@anim/fragment_exit"
app:popEnterAnim="@anim/fragment_enter_pop"
app:popExitAnim="@anim/fragment_exit_pop" />
</fragment>
<fragment
android:id="@+id/installFragment"
android:name="com.topjohnwu.magisk.ui.install.InstallFragment"
android:label="InstallFragment"
tools:layout="@layout/fragment_install_md2" />
<fragment
android:id="@+id/logFragment"
android:name="com.topjohnwu.magisk.ui.log.LogFragment"
android:label="LogFragment"
tools:layout="@layout/fragment_log_md2" />
<fragment
android:id="@+id/modulesFragment"
android:name="com.topjohnwu.magisk.ui.module.ModuleFragment"
android:label="ModuleFragment"
tools:layout="@layout/fragment_module_md2" />
<fragment
android:id="@+id/safetynetFragment"
android:name="com.topjohnwu.magisk.ui.safetynet.SafetynetFragment"
android:label="SafetynetFragment"
tools:layout="@layout/fragment_safetynet_md2" />
<fragment
android:id="@+id/settingsFragment"
android:name="com.topjohnwu.magisk.ui.settings.SettingsFragment"
android:label="SettingsFragment"
tools:layout="@layout/fragment_settings_md2">
<action
android:id="@+id/action_settingsFragment_to_themeFragment"
app:destination="@id/themeFragment"
app:enterAnim="@anim/fragment_enter"
app:exitAnim="@anim/fragment_exit"
app:popEnterAnim="@anim/fragment_enter_pop"
app:popExitAnim="@anim/fragment_exit_pop" />
</fragment>
<fragment
android:id="@+id/superuserFragment"
android:name="com.topjohnwu.magisk.ui.superuser.SuperuserFragment"
android:label="SuperuserFragment"
tools:layout="@layout/fragment_superuser_md2">
<action
android:id="@+id/action_superuserFragment_to_safetynetFragment"
app:destination="@id/safetynetFragment"
app:enterAnim="@anim/fragment_enter"
app:exitAnim="@anim/fragment_exit"
app:popEnterAnim="@anim/fragment_enter_pop"
app:popExitAnim="@anim/fragment_exit_pop" />
<action
android:id="@+id/action_superuserFragment_to_hideFragment"
app:destination="@id/hideFragment"
app:enterAnim="@anim/fragment_enter"
app:exitAnim="@anim/fragment_exit"
app:popEnterAnim="@anim/fragment_enter_pop"
app:popExitAnim="@anim/fragment_exit_pop" />
</fragment>
<fragment
android:id="@+id/themeFragment"
android:name="com.topjohnwu.magisk.ui.theme.ThemeFragment"
android:label="ThemeFragment"
tools:layout="@layout/fragment_theme_md2" />
<action
android:id="@+id/action_homeFragment"
app:destination="@id/homeFragment"
app:enterAnim="@anim/fragment_enter"
app:exitAnim="@anim/fragment_exit"
app:popEnterAnim="@anim/fragment_enter_pop"
app:popExitAnim="@anim/fragment_exit_pop"
app:popUpTo="@id/homeFragment"
app:popUpToInclusive="true" />
<action
android:id="@+id/action_superuserFragment"
app:destination="@id/superuserFragment"
app:enterAnim="@anim/fragment_enter"
app:exitAnim="@anim/fragment_exit"
app:popEnterAnim="@anim/fragment_enter_pop"
app:popExitAnim="@anim/fragment_exit_pop"
app:popUpTo="@id/superuserFragment"
app:popUpToInclusive="true" />
<action
android:id="@+id/action_logFragment"
app:destination="@id/logFragment"
app:enterAnim="@anim/fragment_enter"
app:exitAnim="@anim/fragment_exit"
app:popEnterAnim="@anim/fragment_enter_pop"
app:popExitAnim="@anim/fragment_exit_pop"
app:popUpTo="@id/logFragment"
app:popUpToInclusive="true" />
<action
android:id="@+id/action_moduleFragment"
app:destination="@id/modulesFragment"
app:enterAnim="@anim/fragment_enter"
app:exitAnim="@anim/fragment_exit"
app:popEnterAnim="@anim/fragment_enter_pop"
app:popExitAnim="@anim/fragment_exit_pop"
app:popUpTo="@id/modulesFragment"
app:popUpToInclusive="true" />
</navigation>

View File

@ -8,6 +8,7 @@ if (configPath.exists())
buildscript {
ext.vKotlin = '1.3.70'
ext.vNav = '2.3.0-alpha03'
repositories {
google()
@ -18,6 +19,7 @@ buildscript {
dependencies {
classpath 'com.android.tools.build:gradle:3.6.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${vKotlin}"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$vNav"
// NOTE: Do not place your application dependencies here; they belong