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: 'com.android.application'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-kapt'
kapt { kapt {
@ -80,4 +80,7 @@ dependencies {
exclude group: 'androidx.work', module: 'work-runtime-ktx' exclude group: 'androidx.work', module: 'work-runtime-ktx'
exclude group: 'androidx.room', module: 'room-runtime' 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 package com.topjohnwu.magisk.model.navigation
import com.skoumal.teanity.viewevents.NavigationEvent import com.topjohnwu.magisk.ui.hide.MagiskHideFragment
import com.topjohnwu.magisk.R 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 { object Navigation {
fun home() = NavigationEvent { fun home() = MagiskNavigationEvent {
navDirections { destination = R.id.magiskFragment } navDirections { destination = MagiskFragment::class }
navOptions { popUpTo = R.id.magiskFragment } navOptions { popUpTo = MagiskFragment::class }
} }
fun superuser() = NavigationEvent { fun superuser() = MagiskNavigationEvent {
navDirections { destination = R.id.superuserFragment } navDirections { destination = SuperuserFragment::class }
} }
fun modules() = NavigationEvent { fun modules() = MagiskNavigationEvent {
navDirections { destination = R.id.modulesFragment } navDirections { destination = ModulesFragment::class }
} }
fun repos() = NavigationEvent { fun repos() = MagiskNavigationEvent {
navDirections { destination = R.id.reposFragment } navDirections { destination = ReposFragment::class }
} }
fun hide() = NavigationEvent { fun hide() = MagiskNavigationEvent {
navDirections { destination = R.id.magiskHideFragment } navDirections { destination = MagiskHideFragment::class }
} }
fun log() = NavigationEvent { fun log() = MagiskNavigationEvent {
navDirections { destination = R.id.logFragment } 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.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.core.view.GravityCompat import androidx.core.view.GravityCompat
import androidx.navigation.ui.setupWithNavController import androidx.fragment.app.Fragment
import com.topjohnwu.magisk.ClassMap import com.topjohnwu.magisk.ClassMap
import com.topjohnwu.magisk.Config import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.ActivityMainBinding import com.topjohnwu.magisk.databinding.ActivityMainBinding
import com.topjohnwu.magisk.model.navigation.Navigation import com.topjohnwu.magisk.model.navigation.Navigation
import com.topjohnwu.magisk.ui.base.MagiskActivity 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.magisk.utils.Utils
import com.topjohnwu.net.Networking import com.topjohnwu.net.Networking
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
import com.topjohnwu.magisk.ui.home.MagiskFragment as HomeFragment
open class MainActivity : MagiskActivity<MainViewModel, ActivityMainBinding>() { open class MainActivity : MagiskActivity<MainViewModel, ActivityMainBinding>() {
override val layoutRes: Int = R.layout.activity_main override val layoutRes: Int = R.layout.activity_main
override val viewModel: MainViewModel by viewModel() override val viewModel: MainViewModel by viewModel()
override val navHostId: Int = R.id.main_nav_host 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 { /*override fun getDarkTheme(): Int {
return R.style.AppTheme_Dark return R.style.AppTheme_Dark
@ -35,7 +54,6 @@ open class MainActivity : MagiskActivity<MainViewModel, ActivityMainBinding>() {
checkHideSection() checkHideSection()
setSupportActionBar(binding.mainInclude.mainToolbar) setSupportActionBar(binding.mainInclude.mainToolbar)
binding.navView.setupWithNavController(navController)
} }
override fun onBackPressed() { override fun onBackPressed() {

View File

@ -1,5 +1,7 @@
package com.topjohnwu.magisk.ui 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.model.navigation.Navigation
import com.topjohnwu.magisk.ui.base.MagiskViewModel import com.topjohnwu.magisk.ui.base.MagiskViewModel
@ -8,4 +10,18 @@ class MainViewModel : MagiskViewModel() {
fun navPressed() = Navigation.Main.OPEN_NAV.publish() 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 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.core.net.toUri
import androidx.databinding.ViewDataBinding 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 com.topjohnwu.magisk.utils.Utils
import timber.log.Timber
import kotlin.reflect.KClass
abstract class MagiskActivity<ViewModel : MagiskViewModel, Binding : ViewDataBinding> : 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()) 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 package com.topjohnwu.magisk.ui.base
import androidx.annotation.CallSuper
import androidx.databinding.ViewDataBinding import androidx.databinding.ViewDataBinding
import androidx.fragment.app.Fragment
import com.skoumal.teanity.view.TeanityFragment 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> : abstract class MagiskFragment<ViewModel : MagiskViewModel, Binding : ViewDataBinding> :
TeanityFragment<ViewModel, Binding>() { TeanityFragment<ViewModel, Binding>(), Navigator {
protected val magiskActivity get() = activity as MagiskActivity<*, *> protected val magiskActivity get() = activity as MagiskActivity<*, *>
fun openLink(url: String) { // We don't need nested fragments
magiskActivity.openUrl(url) 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.AppCompatImageView
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.databinding.BindingAdapter import androidx.databinding.BindingAdapter
import androidx.drawerlayout.widget.DrawerLayout
import com.google.android.material.navigation.NavigationView
@BindingAdapter("onNavigationClick") @BindingAdapter("onNavigationClick")
@ -13,6 +15,17 @@ fun setOnNavigationClickedListener(view: Toolbar, listener: View.OnClickListener
view.setNavigationOnClickListener(listener) view.setNavigationOnClickListener(listener)
} }
@BindingAdapter("onNavigationClick")
fun setOnNavigationClickedListener(
view: NavigationView,
listener: NavigationView.OnNavigationItemSelectedListener
) {
view.setNavigationItemSelectedListener {
(view.parent as? DrawerLayout)?.closeDrawers()
listener.onNavigationItemSelected(it)
}
}
@BindingAdapter("srcCompat") @BindingAdapter("srcCompat")
fun setImageResource(view: AppCompatImageView, @DrawableRes resId: Int) { fun setImageResource(view: AppCompatImageView, @DrawableRes resId: Int) {
view.setImageResource(resId) view.setImageResource(resId)

View File

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

View File

@ -31,15 +31,12 @@
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<fragment <FrameLayout
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"
android:background="?android:windowBackground" android:background="?android:windowBackground"
app:defaultNavHost="true" app:layout_behavior="@string/appbar_scrolling_view_behavior" />
app:layout_behavior="@string/appbar_scrolling_view_behavior"
app:navGraph="@navigation/navigation_main" />
</androidx.coordinatorlayout.widget.CoordinatorLayout> </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:r8:1.4.79'
classpath 'com.android.tools.build:gradle:3.3.2' classpath 'com.android.tools.build:gradle:3.3.2'
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.30' 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 // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files