Updated the app to use navigation components instead of custom solution
Welcome to mid 2018.
This commit is contained in:
parent
922e36cfb0
commit
1b8813228b
@ -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
|
||||
@ -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}"
|
||||
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
}
|
@ -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
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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"
|
||||
|
148
app/src/main/res/navigation/main.xml
Normal file
148
app/src/main/res/navigation/main.xml
Normal 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>
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user