[PM-33982] feat: Add device management screen#6754
Conversation
|
Great job! No new security vulnerabilities introduced in this pull request |
# Conflicts: # app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/vaultunlockednavbar/VaultUnlockedNavBarNavigation.kt # app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/vaultunlockednavbar/VaultUnlockedNavBarScreen.kt # app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/vaultunlockednavbar/VaultUnlockedNavBarScreenTest.kt # core/src/main/kotlin/com/bitwarden/core/data/manager/model/FlagKey.kt # ui/src/main/kotlin/com/bitwarden/ui/platform/components/debug/FeatureFlagListItems.kt # ui/src/main/res/values/strings.xml
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #6754 +/- ##
==========================================
- Coverage 85.57% 83.50% -2.07%
==========================================
Files 830 946 +116
Lines 58652 61284 +2632
Branches 8570 8682 +112
==========================================
+ Hits 50189 51173 +984
- Misses 5492 7111 +1619
- Partials 2971 3000 +29
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
| daysAgo < 7 -> BitwardenString.active_this_week | ||
| daysAgo < 14 -> BitwardenString.active_last_week | ||
| daysAgo < 30 -> BitwardenString.active_this_month | ||
| else -> BitwardenString.active_over_thirty_days_ago |
There was a problem hiding this comment.
So this is all based on number of days in a week/month. Should it be based on the day of the week?
If today is Monday, 3 days ago is last week, not this week, right?
There was a problem hiding this comment.
I have mirrored the other clients logic but that makes sense to me 🤔
There was a problem hiding this comment.
We have updated the texts so that it is clearer 🙌
There was a problem hiding this comment.
The language does match the functionality now. But don't we want it to say this week, not last seven days?
There was a problem hiding this comment.
It was decided to keep it simpler as this change will affect all other clients. So we are keeping the logic and adapting the texts.
| * requiring rounding (unlike the JavaScript equivalent). | ||
| */ | ||
| @Suppress("MagicNumber") | ||
| fun Instant?.toLastActivityLabel(clock: Clock, resourceManager: ResourceManager): String? { |
There was a problem hiding this comment.
Since this is a UI layer component, can we just return a Text?
| 24 -> DeviceTypeEntry(BitwardenString.cli, "MacOs") | ||
| 25 -> DeviceTypeEntry(BitwardenString.cli, "Linux") | ||
| 26 -> DeviceTypeEntry(BitwardenString.extension, "DuckDuckGo") | ||
| else -> return resourceManager.getString(BitwardenString.unknown_device) |
There was a problem hiding this comment.
Same thing here, can we just return a Text
| 19 -> DeviceTypeEntry(BitwardenString.extension, "Vivaldi") | ||
| 20 -> DeviceTypeEntry(BitwardenString.extension, "Safari") | ||
| 21 -> DeviceTypeEntry(BitwardenString.sdk, "") | ||
| 22 -> DeviceTypeEntry(BitwardenString.server, "") |
There was a problem hiding this comment.
Should these say something?
Also, should these platforms be translatable?
There was a problem hiding this comment.
Other clients are returning the same data.
Regarding translations, nothing is translatable on other clients 🤔
It makes sense to me to be able to translate words like "server" or "extension"
|
|
||
| ManageDevicesState.ViewState.Error -> BitwardenErrorContent( | ||
| message = stringResource( | ||
| id = BitwardenString.generic_error_message, |
There was a problem hiding this comment.
Do we have nothing more specific than this?
There was a problem hiding this comment.
We do not, mobile was based roughly on existing Pending Auth screen and Manage Devices extension screen.
Probably will see some more design work before being approved
| ) | ||
| } | ||
|
|
||
| else -> SessionItem( |
There was a problem hiding this comment.
Can we make this exhaustive
Change return type from ui extensions from String to Text
Bitwarden Claude Code ReviewOverall Assessment: REQUEST CHANGES This PR adds a new device management screen behind a feature flag ( Code Review Details
|
| daysAgo < 30 -> BitwardenString.past_thirty_days | ||
| else -> BitwardenString.over_thirty_days_ago | ||
| } | ||
| return resourceManager.getString(resId).asText() |
There was a problem hiding this comment.
Now that you are returning a Text you do not need the ResourceManager here anymore.
| * Returns e.g. "Mobile - Android", "Extension - Chrome", "Desktop - Windows". | ||
| */ | ||
| @Suppress("CyclomaticComplexMethod", "MagicNumber") | ||
| fun Int.toReadableDeviceTypeName(resourceManager: ResourceManager): Text { |
There was a problem hiding this comment.
Same here, the ResourceManager is unnecessary
# Conflicts: # core/src/main/kotlin/com/bitwarden/core/data/manager/model/FlagKey.kt
| val hideBottomSheet: Boolean | ||
| get() = internalHideBottomSheet && | ||
| !isFdroid && | ||
| isBuildVersionAtLeast(Build.VERSION_CODES.TIRAMISU) |
There was a problem hiding this comment.
hideBottomSheet always returns false, so the "Skip for now" button cannot dismiss the notification bottom sheet.
Details and fix
The current AND logic requires all three conditions to be true simultaneously. When isFdroid is true, the expression !isFdroid evaluates to false, making the entire result false regardless of internalHideBottomSheet. This means:
- On F-Droid with API 33+: the bottom sheet shows (asking to enable push notifications that don't work on F-Droid) and cannot be dismissed.
- On pre-TIRAMISU: same undismissable state, though the permission check in the Screen may mask this in some cases.
The fix is to use OR logic so that any single reason to hide the sheet is sufficient:
| val hideBottomSheet: Boolean | |
| get() = internalHideBottomSheet && | |
| !isFdroid && | |
| isBuildVersionAtLeast(Build.VERSION_CODES.TIRAMISU) | |
| val hideBottomSheet: Boolean | |
| get() = internalHideBottomSheet || | |
| isFdroid || | |
| !isBuildVersionAtLeast(Build.VERSION_CODES.TIRAMISU) |
The @ChecksSdkIntAtLeast annotation on this property should also be removed since the corrected logic no longer solely checks for a minimum SDK level.
| return if (entry.platform.isNotEmpty()) { | ||
| entry.categoryResId.asText() | ||
| .concat( | ||
| " - ${entry.platform}".asText(), |
There was a problem hiding this comment.
Instead of concat, can we make these format args?
| <string name="continue_without_syncing">Continue without syncing</string> | ||
| <string name="external_link">External link</string> | ||
| <string name="external_link_format" comment="Used for accessibility to indicate that tapping this item will leave the app">%1$s, External link</string> | ||
| <string name="manage_devices">Manage devices</string> |
There was a problem hiding this comment.
This managed devices string is being used in both prod code and for feature flags.
The feature flag name should be in the unlocalized file.
| } | ||
|
|
||
| private fun handleOnLifecycleResumed() { | ||
| updateAuthRequestList() |
There was a problem hiding this comment.
The updateAuthRequestList is a flow, why reset it on resume?
| authRequestsLoaded = false, | ||
| ) | ||
| } | ||
| updateAuthRequestList() |
There was a problem hiding this comment.
Same here, why restart the flow here?
| private fun fetchAllDevices() { | ||
| devicesJob.cancel() | ||
| devicesJob = viewModelScope.launch { | ||
| coroutineScope { |
There was a problem hiding this comment.
What is the purpose of the coroutineScope here?
| @Parcelize | ||
| data class ManageDevicesState( | ||
| val authRequests: List<AuthRequest>, | ||
| val devices: List<DeviceInfo>, |
There was a problem hiding this comment.
Can we make all these lists into ImmuableLists

🎟️ Tracking
https://bitwarden.atlassian.net/browse/PM-33982
📔 Objective
Add a new screen for device management on Android.
This new screen should display the current device, followed by the pending authorization requests and finally all devices with active sessions ordered by "last active date".
📸 Screenshots