-
Notifications
You must be signed in to change notification settings - Fork 902
PM-27703: Update Authenticator navigation #6109
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
Conversation
|
Claude finished @david-livefront's task —— View job PR Review: Authenticator Navigation UpdatesI've completed a comprehensive review of PR #6109. This PR successfully updates the Authenticator navigation structure to mirror the Password Manager, with excellent architectural improvements. Summary✅ Overall Assessment: Approved with minor documentation suggestion This PR delivers a high-quality navigation refactoring with significant improvements: Key Achievements:
Architecture Compliance:
Review DetailsI've created 10 inline comments covering: Positive Changes:
Documentation Suggestion:
Previous Review Issues - Resolution StatusAll issues from previous reviews have been addressed:
Test CoverageCodecov shows 20% patch coverage (56 lines missing). This is expected and appropriate:
Security ConsiderationsNo security concerns identified:
Recommendations
Final VerdictThis PR is ready to merge. The navigation refactoring is well-executed, follows established patterns, and significantly improves code maintainability. All previous review concerns have been addressed, and the changes align with project architecture guidelines. Great work on this refactoring! 👍 |
|
Great job! No new security vulnerabilities introduced in this pull request |
e37fd5e to
25eaec9
Compare
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #6109 +/- ##
==========================================
+ Coverage 84.30% 84.90% +0.59%
==========================================
Files 863 723 -140
Lines 55387 52753 -2634
Branches 7725 7659 -66
==========================================
- Hits 46694 44789 -1905
+ Misses 5981 5277 -704
+ Partials 2712 2687 -25 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
25eaec9 to
69f66c7
Compare
ui/src/main/kotlin/com/bitwarden/ui/platform/base/util/NavControllerExtensions.kt
Show resolved
Hide resolved
| } | ||
| tutorialSettingsDestination( | ||
| onTutorialFinished = { navController.popBackStack() }, | ||
| settingsDestination( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing tutorial destination in graph:
The settingsGraph function accepts onNavigateToTutorial and passes it to settingsDestination, but the tutorial destination itself (tutorialSettingsDestination) is no longer included in this navigation graph.
Looking at the changes, tutorialSettingsDestination was removed from this file but is now added to AuthenticatorNavigation.kt instead. This means:
- Tutorial navigation is triggered from Settings
- But the tutorial screen is now part of the Authenticator graph, not the Settings graph
Is this the intended hierarchy? This creates a cross-graph navigation dependency where Settings (a leaf in the nav bar) navigates to Tutorial (which is now in the parent Authenticator graph). This works but may be confusing for future maintenance.
Recommendation: Consider documenting this cross-graph navigation pattern or ensuring the tutorial destination is added at the appropriate graph level where it's actually used.
authenticator/src/main/kotlin/com/bitwarden/authenticator/MainActivity.kt
Show resolved
Hide resolved
...src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/rootnav/RootNavNavigation.kt
Show resolved
Hide resolved
ui/src/main/res/values/values.xml
Outdated
| @@ -0,0 +1,4 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <resources> | |||
| <dimen name="dialogDimBackgroundAmount" format="float">0.55</dimen> | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Incorrect format attribute:
The format attribute should not be used in <dimen> resource definitions. The format is automatically inferred from the resource type.
<dimen name="dialogDimBackgroundAmount" format="float">0.55</dimen> Should be:
<dimen name="dialogDimBackgroundAmount">0.55</dimen>The same issue exists in values-night/values.xml:3. While Android may tolerate this, it's not proper XML resource syntax and could cause issues with resource processing or linting tools.
...r/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/SettingsScreen.kt
Show resolved
Hide resolved
app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavNavigation.kt
Show resolved
Hide resolved
...om/bitwarden/authenticator/ui/authenticator/feature/authenticator/AuthenticatorNavigation.kt
Show resolved
Hide resolved
69f66c7 to
864f064
Compare
| } else if (target.graphRoute.toObjectNavigationRoute() == currentDestination?.parent?.route) { | ||
| // We are not at the start destination but we are in the correct graph, | ||
| // so lets pop up to the start destination. | ||
| popBackStack(route = target.startDestinationRoute, inclusive = false) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic here appears correct after reviewing the Password Manager's implementation - the return on line 28 ensures we exit after popping to the start destination. This correctly prevents falling through to the navigate call below.
This addresses the concern raised in a previous review comment. The structure is now:
- Line 21-23: If at start destination → return early ✓
- Line 24-28: If in correct graph but not at start → popBackStack and return ✓
- Line 29+: Otherwise → navigate to new graph ✓
Status: ✅ The code is correct as-is.
| navigateToImport = { navController.navigateToImporting() }, | ||
| navigateToTutorial = { navController.navigateToSettingsTutorial() }, | ||
| ) | ||
| tutorialSettingsDestination( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📝 Tutorial destination moved to parent graph
The tutorial destination (tutorialSettingsDestination) has been moved from the Settings graph to this Authenticator parent graph (line 75-77). This creates a cross-graph navigation pattern where:
- Tutorial navigation is triggered from the Settings screen
- But the tutorial destination lives in the parent Authenticator graph
Analysis: This is intentional and matches the navigation restructuring objective. The tutorial is now accessible from multiple potential entry points in the future while being triggered from Settings currently.
Recommendation: Consider adding a KDoc comment explaining this cross-graph navigation pattern for future maintainers:
// Tutorial destination is at this level rather than in settingsGraph to allow
// access from multiple entry points while maintaining proper back stack behavior
tutorialSettingsDestination(
onTutorialFinished = { navController.popBackStack() },
)| ) | ||
| startDestination = RootNavigationRoute, | ||
| ) { | ||
| // The debug menu is the only thing that should appear on top of the rest |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📝 Comment clarity - multiple destinations at top level
The comment states:
"The debug menu is the only thing that should appear on top of the rest of the app"
However, both rootNavDestination (line 82) and debugMenuDestination (line 83-86) are at this top level in the NavHost.
Recommendation: Update the comment to clarify the intended structure:
// Both root navigation and debug menu exist at this top level.
// The debug menu can appear on top of the rest of the app without
// interacting with the state-based navigation used by RootNavScreen.This is a minor documentation clarity issue, not a functional problem.
app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavNavigation.kt
Show resolved
Hide resolved
...r/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/SettingsScreen.kt
Show resolved
Hide resolved
ui/src/main/kotlin/com/bitwarden/ui/platform/base/util/NavControllerExtensions.kt
Show resolved
Hide resolved
864f064 to
4c27838
Compare
| @@ -1,19 +1,24 @@ | |||
| @file:OmitFromCoverage | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✅ Good addition: @OmitFromCoverage annotation
Properly addresses the codecov concern from previous reviews. Navigation destination functions are typically composition-based and difficult to unit test meaningfully, making them appropriate candidates for coverage exclusion.
| navigateToImport = { navController.navigateToImporting() }, | ||
| navigateToTutorial = { navController.navigateToSettingsTutorial() }, | ||
| ) | ||
| tutorialSettingsDestination( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📝 Cross-graph navigation pattern
The tutorialSettingsDestination is now in the Authenticator parent graph (line 75-77) but is triggered from the Settings screen. This creates an intentional cross-graph navigation where:
- Tutorial navigation callback passes through Settings → AuthenticatorNavBar → this graph
- Tutorial screen lives at Authenticator graph level for potential future multi-entry-point access
Recommendation: Consider adding a KDoc comment explaining this architectural decision:
// Tutorial destination lives at this graph level (rather than in settingsGraph)
// to enable future access from multiple entry points while maintaining proper
// back stack behavior when triggered from Settings.
tutorialSettingsDestination(
onTutorialFinished = { navController.popBackStack() },
)| navController.navigateToQrCodeScanScreen() | ||
| }, | ||
| navigateToManualKeyEntry = { | ||
| editItemDestination( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 Excellent flattening of navigation hierarchy
Moving individual destinations (editItemDestination, itemSearchDestination, qrCodeScanDestination, manualCodeEntryDestination) directly into the authenticatorGraph instead of nesting them in itemListingGraph is a great architectural improvement:
✅ Simplifies the navigation structure
✅ More closely mirrors Password Manager patterns
✅ Makes destinations accessible from multiple entry points
✅ Cleaner back stack management
This follows CLAUDE.md's directive to leverage existing patterns from the Password Manager.
| * | ||
| * @param target The [NavigationItem] representing the desired navigation target | ||
| */ | ||
| fun NavController.navigateToTabOrRoot(target: NavigationItem) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 Well-designed navigation helper function
This navigateToTabOrRoot extension elegantly handles the three navigation scenarios for tab-based navigation:
- Already at target (lines 21-23): No-op to avoid unnecessary recomposition
- In target graph, wrong screen (lines 24-28): Pop to start destination within graph
- Wrong graph entirely (lines 29-41): Navigate to target graph with proper state management
Benefits:
- ✅ Centralizes navigation logic for both Authenticator and Password Manager
- ✅ Properly handles state save/restore for tab switching
- ✅
launchSingleTop = trueprevents duplicate destinations - ✅ Located in
:uimodule for shared access across both apps
This is a great example of extracting common patterns into reusable utilities per CLAUDE.md guidelines.
| onNavigateToImport: () -> Unit, | ||
| onNavigateToTutorial: () -> Unit, | ||
| ) { | ||
| EventsEffect(viewModel = viewModel) { event -> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 Excellent use of new navigateToTabOrRoot helper
Great refactoring! The EventsEffect now uses the shared navigateToTabOrRoot helper function from :ui module:
EventsEffect(viewModel = viewModel) { event ->
navController.navigateToTabOrRoot(target = event.tab)
}This replaces the previous 74-line inline navigation logic with a single, clean function call. Benefits:
- ✅ Reduces duplication (~70 lines removed)
- ✅ Uses the same navigation logic as Password Manager
- ✅ Easier to maintain and test
- ✅ Follows CLAUDE.md: "Use Established Patterns: Leverage existing components before creating new ones"
| ) | ||
| Spacer(modifier = Modifier.height(height = 8.dp)) | ||
| BitwardenTextRow( | ||
| BitwardenPushRow( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 Excellent refactoring: Using BitwardenPushRow
Great change! Replacing BitwardenTextRow with custom chevron icon with the dedicated BitwardenPushRow component:
Before:
BitwardenTextRow(
text = stringResource(id = BitwardenString.import_vault),
onClick = onImportClick,
trailingContent = {
Icon(
painter = rememberVectorPainter(id = BitwardenDrawable.ic_navigate_next),
contentDescription = null,
tint = BitwardenTheme.colorScheme.icon.secondary,
)
},
)After:
BitwardenPushRow(
text = stringResource(id = BitwardenString.import_vault),
onClick = onImportClick,
)Benefits:
- ✅ Reduces code (~10 lines per row)
- ✅ Improves consistency across the app
- ✅ Makes navigational intent clearer
- ✅ Follows CLAUDE.md: "Use Established Patterns: Leverage existing components"
|
Thanks @SaintPatrck |
| ) | ||
| startDestination = RootNavigationRoute, | ||
| ) { | ||
| // Both root navigation and debug menu exist at this top level. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✅ Improved comment clarity
The updated comment is much clearer:
// Both root navigation and debug menu exist at this top level.
// The debug menu can appear on top of the rest of the app without
// interacting with the state-based navigation used by RootNavScreen.This properly explains why both rootNavDestination and debugMenuDestination are at the top level of the NavHost, addressing the previous review feedback about comment accuracy.
| @@ -0,0 +1,25 @@ | |||
| @file:OmitFromCoverage | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✅ Proper coverage annotation applied
The @OmitFromCoverage annotation has been correctly applied to this new navigation destination file, addressing the codecov concern. This matches:
- The pattern in Password Manager's
RootNavNavigation.kt - The pattern in other Authenticator navigation files (
AuthenticatorNavigation.kt)
Navigation destination functions are composition-based and difficult to unit test meaningfully, making them appropriate candidates for coverage exclusion.
| @@ -0,0 +1,4 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <resources> | |||
| <dimen name="dialogDimBackgroundAmount">0.55</dimen> | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✅ New dialog dim background resource
This adds a new dialogDimBackgroundAmount dimen resource used for controlling the background dimming amount when dialogs are displayed:
- Light theme:
0.55(55% dim) - Dark theme:
0.75(75% dim invalues-night/values.xml)
The resource is properly formatted without a format attribute (the format is correctly inferred from the <dimen> type). This appears to be used for the navigation bar dimming effect mentioned in the PR objectives.
| * Navigate to the verification codes screen. | ||
| */ | ||
| data object NavigateToVerificationCodes : AuthenticatorNavBarEvent() | ||
| data object NavigateToVerificationCodes : AuthenticatorNavBarEvent() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📝 New BackStackUpdate action for improved tab state management
The addition of BackStackUpdate action allows the ViewModel to track back stack changes and update the UI accordingly:
AuthenticatorNavBarAction.BackStackUpdate -> {
// No specific action needed - this is primarily used to trigger
// a recomposition when back stack changes occur
}This is connected to the LaunchedEffect in AuthenticatorNavBarScreen (line 58-65) that observes currentBackStackEntryFlow and sends this action on every back stack change.
Purpose: Ensures the nav bar's selected tab state stays synchronized with the actual navigation state, particularly important after back navigation or deep linking.
This pattern mirrors the Password Manager's approach to tab state management.
| ) { | ||
| val state by viewModel.stateFlow.collectAsStateWithLifecycle() | ||
|
|
||
| EventsEffect(viewModel = viewModel) { event -> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 Password Manager also adopts navigateToTabOrRoot
The Password Manager's VaultUnlockedNavBarScreen now uses the same navigateToTabOrRoot helper:
EventsEffect(viewModel = viewModel) { event ->
navController.navigateToTabOrRoot(target = event.tab)
}This confirms the shared navigation pattern between both apps is working as intended. Both Authenticator and Password Manager now benefit from:
- ✅ Consistent navigation behavior
- ✅ Reduced code duplication (~150 lines total removed)
- ✅ Single source of truth for tab navigation logic
- ✅ Easier maintenance and testing
| @@ -1,20 +1,25 @@ | |||
| @file:OmitFromCoverage | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✅ @OmitFromCoverage applied to Password Manager RootNavNavigation
Good consistency! The Password Manager's RootNavNavigation.kt now has the same @OmitFromCoverage annotation as the Authenticator's version, ensuring both apps handle navigation destination coverage the same way.

🎟️ Tracking
PM-27703
📔 Objective
This PR updates the Navigation structure of the Authenticator app to more closely mirror the Password Manager.
Additionally, there are various animation and navigation fixes made.
📸 Screenshots
before.mp4
after.mp4
⏰ Reminders before review
🦮 Reviewer guidelines
:+1:) or similar for great changes:memo:) or ℹ️ (:information_source:) for notes or general info:question:) for questions:thinking:) or 💭 (:thought_balloon:) for more open inquiry that's not quite a confirmed issue and could potentially benefit from discussion:art:) for suggestions / improvements:x:) or:warning:) for more significant problems or concerns needing attention:seedling:) or ♻️ (:recycle:) for future improvements or indications of technical debt:pick:) for minor or nitpick changes