-
Notifications
You must be signed in to change notification settings - Fork 13
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
QuickEditor: No avatar seleted alert banner #458
Conversation
📲 You can test the changes from this Pull Request in Gravatar Demo by scanning the QR code below to install the corresponding build.
|
d94b765
to
0bb7e96
Compare
var nonSelectedAvatarAlertVisible by rememberSaveable { mutableStateOf(uiState.showNonSelectedAvatarAlert) } | ||
|
||
LaunchedEffect(uiState.showNonSelectedAvatarAlert) { | ||
nonSelectedAvatarAlertVisible = uiState.showNonSelectedAvatarAlert | ||
} |
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.
Would you mind explaining why we need this? I believe this is done so that the banner reappears when the state changes in the ViewModel. Is that it?
I'm worried though, that we now have UI state in two places and we should avoid that.
Would it be possible to keep the state fully in ViewModel (including the dismissed state)?
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.
Thanks for pointing this out. 🙇 I agree that the approach wasn't correct. I've refactored it.
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.
Looks better now! I'm worried about the current logic though. Considering a scenario like this:
- User deletes the currently selected Avatar
- Alert banner appears
- User dismisses the banner
- User tries to delete another avatar, but the request fails
- Alert banner reappears -- this shouldn't happen
- User dismisses the banner
- User tries to delete another avatar again, this time successfully
- Alert banner reappears -- this shouldn't happen
Here's the recording showing alert banner reappearing:
banner_reappears.mp4
I do believe we should respect the user's action to dismiss the banner for longer. I would propose to reset that user action only if the user went from avatar selected
to no avatar
state. Here's an updated scenario where the alert banner would reappear:
- User deletes the currently selected Avatar
- Alert banner appears
- User dismisses the banner
- User tries to delete another avatar, but the request fails
- Alert banner DOESN'T reappear
- User tries to delete another avatar again, this time successfully
- Alert banner DOESN'T reappear
- User selects an avatar
- User deletes the currently selected Avatar
- Alert banner appears - Only here
WDYT?
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 makes sense. I've refactored that and added DeleteAvatarAlertStatus
to make it clearer the current status of the AlertBanner.
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 behavior looks good now!
💡 💡 💡 💡
I do feel like we could simply the implementation and make it more future proof though.
The Alert banner visibility is defined by two values isAvatarSelected (emailAvatars.selectedAvatarId == null)
and isBannerDismissed
.
The visibility condition would look like this: banner.visible = !isAvatarSelected && !isBannerDismissed
The first value isAvatarSelected
is already in the UI state so technically we don't need to do anything extra here.
The second one isBannerDissmed
should change based on the behavior in two places:
- When the users dismisses it manually it should be set to
true
- When the
isAvatarSelected
changes fromtrue
tofalse
it should be reset tofalse
Point 1 is already handled in your implementation. Point 2 is to, kinda, but manually in each potential place where the emailAvatars.selectedAvatarId
value could change. This is where I think we might end up with some bugs in the future as we could simply forget to update it in new places.
As I mentioned above we already have the emailAvatars.selectedAvatarId
in the UI state so we could simply observe it and react when the value changes. Here's an example code:
_uiState
.filter { it.emailAvatars != null } //We don't care about this until we get this value
.map { it.emailAvatars?.selectedAvatarId != null }
.distinctUntilChanged()
.onEach { isAvatarSelected ->
if (isAvatarSelected) {
_uiState.update { currentState ->
currentState.copy(
nonSelectedAvatarAlertVisible = DeleteAvatarAlertStatus.HIDDEN,
)
}
} else {
_uiState.update { currentState ->
currentState.copy(
nonSelectedAvatarAlertVisible = DeleteAvatarAlertStatus.VISIBLE,
)
}
}
}
.launchIn(viewModelScope)
I've used DeleteAvatarAlertStatus
from your implementation but not sure we need it.
With the above code, we can get rid of the shouldShowNonSelectedAvatarAlert()
function completely and the isBannerDismissed
should be properly reset whenever the emailAvatars.selectedAvatarId
value changes no matter what triggered it.
Sorry, for not catching this earlier, but I was focused on the behavior rather than the code! Let me know what you think!
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.
I was looking for a similar approach, but I couldn't come up with anything. Looks like I could have been more inspired.
I've updated the code, removing the DeleteAvatarAlertStatus
, which is totally unnecessary right now, and observing the flow to update the nonSelectedAvatarAlertVisible
, as you suggested.
Thanks for your review, @AdamGrzybkowski, I really appreciate it. I hope it looks better now.
@@ -197,6 +204,13 @@ internal fun AvatarPicker(uiState: AvatarPickerUiState, onEvent: (AvatarPickerEv | |||
.fillMaxWidth() | |||
.padding(bottom = 10.dp), | |||
) | |||
AnimatedVisibility(nonSelectedAvatarAlertVisible) { |
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.
I wonder if we could customize this animation. Currently when this banner gets hidden the whole UI is moved up, rather than the top of the bottom sheet just going down. Feels unnatural.
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.
I've been playing with the AnimatedVisibility
params and options. It's not easy to achieve, or at least it's not pretty straightforward.
I agree it's nice to have, but I don't want to dedicate too much time to this now. What do you think about creating a ticket and returning to it when we have more "free time"?
The UI State for the non selected avatar banner was wrongly split in two different places, the viewModel and the composable. The ViewModel should be the only source of thrust, so moving all the state there.
28352da
to
14d1d32
Compare
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.
Thanks for all the updates @hamorillo!! Looks good now 🎉
I believe, we need to switch the target branch, as this won't make it into 2.2.0, right?
Yeah, as we've discussed, this should be in |
Closes #443
Description
This PR adds an informative banner to encourage users to select an avatar instead of using the default one. Per the definition, the banner should be dismissable but must reappear if the condition occurs again. (Ex: selecting an avatar and deleting it).
Kapture.2024-11-22.at.13.24.53.mp4
Testing Steps
Note: As the deletion flow is not working yet in the SDK, you'll need a browser to remove the selected avatar.
Please verify the UI in both light and dark themes.