Skip to content
This repository has been archived by the owner on Dec 14, 2021. It is now read-only.

Commit

Permalink
508: Merge route presenters (#539)
Browse files Browse the repository at this point in the history
* Creating separate parent class for route presenters

* Adding super route presenter to app route presenter and autofill route presenter. Separating generic methods into parent.

* Moving more generic functions into the parent class

* Merging graph_main and graph_autofill. Setting up for separate route presenter test

* Testing route presenter methods

* Dialog fragment tests

* Rebasing with #502

* Making routepresenter abstract, moving some funs around per review suggestions. (WIP)

* Re-separating the nav graphs

* Fixing tests and lint errors

* Remove commented code

* Upgrade dependencies
  • Loading branch information
eliserichards authored Apr 3, 2019
1 parent c8afc8b commit 2061029
Show file tree
Hide file tree
Showing 10 changed files with 550 additions and 254 deletions.
6 changes: 3 additions & 3 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ configurations.all { config ->
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.1.0-alpha03'
implementation 'androidx.appcompat:appcompat:1.1.0-alpha04'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'com.google.android.material:material:1.1.0-alpha05'
implementation "androidx.recyclerview:recyclerview:$recyclerview_version"
Expand Down Expand Up @@ -113,8 +113,8 @@ dependencies {
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.2'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.2'
testImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.2'
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0-alpha03'
implementation 'androidx.lifecycle:lifecycle-common-java8:2.1.0-alpha03'
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0-alpha04'
implementation 'androidx.lifecycle:lifecycle-common-java8:2.1.0-alpha04'
implementation "android.arch.navigation:navigation-fragment:$navigation_version"
implementation "android.arch.navigation:navigation-ui-ktx:$navigation_version"
implementation 'com.adjust.sdk:adjust-android:4.17.0'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import org.junit.runner.RunWith
*/
@ExperimentalCoroutinesApi
@RunWith(AndroidJUnit4::class)
open class RoutePresenterTest {
open class AppRoutePresenterTest {
private val navigator = Navigator()

@Rule @JvmField
Expand Down
171 changes: 171 additions & 0 deletions app/src/main/java/mozilla/lockbox/presenter/AppRoutePresenter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

@file:Suppress("DEPRECATION")

package mozilla.lockbox.presenter

import android.os.Bundle
import androidx.annotation.IdRes
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.Navigation
import io.reactivex.android.schedulers.AndroidSchedulers.mainThread
import io.reactivex.rxkotlin.addTo
import kotlinx.coroutines.ExperimentalCoroutinesApi
import mozilla.lockbox.R
import mozilla.lockbox.action.AppWebPageAction
import mozilla.lockbox.action.DialogAction
import mozilla.lockbox.action.RouteAction
import mozilla.lockbox.action.Setting
import mozilla.lockbox.action.SettingAction
import mozilla.lockbox.extensions.view.AlertDialogHelper
import mozilla.lockbox.flux.Dispatcher
import mozilla.lockbox.store.RouteStore
import mozilla.lockbox.store.SettingStore
import mozilla.lockbox.view.AppWebPageFragmentArgs
import mozilla.lockbox.view.FingerprintAuthDialogFragment
import mozilla.lockbox.view.ItemDetailFragmentArgs

@ExperimentalCoroutinesApi
class AppRoutePresenter(
private val activity: AppCompatActivity,
private val dispatcher: Dispatcher = Dispatcher.shared,
private val routeStore: RouteStore = RouteStore.shared,
private val settingStore: SettingStore = SettingStore.shared
) : RoutePresenter(activity, dispatcher, routeStore) {

override fun onViewReady() {
navController = Navigation.findNavController(activity, R.id.fragment_nav_host)
}

override fun onPause() {
super.onPause()
activity.removeOnBackPressedCallback(backListener)
compositeDisposable.clear()
}

override fun onResume() {
super.onResume()
activity.addOnBackPressedCallback(backListener)
routeStore.routes
.observeOn(mainThread())
.subscribe(this::route)
.addTo(compositeDisposable)
}

fun bundle(action: AppWebPageAction): Bundle {
return AppWebPageFragmentArgs.Builder()
.setUrl(action.url!!)
.setTitle(action.title!!)
.build()
.toBundle()
}

fun bundle(action: RouteAction.ItemDetail): Bundle {
return ItemDetailFragmentArgs.Builder()
.setItemId(action.id)
.build()
.toBundle()
}

override fun route(action: RouteAction) {
activity.setTheme(R.style.AppTheme)
when (action) {
is RouteAction.Welcome -> navigateToFragment(R.id.fragment_welcome)
is RouteAction.Login -> navigateToFragment(R.id.fragment_fxa_login)
is RouteAction.Onboarding.FingerprintAuth ->
navigateToFragment(R.id.fragment_fingerprint_onboarding)
is RouteAction.Onboarding.Autofill -> navigateToFragment(R.id.fragment_autofill_onboarding)
is RouteAction.Onboarding.Confirmation -> navigateToFragment(R.id.fragment_onboarding_confirmation)
is RouteAction.ItemList -> navigateToFragment(R.id.fragment_item_list)
is RouteAction.SettingList -> navigateToFragment(R.id.fragment_setting)
is RouteAction.AccountSetting -> navigateToFragment(R.id.fragment_account_setting)
is RouteAction.LockScreen -> navigateToFragment(R.id.fragment_locked)
is RouteAction.Filter -> navigateToFragment(R.id.fragment_filter_backdrop)
is RouteAction.ItemDetail -> navigateToFragment(R.id.fragment_item_detail, bundle(action))
is RouteAction.OpenWebsite -> openWebsite(action.url)
is RouteAction.SystemSetting -> openSetting(action)
is RouteAction.UnlockFallbackDialog -> showUnlockFallback(action)
is RouteAction.AutoLockSetting -> showAutoLockSelections()
is RouteAction.DialogFragment.FingerprintDialog ->
showDialogFragment(FingerprintAuthDialogFragment(), action)
is DialogAction -> showDialog(action)
is AppWebPageAction -> navigateToFragment(R.id.fragment_webview, bundle(action))
}
}

override fun findTransitionId(@IdRes src: Int, @IdRes dest: Int): Int? {
// This maps two nodes in the graph_main.xml to the edge between them.
// If a RouteAction is called from a place the graph doesn't know about then
// the app will log.error.
return when (src to dest) {
R.id.fragment_null to R.id.fragment_item_list -> R.id.action_init_to_unlocked
R.id.fragment_null to R.id.fragment_locked -> R.id.action_init_to_locked
R.id.fragment_null to R.id.fragment_welcome -> R.id.action_init_to_unprepared

R.id.fragment_welcome to R.id.fragment_fxa_login -> R.id.action_welcome_to_fxaLogin

R.id.fragment_fxa_login to R.id.fragment_item_list -> R.id.action_fxaLogin_to_itemList
R.id.fragment_fxa_login to R.id.fragment_fingerprint_onboarding ->
R.id.action_fxaLogin_to_fingerprint_onboarding
R.id.fragment_fxa_login to R.id.fragment_onboarding_confirmation ->
R.id.action_fxaLogin_to_onboarding_confirmation

R.id.fragment_fingerprint_onboarding to R.id.fragment_onboarding_confirmation ->
R.id.action_fingerprint_onboarding_to_confirmation
R.id.fragment_fingerprint_onboarding to R.id.fragment_autofill_onboarding ->
R.id.action_onboarding_fingerprint_to_autofill

R.id.fragment_autofill_onboarding to R.id.fragment_item_list -> R.id.action_to_itemList
R.id.fragment_autofill_onboarding to R.id.fragment_onboarding_confirmation -> R.id.action_autofill_onboarding_to_confirmation

R.id.fragment_onboarding_confirmation to R.id.fragment_item_list -> R.id.action_to_itemList
R.id.fragment_onboarding_confirmation to R.id.fragment_webview -> R.id.action_to_webview

R.id.fragment_locked to R.id.fragment_item_list -> R.id.action_locked_to_itemList
R.id.fragment_locked to R.id.fragment_welcome -> R.id.action_locked_to_welcome

R.id.fragment_item_list to R.id.fragment_item_detail -> R.id.action_itemList_to_itemDetail
R.id.fragment_item_list to R.id.fragment_setting -> R.id.action_itemList_to_setting
R.id.fragment_item_list to R.id.fragment_account_setting -> R.id.action_itemList_to_accountSetting
R.id.fragment_item_list to R.id.fragment_locked -> R.id.action_itemList_to_locked
R.id.fragment_item_list to R.id.fragment_filter_backdrop -> R.id.action_itemList_to_filter
R.id.fragment_item_list to R.id.fragment_webview -> R.id.action_to_webview

R.id.fragment_item_detail to R.id.fragment_webview -> R.id.action_to_webview

R.id.fragment_setting to R.id.fragment_webview -> R.id.action_to_webview

R.id.fragment_account_setting to R.id.fragment_welcome -> R.id.action_to_welcome

R.id.fragment_filter_backdrop to R.id.fragment_item_detail -> R.id.action_filter_to_itemDetail

else -> null
}
}

private fun showAutoLockSelections() {
val autoLockValues = Setting.AutoLockTime.values()
val items = autoLockValues.map { it.stringValue }.toTypedArray()

settingStore.autoLockTime.take(1)
.map { autoLockValues.indexOf(it) }
.flatMap {
AlertDialogHelper.showRadioAlertDialog(
activity,
R.string.auto_lock,
items,
it,
negativeButtonTitle = R.string.cancel
)
}
.flatMapIterable {
listOf(RouteAction.InternalBack, SettingAction.AutoLockTime(autoLockValues[it]))
}
.subscribe(dispatcher::dispatch)
.addTo(compositeDisposable)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,11 @@ package mozilla.lockbox.presenter
import android.app.Activity
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.service.autofill.FillResponse
import android.view.autofill.AutofillManager
import androidx.annotation.IdRes
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.navigation.NavController
import androidx.navigation.Navigation
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
Expand All @@ -24,7 +20,6 @@ import mozilla.lockbox.action.RouteAction
import mozilla.lockbox.autofill.FillResponseBuilder
import mozilla.lockbox.autofill.IntentBuilder
import mozilla.lockbox.flux.Dispatcher
import mozilla.lockbox.flux.Presenter
import mozilla.lockbox.log
import mozilla.lockbox.store.AutofillStore
import mozilla.lockbox.store.DataStore
Expand All @@ -36,31 +31,19 @@ import mozilla.lockbox.view.FingerprintAuthDialogFragment

@ExperimentalCoroutinesApi
@RequiresApi(Build.VERSION_CODES.O)
class AutofillRoutePresenter(
open class AutofillRoutePresenter(
private val activity: AppCompatActivity,
private val responseBuilder: FillResponseBuilder,
private val dispatcher: Dispatcher = Dispatcher.shared,
private val routeStore: RouteStore = RouteStore.shared,
private val autofillStore: AutofillStore = AutofillStore.shared,
private val dataStore: DataStore = DataStore.shared,
private val pslSupport: PublicSuffixSupport = PublicSuffixSupport.shared
) : Presenter() {
private lateinit var navController: NavController

private val navHostFragmentManager: FragmentManager
get() {
val fragmentManager = activity.supportFragmentManager
val navHost = fragmentManager.fragments.last()
return navHost.childFragmentManager
}

private val currentFragment: Fragment
get() {
return navHostFragmentManager.fragments.last()
}
) : RoutePresenter(activity, dispatcher, routeStore) {

override fun onViewReady() {
navController = Navigation.findNavController(activity, R.id.autofill_fragment_nav_host)

routeStore.routes
.distinctUntilChanged()
.observeOn(AndroidSchedulers.mainThread())
Expand Down Expand Up @@ -94,7 +77,7 @@ class AutofillRoutePresenter(
.addTo(compositeDisposable)
}

private fun route(action: RouteAction) {
override fun route(action: RouteAction) {
when (action) {
is RouteAction.LockScreen -> {
dismissDialogIfPresent()
Expand All @@ -111,51 +94,8 @@ class AutofillRoutePresenter(
}
}

private fun navigateToFragment(@IdRes destinationId: Int, args: Bundle? = null) {
val src = navController.currentDestination ?: return
val srcId = src.id
if (srcId == destinationId && args == null) {
// No point in navigating if nothing has changed.
return
}

val transition = findTransitionId(srcId, destinationId) ?: destinationId

val navOptions = if (transition == destinationId) {
// Without being able to detect if we're in developer mode,
// it is too dangerous to RuntimeException.
val from = activity.resources.getResourceName(srcId)
val to = activity.resources.getResourceName(destinationId)
val graphName = activity.resources.getResourceName(navController.graph.id)
log.error(
"Cannot route from $from to $to. " +
"This is a developer bug, fixable by adding an action to $graphName.xml and/or ${javaClass.simpleName}"
)
null
} else {
// Get the transition action out of the graph, before we manually clear the back
// stack, because it causes IllegalArgumentExceptions.
src.getAction(transition)?.navOptions?.let { navOptions ->
if (navOptions.shouldLaunchSingleTop()) {
while (navController.popBackStack()) {
// NOP
}
routeStore.clearBackStack()
}
navOptions
}
}

try {
navController.navigate(destinationId, args, navOptions)
} catch (e: IllegalArgumentException) {
log.error("This appears to be a bug in navController", e)
navController.navigate(destinationId, args)
}
}

private fun findTransitionId(@IdRes src: Int, @IdRes destination: Int): Int? {
return when (src to destination) {
override fun findTransitionId(@IdRes src: Int, @IdRes dest: Int): Int? {
return when (Pair(src, dest)) {
R.id.fragment_null to R.id.fragment_filter_backdrop -> R.id.action_to_filter
R.id.fragment_null to R.id.fragment_locked -> R.id.action_to_locked
R.id.fragment_locked to R.id.fragment_filter_backdrop -> R.id.action_locked_to_filter
Expand All @@ -164,10 +104,10 @@ class AutofillRoutePresenter(
}
}

private fun showDialogFragment(dialogFragment: DialogFragment, destination: RouteAction.DialogFragment) {
override fun showDialogFragment(dialogFragment: DialogFragment, destination: RouteAction.DialogFragment) {
val fragmentManager = activity.supportFragmentManager
try {
dialogFragment.setTargetFragment(currentFragment, 0)
dialogFragment.show(navHostFragmentManager, dialogFragment.javaClass.name)
dialogFragment.show(fragmentManager, dialogFragment.javaClass.name)
dialogFragment.setupDialog(destination.dialogTitle, destination.dialogSubtitle)
} catch (e: IllegalStateException) {
log.error("Could not show dialog", e)
Expand All @@ -180,23 +120,25 @@ class AutofillRoutePresenter(

private fun finishAutofill(action: AutofillAction) {
when (action) {
is AutofillAction.Cancel -> setFillResponseAndFinish()
is AutofillAction.Cancel -> cancelAndFinish()
is AutofillAction.Complete -> finishResponse(listOf(action.login))
is AutofillAction.CompleteMultiple -> finishResponse(action.logins)
}
}

private fun finishResponse(passwords: List<ServerPassword>) {
val response = responseBuilder.buildFilteredFillResponse(activity, passwords)
setFillResponseAndFinish(response)
response?.let { setFillResponseAndFinish(it) } ?: cancelAndFinish()
}

private fun setFillResponseAndFinish(fillResponse: FillResponse? = null) {
if (fillResponse == null) {
activity.setResult(Activity.RESULT_CANCELED)
} else {
activity.setResult(Activity.RESULT_OK, Intent().putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, fillResponse))
}
private fun cancelAndFinish() {
activity.setResult(Activity.RESULT_CANCELED)
activity.finish()
}

private fun setFillResponseAndFinish(fillResponse: FillResponse) {
val results = Intent().putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, fillResponse)
activity.setResult(Activity.RESULT_OK, results)
activity.finish()
}
}
Loading

0 comments on commit 2061029

Please sign in to comment.