-
Notifications
You must be signed in to change notification settings - Fork 389
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
Conversation
…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.
…_credentials.json for API 35.
…r onCancel picker parameter. Prevent duplicate servers.
…oid into 13.0-modern-login # Conflicts: # libs/test/SalesforceSDKTest/src/com/salesforce/androidsdk/auth/LoginViewModelTest.kt
…heet. Remove all magic numbers.
…t to show duplicates. Cleanup UserAccountListItem.
Generated by 🚫 Danger |
Generated by 🚫 Danger |
// Prevent duplicate servers. | ||
for (LoginServer existingServer : getLoginServers()) { | ||
if (name.equals(existingServer.name) && url.equals(existingServer.url)) { | ||
setSelectedLoginServer(existingServer); | ||
return; | ||
} | ||
} |
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.
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) { |
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.
This was a bit gnarly, but necessary for the new UX.
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 | ||
} |
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.
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 |
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.
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.
// 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() | ||
} | ||
} | ||
} |
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.
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.
libs/SalesforceSDK/src/com/salesforce/androidsdk/config/LoginServerManager.java
Outdated
Show resolved
Hide resolved
… adjust tests as a result. Update picker subtext color.
) | ||
} | ||
|
||
@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)), |
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.
👍
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.
LGTM!
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.
🤘🏻
PickerBottomSheet
is an extremely DRY, reusable container that displays a row list item component that corresponds with aPickerStyle
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](https://private-user-images.githubusercontent.com/28540387/411828219-0451fa65-1546-49ee-adcb-c70af1059fb2.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3Mzk2NDkwODEsIm5iZiI6MTczOTY0ODc4MSwicGF0aCI6Ii8yODU0MDM4Ny80MTE4MjgyMTktMDQ1MWZhNjUtMTU0Ni00OWVlLWFkY2ItYzcwYWYxMDU5ZmIyLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTAyMTUlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwMjE1VDE5NDYyMVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTI3MzFhYzcxNzYzZmQ0Y2IwMTdmZDBiNTQwZDBmOTcyMDJiNGU5OTdmOGNlMzczZjM2Njg0NWU2YWE0YzJhOWUmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.zkDYSxpLBY9zYiutMPKQYTtNJwD8oRLSp2l5tqtyTzs)
UserAccountListItem Preview Example:
![Screenshot 2025-02-10 at 8 09 37 PM](https://private-user-images.githubusercontent.com/28540387/411828271-f5d26620-2cde-492b-9aa7-1bbf9892fe5a.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3Mzk2NDkwODEsIm5iZiI6MTczOTY0ODc4MSwicGF0aCI6Ii8yODU0MDM4Ny80MTE4MjgyNzEtZjVkMjY2MjAtMmNkZS00OTJiLTlhYTctMWJiZjk4OTJmZTVhLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTAyMTUlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwMjE1VDE5NDYyMVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWNmOThjNTQ5YTVhN2Y1OTYxYzc3ZWIxOGJhNWM0MmExOWViMmZjZTdlNmQyZGQ0MjFiYWU5ZDM1MzAzNzk1NTYmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.iJVgss82FePPDVRZlZeYbobP_UjU61DNCqk0R3jPOdc)
AddConnection Preview:
![Screenshot 2025-02-10 at 9 06 34 AM](https://private-user-images.githubusercontent.com/28540387/411665114-c2157ffb-d5c6-4ee3-82f1-70c9b3848445.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3Mzk2NDkwODEsIm5iZiI6MTczOTY0ODc4MSwicGF0aCI6Ii8yODU0MDM4Ny80MTE2NjUxMTQtYzIxNTdmZmItZDVjNi00ZWUzLTgyZjEtNzBjOWIzODQ4NDQ1LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTAyMTUlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwMjE1VDE5NDYyMVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTUwYzRmZTYwZWJhMTI0ODUwYTk0MzRiNzE1YjY1NWUzYTAxYmNjMjU0MmZiOTZiMGUwNzlmNjcwZmJmNmNmMmEmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.U5xNtgORF8SaHs56dvt0obkMoXaejHY1weJ-gxQLlF0)
PickerBottomSheet Preview:
![Screenshot 2025-02-10 at 8 10 25 PM](https://private-user-images.githubusercontent.com/28540387/411828299-2c2e8ad8-0b53-48c8-bebd-691470979adf.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3Mzk2NDkwODEsIm5iZiI6MTczOTY0ODc4MSwicGF0aCI6Ii8yODU0MDM4Ny80MTE4MjgyOTktMmMyZThhZDgtMGI1My00OGM4LWJlYmQtNjkxNDcwOTc5YWRmLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTAyMTUlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwMjE1VDE5NDYyMVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWUyNzViMzQwZmZhNDNlODc5NjBiZTA3ZDg5ZjkzNmE2MjRlZjdhZTUyYzc3MDAzOTYzOGJkMzA2YTk2OWI0ZGMmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.P6D-sk5wKUn_UScQgsYt5qDYly6Y3Qw5h6t8Ri-bp10)