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' plugins {
apply plugin: 'kotlin-android' id("com.android.application")
apply plugin: 'kotlin-android-extensions' id("kotlin-android")
apply plugin: 'kotlin-kapt' id("kotlin-android-extensions")
id("kotlin-kapt")
id("androidx.navigation.safeargs.kotlin")
}
kapt { kapt {
correctErrorTypes = true correctErrorTypes = true
@ -22,7 +25,7 @@ android {
javaCompileOptions { javaCompileOptions {
annotationProcessorOptions { annotationProcessorOptions {
arguments = ["room.incremental":"true"] arguments = ["room.incremental": "true"]
} }
} }
} }
@ -65,7 +68,6 @@ dependencies {
implementation 'com.github.topjohnwu:jtar:1.0.0' implementation 'com.github.topjohnwu:jtar:1.0.0'
implementation 'com.jakewharton.timber:timber:4.7.1' 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 'com.github.pwittchen:reactivenetwork-rx2:3.0.6'
implementation 'io.reactivex.rxjava2:rxjava:2.2.18' implementation 'io.reactivex.rxjava2:rxjava:2.2.18'
@ -116,7 +118,6 @@ dependencies {
implementation "androidx.room:room-rxjava2:${vRoom}" implementation "androidx.room:room-rxjava2:${vRoom}"
kapt "androidx.room:room-compiler:${vRoom}" kapt "androidx.room:room-compiler:${vRoom}"
def vNav = '2.2.1'
implementation "androidx.navigation:navigation-fragment-ktx:${vNav}" implementation "androidx.navigation:navigation-fragment-ktx:${vNav}"
implementation "androidx.navigation:navigation-ui-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.net.Uri
import android.os.Bundle import android.os.Bundle
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.navigation.NavController
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Const import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.intent 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.SnackbarEvent
import com.topjohnwu.magisk.model.events.ViewEvent import com.topjohnwu.magisk.model.events.ViewEvent
import com.topjohnwu.magisk.ui.base.BaseUIActivity import com.topjohnwu.magisk.ui.base.BaseUIActivity
import com.topjohnwu.magisk.ui.base.CompatNavigationDelegate
import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
import java.io.File import java.io.File
@ -25,8 +25,7 @@ open class FlashActivity : BaseUIActivity<FlashViewModel, ActivityFlashBinding>(
override val layoutRes: Int = R.layout.activity_flash override val layoutRes: Int = R.layout.activity_flash
override val viewModel: FlashViewModel by viewModel() override val viewModel: FlashViewModel by viewModel()
override val navigation: CompatNavigationDelegate<BaseUIActivity<FlashViewModel, ActivityFlashBinding>>? = override val navigation: NavController? = null
null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR

View File

@ -7,6 +7,7 @@ import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.Window import android.view.Window
import android.view.WindowManager import android.view.WindowManager
import androidx.navigation.NavController
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.su.SuCallbackHandler import com.topjohnwu.magisk.core.su.SuCallbackHandler
import com.topjohnwu.magisk.core.su.SuCallbackHandler.REQUEST 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.ViewActionEvent
import com.topjohnwu.magisk.model.events.ViewEvent import com.topjohnwu.magisk.model.events.ViewEvent
import com.topjohnwu.magisk.ui.base.BaseUIActivity import com.topjohnwu.magisk.ui.base.BaseUIActivity
import com.topjohnwu.magisk.ui.base.CompatNavigationDelegate
import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
open class SuRequestActivity : BaseUIActivity<SuRequestViewModel, ActivityRequestBinding>() { 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 layoutRes: Int = R.layout.activity_request
override val viewModel: SuRequestViewModel by viewModel() override val viewModel: SuRequestViewModel by viewModel()
override val navigation: CompatNavigationDelegate<BaseUIActivity<SuRequestViewModel, ActivityRequestBinding>>? = override val navigation: NavController? = null
null
override fun onBackPressed() { override fun onBackPressed() {
viewModel.denyPressed() 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.Const
import com.topjohnwu.magisk.core.intent import com.topjohnwu.magisk.core.intent
import com.topjohnwu.magisk.ui.MainActivity 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 { 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) { fun start(launchIntent: Intent, context: Context) {
context.intent<MainActivity>() context.intent<MainActivity>()
.putExtra( .putExtra(
@ -92,10 +21,6 @@ object Navigation {
.also { context.startActivity(it) } .also { context.startActivity(it) }
} }
object Main {
const val OPEN_NAV = 1
}
private val ACTION_APPLICATION_PREFERENCES private val ACTION_APPLICATION_PREFERENCES
get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Intent.ACTION_APPLICATION_PREFERENCES 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 android.view.WindowManager
import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
import androidx.core.view.forEach
import androidx.core.view.setPadding import androidx.core.view.setPadding
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.fragment.app.Fragment
import com.google.android.material.card.MaterialCardView 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.R
import com.topjohnwu.magisk.core.Const import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.Info import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.databinding.ActivityMainMd2Binding import com.topjohnwu.magisk.databinding.ActivityMainMd2Binding
import com.topjohnwu.magisk.extensions.startAnimations 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.BaseUIActivity
import com.topjohnwu.magisk.ui.base.CompatNavigationDelegate import com.topjohnwu.magisk.ui.home.HomeFragmentDirections
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.utils.HideBottomViewOnScrollBehavior import com.topjohnwu.magisk.utils.HideBottomViewOnScrollBehavior
import com.topjohnwu.magisk.utils.HideTopViewOnScrollBehavior import com.topjohnwu.magisk.utils.HideTopViewOnScrollBehavior
import com.topjohnwu.magisk.utils.HideableBehavior import com.topjohnwu.magisk.utils.HideableBehavior
import com.topjohnwu.magisk.view.MagiskDialog import com.topjohnwu.magisk.view.MagiskDialog
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
import kotlin.reflect.KClass
open class MainActivity : BaseUIActivity<MainViewModel, ActivityMainMd2Binding>(), open class MainActivity : BaseUIActivity<MainViewModel, ActivityMainMd2Binding>() {
FragNavController.TransactionListener {
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 navHost: Int = R.id.main_nav_host 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 //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
@ -56,6 +40,9 @@ open class MainActivity : BaseUIActivity<MainViewModel, ActivityMainMd2Binding>(
binding.mainNavigation.setPadding(0) binding.mainNavigation.setPadding(0)
} }
protected var isRoot = true
private set
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -70,6 +57,25 @@ open class MainActivity : BaseUIActivity<MainViewModel, ActivityMainMd2Binding>(
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) 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) setSupportActionBar(binding.mainToolbar)
binding.mainToolbarWrapper.updateLayoutParams<CoordinatorLayout.LayoutParams> { binding.mainToolbarWrapper.updateLayoutParams<CoordinatorLayout.LayoutParams> {
@ -80,28 +86,26 @@ open class MainActivity : BaseUIActivity<MainViewModel, ActivityMainMd2Binding>(
} }
binding.mainNavigation.setOnNavigationItemSelectedListener { binding.mainNavigation.setOnNavigationItemSelectedListener {
when (it.itemId) { when (it.itemId) {
R.id.homeFragment -> Navigation.home() R.id.homeFragment -> MainDirections.actionHomeFragment()
R.id.modulesFragment -> Navigation.modules() R.id.modulesFragment -> MainDirections.actionModuleFragment()
R.id.superuserFragment -> Navigation.superuser() R.id.superuserFragment -> MainDirections.actionSuperuserFragment()
R.id.logFragment -> Navigation.log() R.id.logFragment -> MainDirections.actionLogFragment()
else -> throw NotImplementedError("Id ${it.itemId} is not defined as selectable") else -> throw NotImplementedError("Id ${it.itemId} is not defined as selectable")
}.dispatchOnSelf() }.navigate()
true true
} }
binding.mainNavigation.setOnNavigationItemReselectedListener { binding.mainNavigation.setOnNavigationItemReselectedListener {
navigation.onReselected() (currentFragment as? ReselectionTarget)?.onReselected()
} }
binding.mainNavigation.viewTreeObserver.addOnGlobalLayoutListener(navObserver) binding.mainNavigation.viewTreeObserver.addOnGlobalLayoutListener(navObserver)
if (intent.getBooleanExtra(Const.Key.OPEN_SETTINGS, false)) { if (intent.getBooleanExtra(Const.Key.OPEN_SETTINGS, false)) {
Navigation.settings().dispatchOnSelf() HomeFragmentDirections.actionHomeFragmentToSettingsFragment().navigate()
} }
if (savedInstanceState != null) { if (savedInstanceState != null) {
onTabTransaction(null, -1) if (!isRoot) {
if (!navigation.isRoot) {
requestNavigationHidden() requestNavigationHidden()
} }
} }
@ -129,17 +133,6 @@ open class MainActivity : BaseUIActivity<MainViewModel, ActivityMainMd2Binding>(
return true 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) { override fun peekSystemWindowInsets(insets: Insets) {
viewModel.insets.value = insets viewModel.insets.value = insets
} }

View File

@ -9,7 +9,8 @@ import androidx.core.graphics.Insets
import androidx.databinding.DataBindingUtil import androidx.databinding.DataBindingUtil
import androidx.databinding.OnRebindCallback import androidx.databinding.OnRebindCallback
import androidx.databinding.ViewDataBinding 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.BR
import com.topjohnwu.magisk.core.Config import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.base.BaseActivity 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.SnackbarEvent
import com.topjohnwu.magisk.model.events.ViewEvent import com.topjohnwu.magisk.model.events.ViewEvent
import com.topjohnwu.magisk.ui.theme.Theme import com.topjohnwu.magisk.ui.theme.Theme
import kotlin.reflect.KClass
abstract class BaseUIActivity<ViewModel : BaseViewModel, Binding : ViewDataBinding> : abstract class BaseUIActivity<ViewModel : BaseViewModel, Binding : ViewDataBinding> :
BaseActivity(), CompatView<ViewModel>, EventHandler { BaseActivity(), CompatView<ViewModel>, EventHandler {
@ -28,20 +28,30 @@ abstract class BaseUIActivity<ViewModel : BaseViewModel, Binding : ViewDataBindi
protected abstract val layoutRes: Int protected abstract val layoutRes: Int
protected open val themeRes: Int = Theme.selected.themeRes 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 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) } private val delegate by lazy { CompatDelegate(this) }
open val navHost: Int = 0 open val navHost: Int = 0
open val snackbarView get() = binding.root open val snackbarView get() = binding.root
open val baseFragments = listOf<KClass<out Fragment>>()
init { init {
val theme = Config.darkThemeExtended val theme = Config.darkThemeExtended
AppCompatDelegate.setDefaultNightMode(theme) 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?) { override fun onCreate(savedInstanceState: Bundle?) {
setTheme(themeRes) setTheme(themeRes)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -61,12 +71,6 @@ abstract class BaseUIActivity<ViewModel : BaseViewModel, Binding : ViewDataBindi
}) })
delegate.onCreate() 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() { override fun onResume() {
@ -74,11 +78,6 @@ abstract class BaseUIActivity<ViewModel : BaseViewModel, Binding : ViewDataBindi
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) {
delegate.onEventExecute(event, this) delegate.onEventExecute(event, this)
when (event) { when (event) {
@ -87,7 +86,7 @@ abstract class BaseUIActivity<ViewModel : BaseViewModel, Binding : ViewDataBindi
} }
override fun onBackPressed() { override fun onBackPressed() {
if (navigation == null || navigation?.onBackPressed()?.not() == true) { if (navigation == null || currentFragment?.onBackPressed()?.not() == true) {
super.onBackPressed() super.onBackPressed()
} }
} }
@ -97,4 +96,9 @@ abstract class BaseUIActivity<ViewModel : BaseViewModel, Binding : ViewDataBindi
} }
protected fun ViewEvent.dispatchOnSelf() = onEventDispatched(this) 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.OnRebindCallback
import androidx.databinding.ViewDataBinding import androidx.databinding.ViewDataBinding
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.navigation.NavDirections
import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.extensions.startAnimations import com.topjohnwu.magisk.extensions.startAnimations
import com.topjohnwu.magisk.model.events.EventHandler import com.topjohnwu.magisk.model.events.EventHandler
@ -22,7 +23,7 @@ abstract class BaseUIFragment<ViewModel : BaseViewModel, Binding : ViewDataBindi
protected abstract val layoutRes: Int protected abstract val layoutRes: Int
override val viewRoot: View get() = binding.root 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) } private val delegate by lazy { CompatDelegate(this) }
override fun consumeSystemWindowInsets(insets: Insets) = insets 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) 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.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.navigation.NavDirections
import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.core.Info import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.base.BaseActivity import com.topjohnwu.magisk.core.base.BaseActivity
import com.topjohnwu.magisk.extensions.doOnSubscribeUi import com.topjohnwu.magisk.extensions.doOnSubscribeUi
import com.topjohnwu.magisk.model.events.* import com.topjohnwu.magisk.model.events.*
import com.topjohnwu.magisk.model.navigation.NavigationWrapper
import com.topjohnwu.magisk.model.observer.Observer import com.topjohnwu.magisk.model.observer.Observer
import com.topjohnwu.magisk.utils.KObservableField import com.topjohnwu.magisk.utils.KObservableField
import io.reactivex.* import io.reactivex.*
@ -99,6 +101,10 @@ abstract class BaseViewModel(
_viewEvents.postValue(SimpleViewEvent(this)) _viewEvents.postValue(SimpleViewEvent(this))
} }
fun NavDirections.publish() {
_viewEvents.postValue(NavigationWrapper(this))
}
fun Disposable.add() { fun Disposable.add() {
disposables.add(this) 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 android.view.View
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
import androidx.navigation.NavController
internal interface CompatView<ViewModel : BaseViewModel> { internal interface CompatView<ViewModel : BaseViewModel> {
val viewRoot: View val viewRoot: View
val viewModel: ViewModel val viewModel: ViewModel
val navigation: CompatNavigationDelegate<*>? val navigation: NavController?
fun peekSystemWindowInsets(insets: Insets) = Unit fun peekSystemWindowInsets(insets: Insets) = Unit
fun consumeSystemWindowInsets(insets: Insets): Insets? = null fun consumeSystemWindowInsets(insets: Insets): Insets? = null

View File

@ -5,7 +5,6 @@ import android.view.*
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.FragmentHomeMd2Binding import com.topjohnwu.magisk.databinding.FragmentHomeMd2Binding
import com.topjohnwu.magisk.model.events.RebootEvent import com.topjohnwu.magisk.model.events.RebootEvent
import com.topjohnwu.magisk.model.navigation.Navigation
import com.topjohnwu.magisk.ui.base.BaseUIFragment import com.topjohnwu.magisk.ui.base.BaseUIFragment
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import org.koin.androidx.viewmodel.ext.android.viewModel 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) { 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() R.id.action_reboot -> RebootEvent.inflateMenu(activity).show()
else -> null else -> null
}?.let { true } ?: super.onOptionsItemSelected(item) }?.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.EnvFixDialog
import com.topjohnwu.magisk.model.events.dialog.ManagerInstallDialog import com.topjohnwu.magisk.model.events.dialog.ManagerInstallDialog
import com.topjohnwu.magisk.model.events.dialog.UninstallDialog 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.BaseViewModel
import com.topjohnwu.magisk.ui.base.itemBindingOf import com.topjohnwu.magisk.ui.base.itemBindingOf
import com.topjohnwu.magisk.utils.KObservableField import com.topjohnwu.magisk.utils.KObservableField
@ -114,7 +113,7 @@ class HomeViewModel(
Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE Manifest.permission.WRITE_EXTERNAL_STORAGE
).map { check(it);it } ).map { check(it);it }
.subscribeK { Navigation.install().publish() } .subscribeK { HomeFragmentDirections.actionHomeFragmentToInstallFragment().publish() }
.add() .add()
fun toggle(kof: KObservableField<Boolean>) = kof.toggle() 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.PermissionEvent
import com.topjohnwu.magisk.model.events.RecreateEvent import com.topjohnwu.magisk.model.events.RecreateEvent
import com.topjohnwu.magisk.model.events.dialog.BiometricDialog 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.BaseViewModel
import com.topjohnwu.magisk.ui.base.adapterOf import com.topjohnwu.magisk.ui.base.adapterOf
import com.topjohnwu.magisk.ui.base.diffListOf import com.topjohnwu.magisk.ui.base.diffListOf
@ -95,7 +94,7 @@ class SettingsViewModel(
override fun onItemChanged(view: View, item: SettingsItem) = when (item) { override fun onItemChanged(view: View, item: SettingsItem) = when (item) {
// use only instances you want, don't declare everything // use only instances you want, don't declare everything
is Theme -> Navigation.theme().publish() is Theme -> SettingsFragmentDirections.actionSettingsFragmentToThemeFragment().publish()
is Language -> RecreateEvent().publish() is Language -> RecreateEvent().publish()
is UpdateChannel -> openUrlIfNecessary(view) 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.SnackbarEvent
import com.topjohnwu.magisk.model.events.dialog.BiometricDialog import com.topjohnwu.magisk.model.events.dialog.BiometricDialog
import com.topjohnwu.magisk.model.events.dialog.SuperuserRevokeDialog 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.BaseViewModel
import com.topjohnwu.magisk.ui.base.adapterOf import com.topjohnwu.magisk.ui.base.adapterOf
import com.topjohnwu.magisk.ui.base.diffListOf import com.topjohnwu.magisk.ui.base.diffListOf
@ -85,8 +84,11 @@ class SuperuserViewModel(
else -> Unit else -> Unit
} }
private fun safetynetPressed() = Navigation.safetynet().publish() private fun safetynetPressed() =
private fun hidePressed() = Navigation.hide().publish() SuperuserFragmentDirections.actionSuperuserFragmentToSafetynetFragment().publish()
private fun hidePressed() =
SuperuserFragmentDirections.actionSuperuserFragmentToHideFragment().publish()
fun deletePressed(item: PolicyItem) { fun deletePressed(item: PolicyItem) {
fun updateState() = deletePolicy(item.item) fun updateState() = deletePolicy(item.item)

View File

@ -19,10 +19,13 @@
android:paddingRight="@{viewModel.insets.right}" android:paddingRight="@{viewModel.insets.right}"
tools:ignore="RtlHardcoded"> tools:ignore="RtlHardcoded">
<androidx.fragment.app.FragmentContainerView <fragment
android:id="@+id/main_nav_host" android:id="@+id/main_nav_host"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent" 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 <com.google.android.material.appbar.AppBarLayout
android:id="@+id/main_toolbar_wrapper" 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 { buildscript {
ext.vKotlin = '1.3.70' ext.vKotlin = '1.3.70'
ext.vNav = '2.3.0-alpha03'
repositories { repositories {
google() google()
@ -18,6 +19,7 @@ buildscript {
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.6.1' classpath 'com.android.tools.build:gradle:3.6.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${vKotlin}" 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 // NOTE: Do not place your application dependencies here; they belong