Skip to content

Conversation

@jbsession
Copy link
Collaborator

@jbsession jbsession commented Nov 6, 2025

This PR updates the Manage Members screen:

  • Replaces bottomsheet action buttons with a collapsible footer for:
    • Resending invitations
    • Removing members from the group
  • Added search functionality and updated list sorting based on PRD
  • Adds new UI strings and confirmation dialogs for the updated actions
  • Updated handling invitation resends

TO DOs:

  • Update toast design
  • Invite members
manageMember.mov

@jbsession jbsession self-assigned this Nov 6, 2025
@jbsession jbsession marked this pull request as ready for review November 6, 2025 03:37
@@ -0,0 +1,16 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="50dp"
Copy link
Collaborator

Choose a reason for hiding this comment

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

Remember to set the icon's size to 24 when you import it to match our other icons.
You can still change it after the fact by updating to

android:width="24dp"
android:height="24dp"

But you can leave the viewport to 50

This icon should probably be allowed to be mirrored for RTL too with:
android:autoMirrored="true"

Which is also true for the other icon you added

*/

@Composable
fun SearchBarWithCancel(
Copy link
Collaborator

Choose a reason for hiding this comment

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

We already have a search bar component: SearchBar. Any reason to not just use that? The cancel can be added where needed in a row with it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I was thinking it would be easier to have this reusable component that handles the visibility of the Cancel .

Copy link
Collaborator

Choose a reason for hiding this comment

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

In theory composables should be stateless and pass the responsibility all the way up to the VM.
It's a bit painful at times, so if it feels over complicated you could leave it here, but I guess the question is still: Why not use out SearchBar?
Is it not compatible with this?


// Output: List of only NON-ADMINS
@OptIn(FlowPreview::class)
val nonAdminMembers: StateFlow<List<GroupMemberState>> = combine(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why recreate the members logic here? You already have members so you can map it directly:

    val nonAdminMembers: StateFlow<List<GroupMemberState>> = members
        .map { list -> list.filter { !it.showAsAdmin } }
        .stateIn(viewModelScope, SharingStarted.Lazily, emptyList())
        ```

import org.session.libsignal.utilities.Log

class InviteContactsJob(val groupSessionId: String, val memberSessionIds: Array<String>) : Job {
class InviteContactsJob(val groupSessionId: String, val memberSessionIds: Array<String>, val isReinvite : Boolean) : Job {
Copy link
Collaborator

Choose a reason for hiding this comment

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

It might not be safe adding a property directly into an existing job. A job is persisted into db and when it's deserialized, funny thing could happen but i'm not very sure what the behavior here will be. You can try this by inviting someone to the group, using code prior to this PR, then quickly kill the app before the invite completes. Then you install the code from this PR and see whether you'll have trouble reintroduce the job. It could also be fine though, just need some testing

}
) {
// Look up current member configs once
val membersCfg = configFactory.withGroupConfigs(groupId) { it.groupMembers }
Copy link
Collaborator

Choose a reason for hiding this comment

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

It's not safe to use the member config object outside of the withGroupConfigs function. The function is awkward to use to prevent this from happening. This is because the config objects are not thread safe, the withGroupConfigs holds a mutex to allow you to access it safely. You'll need to do things quickly with the group members inside the block.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I see. Will update to this:

          val invites: List<MemberInvite> = configFactory.withGroupConfigs(groupId) { cfg ->
               selectedMembers.value.map { member ->
                   val shareHistory =
                       cfg.groupMembers.getOrNull(member.accountId.hexString)?.supplement == true
                   MemberInvite(id = member.accountId, shareHistory = shareHistory)
               }
           }

navigateToInviteContact: (Set<String>) -> Unit,
onBack: () -> Unit,
) {
ManageMembers(
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is using the old way of listing each actions one by one. You can see why we decided to switch for the VM Commands system as this quickly gets out of hand the more functionality you have.
It would have been a good occasion to switch it to commands as you created new screens.
It's ok for this one as it's already done, but it's good to keep in mind for new screens as it makes it a lot more manageable.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ah, I can clean this up since I have the sendCommand parameter already setup.

removeMembersData: ManageGroupMembersViewModel.RemoveMembersState,
removeSearchState: (clearSelection : Boolean) -> Unit
) {
val optionsList: List<ManageGroupMembersViewModel.OptionsItem> = listOf(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Wouldn't you want the VM to create this? This way we could have more buttonsin the future, also mixed in with logic, so the VM could handle choosing which buttons are displayed.

}
}

//todo : Delete after implementing collapsing bottom
Copy link
Collaborator

Choose a reason for hiding this comment

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

You have a few todo to remove certain elements, and it looks like they can be removed

private val mutableError = MutableStateFlow<String?>(null)
val error: StateFlow<String?> get() = mutableError

private val _mutableOngoingAction = MutableStateFlow<String?>(null)
Copy link
Collaborator

Choose a reason for hiding this comment

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

If you end up with a lot of different state for the screen, they could be solidified under one UiState object.
I'll leave it for you to decide out to organise it.

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.

3 participants