Replaced xml navigation with self-handled

This commit is contained in:
Viktor De Pasquale 2019-04-16 19:00:32 +02:00
parent 0e5417a13e
commit ebab126579
13 changed files with 311 additions and 67 deletions

View File

@ -1,6 +1,6 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
kapt {
@ -80,4 +80,7 @@ dependencies {
exclude group: 'androidx.work', module: 'work-runtime-ktx'
exclude group: 'androidx.room', module: 'room-runtime'
}
def navigation = "3.2.0"
implementation "com.ncapdevi:frag-nav:${navigation}"
}

View File

@ -0,0 +1,82 @@
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.skoumal.teanity.viewevents.NavigationDslMarker
import com.skoumal.teanity.viewevents.ViewEvent
import kotlin.reflect.KClass
class MagiskNavigationEvent(
val navDirections: MagiskNavDirectionsBuilder,
val navOptions: MagiskNavOptions,
val animOptions: MagiskAnimBuilder
) : ViewEvent() {
companion object {
operator fun invoke(builder: Builder.() -> Unit) = Builder().apply(builder).build()
}
@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
}

View File

@ -1,34 +1,43 @@
package com.topjohnwu.magisk.model.navigation
import com.skoumal.teanity.viewevents.NavigationEvent
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.ui.hide.MagiskHideFragment
import com.topjohnwu.magisk.ui.home.MagiskFragment
import com.topjohnwu.magisk.ui.log.LogFragment
import com.topjohnwu.magisk.ui.module.ModulesFragment
import com.topjohnwu.magisk.ui.module.ReposFragment
import com.topjohnwu.magisk.ui.settings.SettingsFragment
import com.topjohnwu.magisk.ui.superuser.SuperuserFragment
object Navigation {
fun home() = NavigationEvent {
navDirections { destination = R.id.magiskFragment }
navOptions { popUpTo = R.id.magiskFragment }
fun home() = MagiskNavigationEvent {
navDirections { destination = MagiskFragment::class }
navOptions { popUpTo = MagiskFragment::class }
}
fun superuser() = NavigationEvent {
navDirections { destination = R.id.superuserFragment }
fun superuser() = MagiskNavigationEvent {
navDirections { destination = SuperuserFragment::class }
}
fun modules() = NavigationEvent {
navDirections { destination = R.id.modulesFragment }
fun modules() = MagiskNavigationEvent {
navDirections { destination = ModulesFragment::class }
}
fun repos() = NavigationEvent {
navDirections { destination = R.id.reposFragment }
fun repos() = MagiskNavigationEvent {
navDirections { destination = ReposFragment::class }
}
fun hide() = NavigationEvent {
navDirections { destination = R.id.magiskHideFragment }
fun hide() = MagiskNavigationEvent {
navDirections { destination = MagiskHideFragment::class }
}
fun log() = NavigationEvent {
navDirections { destination = R.id.logFragment }
fun log() = MagiskNavigationEvent {
navDirections { destination = LogFragment::class }
}
fun settings() = MagiskNavigationEvent {
navDirections { destination = SettingsFragment::class }
}

View File

@ -0,0 +1,13 @@
package com.topjohnwu.magisk.model.navigation
import androidx.fragment.app.Fragment
import kotlin.reflect.KClass
interface Navigator {
//TODO Elevate Fragment to MagiskFragment<*,*> once everything is on board with it
val baseFragments: List<KClass<out Fragment>>
fun navigateTo(event: MagiskNavigationEvent)
}

View File

@ -3,23 +3,42 @@ package com.topjohnwu.magisk.ui
import android.content.Intent
import android.os.Bundle
import androidx.core.view.GravityCompat
import androidx.navigation.ui.setupWithNavController
import androidx.fragment.app.Fragment
import com.topjohnwu.magisk.ClassMap
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.ActivityMainBinding
import com.topjohnwu.magisk.model.navigation.Navigation
import com.topjohnwu.magisk.ui.base.MagiskActivity
import com.topjohnwu.magisk.ui.hide.MagiskHideFragment
import com.topjohnwu.magisk.ui.log.LogFragment
import com.topjohnwu.magisk.ui.module.ModulesFragment
import com.topjohnwu.magisk.ui.module.ReposFragment
import com.topjohnwu.magisk.ui.settings.SettingsFragment
import com.topjohnwu.magisk.ui.superuser.SuperuserFragment
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.net.Networking
import com.topjohnwu.superuser.Shell
import org.koin.androidx.viewmodel.ext.android.viewModel
import kotlin.reflect.KClass
import com.topjohnwu.magisk.ui.home.MagiskFragment as HomeFragment
open class MainActivity : MagiskActivity<MainViewModel, ActivityMainBinding>() {
override val layoutRes: Int = R.layout.activity_main
override val viewModel: MainViewModel by viewModel()
override val navHostId: Int = R.id.main_nav_host
override val defaultPosition: Int = 0
override val baseFragments: List<KClass<out Fragment>> = listOf(
HomeFragment::class,
SuperuserFragment::class,
MagiskHideFragment::class,
ModulesFragment::class,
ReposFragment::class,
LogFragment::class,
SettingsFragment::class
)
/*override fun getDarkTheme(): Int {
return R.style.AppTheme_Dark
@ -35,7 +54,6 @@ open class MainActivity : MagiskActivity<MainViewModel, ActivityMainBinding>() {
checkHideSection()
setSupportActionBar(binding.mainInclude.mainToolbar)
binding.navView.setupWithNavController(navController)
}
override fun onBackPressed() {

View File

@ -1,5 +1,7 @@
package com.topjohnwu.magisk.ui
import android.view.MenuItem
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.model.navigation.Navigation
import com.topjohnwu.magisk.ui.base.MagiskViewModel
@ -8,4 +10,18 @@ class MainViewModel : MagiskViewModel() {
fun navPressed() = Navigation.Main.OPEN_NAV.publish()
fun navigationItemPressed(item: MenuItem): Boolean {
when (item.itemId) {
R.id.magiskFragment -> Navigation.home()
R.id.superuserFragment -> Navigation.superuser()
R.id.magiskHideFragment -> Navigation.hide()
R.id.modulesFragment -> Navigation.modules()
R.id.reposFragment -> Navigation.repos()
R.id.logFragment -> Navigation.log()
R.id.settings -> Navigation.settings()
else -> null
}?.publish()?.let { return@navigationItemPressed true }
return false
}
}

View File

@ -1,13 +1,126 @@
package com.topjohnwu.magisk.ui.base
import android.content.Intent
import android.os.Bundle
import androidx.annotation.CallSuper
import androidx.core.net.toUri
import androidx.databinding.ViewDataBinding
import androidx.fragment.app.Fragment
import com.ncapdevi.fragnav.FragNavController
import com.ncapdevi.fragnav.FragNavTransactionOptions
import com.skoumal.teanity.viewevents.ViewEvent
import com.topjohnwu.magisk.model.navigation.MagiskAnimBuilder
import com.topjohnwu.magisk.model.navigation.MagiskNavigationEvent
import com.topjohnwu.magisk.model.navigation.Navigator
import com.topjohnwu.magisk.utils.Utils
import timber.log.Timber
import kotlin.reflect.KClass
abstract class MagiskActivity<ViewModel : MagiskViewModel, Binding : ViewDataBinding> :
MagiskLeanbackActivity<ViewModel, Binding>() {
MagiskLeanbackActivity<ViewModel, Binding>(), FragNavController.RootFragmentListener,
Navigator {
override val numberOfRootFragments: Int get() = baseFragments.size
override val baseFragments: List<KClass<out Fragment>> = listOf()
protected open val defaultPosition: Int = 0
protected val navigationController by lazy {
if (navHostId == 0) throw IllegalStateException("Did you forget to override \"navHostId\"?")
FragNavController(supportFragmentManager, navHostId)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
navigationController.apply {
rootFragmentListener = this@MagiskActivity
initialize(defaultPosition, savedInstanceState)
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
navigationController.onSaveInstanceState(outState)
}
@CallSuper
override fun onEventDispatched(event: ViewEvent) {
super.onEventDispatched(event)
when (event) {
is MagiskNavigationEvent -> navigateTo(event)
}
}
override fun getRootFragment(index: Int) = baseFragments[index].java.newInstance()
override fun navigateTo(event: MagiskNavigationEvent) {
val directions = event.navDirections
navigationController.defaultTransactionOptions = FragNavTransactionOptions.newBuilder()
.customAnimations(event.animOptions)
.build()
navigationController.currentStack
?.indexOfFirst { it.javaClass == event.navOptions.popUpTo }
?.let { if (it == -1) null else it } // invalidate if class is not found
?.let { if (event.navOptions.inclusive) it + 1 else it }
?.let { navigationController.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(this, 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 { startActivity(it) }
}
private fun navigateToFragment(event: MagiskNavigationEvent) {
val destination = event.navDirections.destination?.java ?: let {
Timber.e("Cannot navigate to null destination")
return
}
when (val index = baseFragments.indexOfFirst { it.java.name == destination.name }) {
-1 -> destination.newInstance()
.apply { arguments = event.navDirections.args }
.let { navigationController.pushFragment(it) }
// When it's desired that fragments of same class are put on top of one another edit this
else -> navigationController.switchTab(index)
}
}
override fun onBackPressed() {
val fragment = navigationController.currentFrag as? MagiskFragment<*, *>
if (fragment?.onBackPressed() == true) {
return
}
try {
navigationController.popFragment()
} catch (e: UnsupportedOperationException) {
super.onBackPressed()
}
}
fun openUrl(url: String) = Utils.openLink(this, url.toUri())
private fun FragNavTransactionOptions.Builder.customAnimations(options: MagiskAnimBuilder) =
customAnimations(options.enter, options.exit, options.popEnter, options.popExit)
}

View File

@ -1,16 +1,35 @@
package com.topjohnwu.magisk.ui.base
import androidx.annotation.CallSuper
import androidx.databinding.ViewDataBinding
import androidx.fragment.app.Fragment
import com.skoumal.teanity.view.TeanityFragment
import com.skoumal.teanity.viewevents.ViewEvent
import com.topjohnwu.magisk.model.navigation.MagiskNavigationEvent
import com.topjohnwu.magisk.model.navigation.Navigator
import kotlin.reflect.KClass
abstract class MagiskFragment<ViewModel : MagiskViewModel, Binding : ViewDataBinding> :
TeanityFragment<ViewModel, Binding>() {
TeanityFragment<ViewModel, Binding>(), Navigator {
protected val magiskActivity get() = activity as MagiskActivity<*, *>
fun openLink(url: String) {
magiskActivity.openUrl(url)
// We don't need nested fragments
override val baseFragments: List<KClass<Fragment>> = listOf()
override fun navigateTo(event: MagiskNavigationEvent) = magiskActivity.navigateTo(event)
@CallSuper
override fun onEventDispatched(event: ViewEvent) {
super.onEventDispatched(event)
when (event) {
is MagiskNavigationEvent -> navigateTo(event)
}
}
fun openLink(url: String) = magiskActivity.openUrl(url)
open fun onBackPressed(): Boolean = false
}

View File

@ -6,6 +6,8 @@ import androidx.annotation.DrawableRes
import androidx.appcompat.widget.AppCompatImageView
import androidx.appcompat.widget.Toolbar
import androidx.databinding.BindingAdapter
import androidx.drawerlayout.widget.DrawerLayout
import com.google.android.material.navigation.NavigationView
@BindingAdapter("onNavigationClick")
@ -13,6 +15,17 @@ fun setOnNavigationClickedListener(view: Toolbar, listener: View.OnClickListener
view.setNavigationOnClickListener(listener)
}
@BindingAdapter("onNavigationClick")
fun setOnNavigationClickedListener(
view: NavigationView,
listener: NavigationView.OnNavigationItemSelectedListener
) {
view.setNavigationItemSelectedListener {
(view.parent as? DrawerLayout)?.closeDrawers()
listener.onNavigationItemSelected(it)
}
}
@BindingAdapter("srcCompat")
fun setImageResource(view: AppCompatImageView, @DrawableRes resId: Int) {
view.setImageResource(resId)

View File

@ -29,6 +29,7 @@
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
onNavigationClick="@{(item) -> viewModel.navigationItemPressed(item)}"
app:menu="@menu/drawer" />
</androidx.drawerlayout.widget.DrawerLayout>

View File

@ -31,15 +31,12 @@
</com.google.android.material.appbar.AppBarLayout>
<fragment
<FrameLayout
android:id="@+id/main_nav_host"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:windowBackground"
app:defaultNavHost="true"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
app:navGraph="@navigation/navigation_main" />
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -1,39 +0,0 @@
<?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/navigation_main"
app:startDestination="@id/magiskFragment">
<fragment
android:id="@+id/magiskFragment"
android:name="com.topjohnwu.magisk.ui.home.MagiskFragment"
android:label="fragment_magisk"
tools:layout="@layout/fragment_magisk" />
<fragment
android:id="@+id/superuserFragment"
android:name="com.topjohnwu.magisk.ui.superuser.SuperuserFragment"
android:label="fragment_superuser"
tools:layout="@layout/fragment_superuser" />
<fragment
android:id="@+id/modulesFragment"
android:name="com.topjohnwu.magisk.ui.module.ModulesFragment"
android:label="fragment_modules"
tools:layout="@layout/fragment_modules" />
<fragment
android:id="@+id/reposFragment"
android:name="com.topjohnwu.magisk.ui.module.ReposFragment"
android:label="fragment_repos"
tools:layout="@layout/fragment_repos" />
<fragment
android:id="@+id/magiskHideFragment"
android:name="com.topjohnwu.magisk.ui.hide.MagiskHideFragment"
android:label="fragment_magisk_hide"
tools:layout="@layout/fragment_magisk_hide" />
<fragment
android:id="@+id/logFragment"
android:name="com.topjohnwu.magisk.ui.log.LogFragment"
android:label="fragment_log"
tools:layout="@layout/fragment_log" />
</navigation>

View File

@ -17,7 +17,6 @@ buildscript {
classpath 'com.android.tools:r8:1.4.79'
classpath 'com.android.tools.build:gradle:3.3.2'
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.30'
classpath 'androidx.navigation:navigation-safe-args-gradle-plugin:2.0.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files