Skip to content
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

Generic LoginServer / UserAccount Picker Bottom Sheet #2670

Merged
merged 74 commits into from
Feb 11, 2025

Conversation

brandonpage
Copy link
Contributor

@brandonpage brandonpage commented Feb 10, 2025

PickerBottomSheet is an extremely DRY, reusable container that displays a row list item component that corresponds with a PickerStyle enum value. If we want to add a new type of Picker in the future, it should be as easy as adding a new enum value and filling in the places the compiler tells you are not exhaustive.

The public component does a lot of setup work with SalesforceSDKManager, LoginViewModel, etc to call a secondary internal component of the same name. This is done to facilitate previews and to make the component testable. For example: the internal picker takes in an onItemSelected: (Any?, Boolean) -> Unit parameter which can be supplied with nothing for previews and a function that tests if is actually being called in tests.

Combining these two approaches makes the picker quite abstract but I believe though careful naming the code is still very readable (let me know if you disagree!).

LoginServerListItem Preview Example:
Screenshot 2025-02-10 at 8 09 08 PM

UserAccountListItem Preview Example:
Screenshot 2025-02-10 at 8 09 37 PM

AddConnection Preview:
Screenshot 2025-02-10 at 9 06 34 AM

PickerBottomSheet Preview:
Screenshot 2025-02-10 at 8 10 25 PM

…ivity reference from LoginView, LoginWebview and LoginWebviewClient.
…del. Add a view model factory var on SDKMangager so LoginActivity can be customized without overriding.
…ow WebChromeClient to be passed into LoginView.
…ve unnecessary passing of LoginViewModelFactory.
Copy link

1 Warning
⚠️ Big PR, try to keep changes smaller if you can.

Generated by 🚫 Danger

Copy link

github-actions bot commented Feb 10, 2025

3 Warnings
⚠️ libs/SalesforceSDK/AndroidManifest.xml#L0 - The project references RTL attributes, but does not explicitly enable or disable RTL support with android:supportsRtl in the manifest
⚠️ libs/SalesforceSDK/AndroidManifest.xml#L61 - Should not restrict activity to fixed orientation. This may not be suitable for different form factors, causing the app to be letterboxed.
⚠️ libs/SalesforceSDK/src/com/salesforce/androidsdk/config/LoginServerManager.java#L337 - Use of this function is discouraged because resource reflection makes it harder to perform build optimizations and compile-time verification of code. It is much more efficient to retrieve resources by identifier (e.g. R.foo.bar) than by name (e.g. getIdentifier("bar", "foo", null)).

Generated by 🚫 Danger

Comment on lines +182 to +188
// Prevent duplicate servers.
for (LoginServer existingServer : getLoginServers()) {
if (name.equals(existingServer.name) && url.equals(existingServer.url)) {
setSelectedLoginServer(existingServer);
return;
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It did not make sense to check for this in the UI code. If we did, we would then have to surface some kind of error message. Simply not adding the duplicate and selecting the original in the data source of truth seemed like an elegant solution.

And I purposefully did not use our equals method because I wanted to ignore isCustom.

*
* @param server the server to remove
*/
public void removeServer(LoginServer server) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a bit gnarly, but necessary for the new UX.

@codecov-commenter
Copy link

codecov-commenter commented Feb 10, 2025

Codecov Report

Attention: Patch coverage is 53.92749% with 305 lines in your changes missing coverage. Please review.

Project coverage is 47.20%. Comparing base (33f7c5c) to head (49d7f22).
Report is 4 commits behind head on dev.

Files with missing lines Patch % Lines
...orce/androidsdk/ui/components/PickerBottomSheet.kt 55.42% 115 Missing and 33 partials ⚠️
...ce/androidsdk/ui/components/LoginServerListItem.kt 53.14% 76 Missing and 6 partials ⚠️
...ce/androidsdk/ui/components/UserAccountListItem.kt 46.06% 44 Missing and 4 partials ⚠️
...src/com/salesforce/androidsdk/ui/theme/SFColors.kt 48.38% 15 Missing and 1 partial ⚠️
...alesforce/androidsdk/ui/AccountSwitcherActivity.kt 0.00% 9 Missing ⚠️
...lesforce/androidsdk/config/LoginServerManager.java 95.83% 0 Missing and 1 partial ⚠️
...m/salesforce/androidsdk/ui/components/LoginView.kt 0.00% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##                dev    #2670      +/-   ##
============================================
+ Coverage     46.37%   47.20%   +0.82%     
- Complexity     2145     2160      +15     
============================================
  Files           206      205       -1     
  Lines         16375    16844     +469     
  Branches       2195     2302     +107     
============================================
+ Hits           7594     7951     +357     
- Misses         7963     8030      +67     
- Partials        818      863      +45     
Components Coverage Δ
Analytics 47.92% <ø> (ø)
SalesforceSDK 32.79% <53.92%> (+2.50%) ⬆️
Hybrid 56.88% <ø> (ø)
SmartStore 78.20% <ø> (ø)
MobileSync 71.49% <ø> (ø)
React 0.00% <ø> (ø)
Files with missing lines Coverage Δ
...src/com/salesforce/androidsdk/ui/LoginViewModel.kt 53.78% <100.00%> (+0.39%) ⬆️
...lesforce/androidsdk/config/LoginServerManager.java 69.43% <95.83%> (+4.93%) ⬆️
...m/salesforce/androidsdk/ui/components/LoginView.kt 40.90% <0.00%> (ø)
...alesforce/androidsdk/ui/AccountSwitcherActivity.kt 0.00% <0.00%> (ø)
...src/com/salesforce/androidsdk/ui/theme/SFColors.kt 37.34% <48.38%> (+7.84%) ⬆️
...ce/androidsdk/ui/components/UserAccountListItem.kt 46.06% <46.06%> (ø)
...ce/androidsdk/ui/components/LoginServerListItem.kt 53.14% <53.14%> (ø)
...orce/androidsdk/ui/components/PickerBottomSheet.kt 55.42% <55.42%> (ø)

... and 1 file with indirect coverage changes

Comment on lines +154 to +193
val onNewLoginServerSelected = { newSelectedServer: Any?, closePicker: Boolean ->
if (newSelectedServer != null && newSelectedServer is LoginServer) {
viewModel.showServerPicker.value = !closePicker
viewModel.loading.value = true
viewModel.dynamicBackgroundColor.value = backgroundColor
SalesforceSDKManager.getInstance().loginServerManager.selectedLoginServer = newSelectedServer
}
}
val onLoginServerCancel = { viewModel.showServerPicker.value = false }
val onUserAccountSelected = { userAccount: Any?, _: Boolean ->
if (userAccount != null && userAccount is UserAccount) {
activity?.finish()
userAccountManager.switchToUser(userAccount)
}
}
val onUserSwitchCancel = {
activity?.finish()
if (userAccountManager.currentUser == null) {
userAccountManager.switchToUser(userAccountManager.authenticatedUsers.first())
}
}
val sheetState = rememberModalBottomSheetState(
skipPartiallyExpanded = true,
confirmValueChange = { sheetValue ->
if (sheetValue == SheetValue.Hidden) {
when (pickerStyle) {
PickerStyle.LoginServerPicker -> onLoginServerCancel()
PickerStyle.UserAccountPicker -> onUserSwitchCancel()
}
}

true
}
)
val addNewLoginServer = { name: String, url: String ->
loginServerManager.addCustomLoginServer(name, url)
viewModel.showServerPicker.value = false
viewModel.loading.value = true
viewModel.dynamicBackgroundColor.value = backgroundColor
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These inline functions are the only untested part of the code. My UI tests check that they are called but their actual contents can't be tested. Maybe the Login Server ones should be added to LoginViewModel?

The UserAccount ones wouldn't belong there and require reference to the activity (which should be avoided in view models IMO).

color = colorScheme.surfaceVariant,
)

// Add New Connection/Account Button
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding this to the end of the list feels a bit obtuse, but having the add button as the last list item really helps to make the UI more usable on a phone in landscape when the list is very cramped.

Comment on lines +540 to +548
// Ensure no duplicates in the list because they are not allowed by lazy column.
fun List<Any>.pickerDistinctBy() : List<Any> {
return distinctBy { listItem ->
when(listItem) {
is LoginServer -> with(listItem) { "$name$url" }
else -> listItem.toString()
}
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is probably necessary/overkill since the lazy column uses the list items's toString as a unique key but I would rather just not show duplicate servers at all if they are already stored on the users device pre-13.0.

)
}

@Composable
fun sfDarkColors(): ColorScheme {
val context = LocalContext.current
return darkColorScheme(
primary = Color(SFColors.primaryColor(context)),
primaryContainer = Color(SFColors.primaryColorDark(context)),
primary = Color(SFColors.primaryColorDark(context)),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Copy link
Contributor

@wmathurin wmathurin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

Copy link
Contributor

@JohnsonEricAtSalesforce JohnsonEricAtSalesforce left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤘🏻

@brandonpage brandonpage merged commit eac0b7c into forcedotcom:dev Feb 11, 2025
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants