-
-
Notifications
You must be signed in to change notification settings - Fork 48
CCCT-2440 Connect App Launch For Remaining Pages And Back Navigation #3756
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
conroy-ricketts
wants to merge
13
commits into
CCCT-2439-connect-login-silent-launch-path
Choose a base branch
from
CCCT-2440-login-silent-launch-path-for-remaining-connect-pages
base: CCCT-2439-connect-login-silent-launch-path
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
e355bf0
CCCT-2440 Login Silent Launch Path For Remaining Connect Pages
conroy-ricketts 76662d5
CCCT-2440 Login Silent Launch Path For Remaining Connect Pages
conroy-ricketts 11fa6cd
CCCT-2440 Login Silent Launch Path For Remaining Connect Pages
conroy-ricketts 741ae47
CCCT-2440 Login Silent Launch Path For Remaining Connect Pages
conroy-ricketts de6ab62
CCCT-2440 Login Silent Launch Path For Remaining Connect Pages
conroy-ricketts ee1338f
Add QA notes for CCCT-2440
conroy-ricketts 8bfa45e
Move CCCT-2440 QA notes under the existing Connect app launch bullet
conroy-ricketts 2b23993
CCCT-2440 Connect App Launch For Remaining Pages And Back Navigation
conroy-ricketts 983d87d
Merge branch 'CCCT-2439-connect-login-silent-launch-path' of github.c…
conroy-ricketts 2281db8
Merge branch 'CCCT-2439-connect-login-silent-launch-path' of github.c…
conroy-ricketts f4ed51c
Merge branch 'CCCT-2439-connect-login-silent-launch-path' of github.c…
conroy-ricketts 03af44e
CCCT-2440 Connect App Launch For Remaining Pages And Back Navigation
conroy-ricketts 8c05294
Merge branch 'CCCT-2439-connect-login-silent-launch-path' of github.c…
conroy-ricketts File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
231 changes: 231 additions & 0 deletions
231
app/src/org/commcare/connect/ConnectAppLaunchController.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,231 @@ | ||
| package org.commcare.connect | ||
|
|
||
| import android.app.Activity | ||
| import android.content.Intent | ||
| import androidx.activity.result.ActivityResult | ||
| import androidx.activity.result.ActivityResultLauncher | ||
| import androidx.activity.result.contract.ActivityResultContracts | ||
| import androidx.fragment.app.Fragment | ||
| import androidx.lifecycle.DefaultLifecycleObserver | ||
| import androidx.lifecycle.Lifecycle | ||
| import androidx.lifecycle.LifecycleOwner | ||
| import org.commcare.CommCareApplication | ||
| import org.commcare.activities.DispatchActivity | ||
| import org.commcare.activities.HomeScreenBaseActivity | ||
| import org.commcare.activities.LoginActivity | ||
| import org.commcare.connect.network.TokenExceptionHandler | ||
| import org.commcare.google.services.analytics.FirebaseAnalyticsUtil | ||
| import org.commcare.login.LoginPhase | ||
| import org.commcare.login.LoginProgress | ||
| import org.commcare.util.LogTypes | ||
| import org.commcare.views.dialogs.CustomProgressDialog | ||
| import org.javarosa.core.services.Logger | ||
| import org.javarosa.core.services.locale.Localization | ||
|
|
||
| /** The app a Connect launch is targeting; [isLearning] selects the learn vs delivery app type. */ | ||
| internal data class LaunchTarget( | ||
| val appId: String, | ||
| val isLearning: Boolean, | ||
| ) | ||
|
|
||
| /** | ||
| * How the launch dialog should render a [LoginProgress]: [titleKey]/[messageKey] are localization | ||
| * keys; [overrideMessage] is an already-localized runtime message that supersedes [messageKey]. | ||
| */ | ||
| internal data class LaunchDialogState( | ||
| val showSyncDialog: Boolean, | ||
| val titleKey: String, | ||
| val messageKey: String, | ||
| val overrideMessage: String?, | ||
| val percent: Int?, | ||
| ) | ||
|
|
||
| /** Maps each [LoginProgress] phase to the [LaunchDialogState] the launch dialog should show for it. */ | ||
| internal object LaunchProgressMapper { | ||
| fun map(progress: LoginProgress): LaunchDialogState { | ||
| val syncing = progress.phase == LoginPhase.Syncing | ||
| val (titleKey, messageKey) = | ||
| when (progress.phase) { | ||
| LoginPhase.Seating -> "seating.app" to "seating.app" | ||
| LoginPhase.SigningIn -> "key.manage.title" to "key.manage.start" | ||
| LoginPhase.Syncing -> "sync.communicating.title" to "sync.progress.starting" | ||
| } | ||
| return LaunchDialogState( | ||
| showSyncDialog = syncing, | ||
| titleKey = titleKey, | ||
| messageKey = messageKey, | ||
| overrideMessage = progress.message, | ||
| percent = if (syncing) progress.percent else null, | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Drives a Connect app launch from a fragment without showing LoginActivity: shows a | ||
| * [CustomProgressDialog], runs [ConnectAppLauncher], and routes the [LaunchOutcome] through | ||
| * [LaunchOutcomeRouter]. | ||
| * | ||
| * The dialog is dismissed automatically when the fragment's view is destroyed, so callers don't | ||
| * have to manage cleanup themselves. | ||
| */ | ||
| class ConnectAppLaunchController | ||
| @JvmOverloads | ||
| constructor( | ||
| private val fragment: Fragment, | ||
| private val launcher: ConnectAppLauncher = ConnectAppLauncher(), | ||
| ) { | ||
| private var launchDialog: CustomProgressDialog? = null | ||
| private var showingSyncDialog = false | ||
| private var observedLifecycle: Lifecycle? = null | ||
|
|
||
| // Registered at construction (fragment init, the only valid time) so a back-out of the launched | ||
| // app's Home is delivered back here and can return the worker to the opportunities list. | ||
| private val homeResultLauncher: ActivityResultLauncher<Intent> = | ||
| fragment.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> | ||
| onHomeResult(result) | ||
| } | ||
|
|
||
| fun launchLearningApp(appId: String) = launch(LaunchTarget(appId, isLearning = true)) | ||
|
|
||
| fun launchDeliveryApp(appId: String) = launch(LaunchTarget(appId, isLearning = false)) | ||
|
|
||
| private fun launch(target: LaunchTarget) { | ||
| val activity = fragment.requireActivity() | ||
| val owner = fragment.viewLifecycleOwner | ||
| registerDialogCleanup(owner) | ||
| showOrUpdateDialog(LaunchProgressMapper.map(LoginProgress(LoginPhase.Seating))) | ||
| launcher.start( | ||
| owner, | ||
| activity, | ||
| target.appId, | ||
| target.isLearning, | ||
| { progress -> activity.runOnUiThread { updateLaunchProgress(progress) } }, | ||
| { outcome -> handleLaunchOutcome(outcome, activity, target) }, | ||
| ) | ||
| } | ||
|
|
||
| private fun updateLaunchProgress(progress: LoginProgress) { | ||
| if (!fragment.isAdded) { | ||
| return | ||
| } | ||
| showOrUpdateDialog(LaunchProgressMapper.map(progress)) | ||
| } | ||
|
|
||
| private fun handleLaunchOutcome( | ||
| outcome: LaunchOutcome, | ||
| activity: Activity, | ||
| target: LaunchTarget, | ||
| ) { | ||
| if (!fragment.isAdded) { | ||
| return | ||
| } | ||
| LaunchOutcomeRouter.dispatch( | ||
| outcome, | ||
| object : LaunchActions { | ||
| override fun dismissProgress() = dismissLaunchDialog() | ||
|
|
||
| override fun launchHome() = homeResultLauncher.launch(HomeScreenBaseActivity.buildHomeLaunchIntent(activity)) | ||
|
|
||
| override fun handleTokenDenied() = TokenExceptionHandler.handleTokenDeniedException() | ||
|
|
||
| override fun recoverFromSeatFailure() { | ||
| val intent = | ||
| Intent(activity, DispatchActivity::class.java) | ||
| .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) | ||
| fragment.startActivity(intent) | ||
| activity.finish() | ||
| } | ||
|
|
||
| override fun fallBackToLegacyLaunch() { | ||
| ConnectAppUtils.launchApp(activity, target.isLearning, target.appId) | ||
| } | ||
|
|
||
| override fun reportFailure(reason: String) { | ||
| Logger.log( | ||
| LogTypes.TYPE_ERROR_WORKFLOW, | ||
| "Connect launch failed for app ${target.appId}: $reason", | ||
| ) | ||
| FirebaseAnalyticsUtil.reportCccAppFailedAutoLogin(target.appId) | ||
| } | ||
| }, | ||
| ) | ||
| } | ||
|
|
||
| private fun showOrUpdateDialog(state: LaunchDialogState) { | ||
| val title = Localization.get(state.titleKey) | ||
| val message = state.overrideMessage ?: Localization.get(state.messageKey) | ||
|
|
||
| if (launchDialog == null || state.showSyncDialog != showingSyncDialog) { | ||
| if (fragment.childFragmentManager.isStateSaved) { | ||
| return | ||
| } | ||
|
|
||
| dismissLaunchDialog() | ||
| launchDialog = | ||
| CustomProgressDialog | ||
| .newInstance(title, message, LAUNCH_DIALOG_TASK_ID) | ||
| .apply { if (state.showSyncDialog) addProgressBar() } | ||
| showingSyncDialog = state.showSyncDialog | ||
| launchDialog?.showNow(fragment.childFragmentManager, LAUNCH_DIALOG_TAG) | ||
| } else { | ||
| launchDialog?.updateTitle(title) | ||
| launchDialog?.updateMessage(message) | ||
| } | ||
|
|
||
| state.percent?.let { launchDialog?.updateProgressBar(it, PROGRESS_BAR_MAX) } | ||
| } | ||
|
|
||
| private fun dismissLaunchDialog() { | ||
| launchDialog?.let { | ||
| if (it.isAdded) { | ||
| it.dismissAllowingStateLoss() | ||
| } | ||
| } | ||
| launchDialog = null | ||
| } | ||
|
|
||
| private fun onHomeResult(result: ActivityResult) { | ||
| if (!fragment.isAdded) { | ||
| return | ||
| } | ||
|
|
||
| val activity = fragment.requireActivity() | ||
| if (result.resultCode == Activity.RESULT_OK) { | ||
| // Route to the login screen via DispatchActivity on the user logging-out. | ||
| val intent = | ||
| Intent(activity, DispatchActivity::class.java) | ||
| .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP) | ||
| .putExtra(LoginActivity.USER_TRIGGERED_LOGOUT, true) | ||
| .putExtra(ConnectAppUtils.IS_LAUNCH_FROM_CONNECT, true) | ||
| activity.startActivity(intent) | ||
| return | ||
| } | ||
|
|
||
| // Back-out (RESULT_CANCELED): end the app session, return to the opportunities list. | ||
| CommCareApplication.instance().closeUserSession() | ||
| ConnectNavHelper.goToConnectJobsList(activity, clearTop = true) | ||
| } | ||
|
|
||
| private fun registerDialogCleanup(owner: LifecycleOwner) { | ||
| if (observedLifecycle === owner.lifecycle) { | ||
| return | ||
| } | ||
| observedLifecycle = owner.lifecycle | ||
| owner.lifecycle.addObserver( | ||
| object : DefaultLifecycleObserver { | ||
| override fun onDestroy(owner: LifecycleOwner) { | ||
| dismissLaunchDialog() | ||
| observedLifecycle = null | ||
| } | ||
| }, | ||
| ) | ||
| } | ||
|
|
||
| companion object { | ||
| private const val LAUNCH_DIALOG_TAG = "connect_launch_progress" | ||
|
|
||
| // Negative so it can't collide with the positive task ids CommCareActivity assigns to real tasks. | ||
| private const val LAUNCH_DIALOG_TASK_ID = -10 | ||
| private const val PROGRESS_BAR_MAX = 100 | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.