Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
f3b8de3
New message description icon change
jbsession Oct 29, 2025
ac4d7c2
Ons lookup failed error
jbsession Oct 29, 2025
65ec34e
unregistered ONS error string
jbsession Oct 29, 2025
edee85b
Modal when clicking help
jbsession Oct 29, 2025
d3b7224
Invalid account id check
jbsession Oct 30, 2025
49d20bb
Updated url dialog commands
jbsession Oct 30, 2025
97ed879
manage admin menu
jbsession Oct 30, 2025
32d2d7f
Removed groupv2 banners
jbsession Oct 30, 2025
91830a2
Leave group option
jbsession Oct 30, 2025
417642a
options
jbsession Nov 3, 2025
06458f3
Initial search with cancel
jbsession Nov 3, 2025
6d81de6
fix search state
jbsession Nov 3, 2025
d93ad02
Animation for focused state:
jbsession Nov 3, 2025
2927aa2
cleanup, Added non-admin members list
jbsession Nov 3, 2025
a6edb1a
Selected items
jbsession Nov 3, 2025
73554cc
Initial bottom footer action
jbsession Nov 3, 2025
fd5d21b
Initial resend invite
jbsession Nov 4, 2025
3b6fb51
Update state when clicking resend
jbsession Nov 4, 2025
73f23ea
Fix search state after resend
jbsession Nov 4, 2025
23ccf9c
New error and toast strings, code cleanup
jbsession Nov 5, 2025
b7386e6
Remove members dialog
jbsession Nov 5, 2025
5351084
cleanup, screen and viewmodel renamed to ManageGroupMembers
jbsession Nov 5, 2025
5873753
clear search state after removing
jbsession Nov 6, 2025
5aebd8d
Fixed some button logic
jbsession Nov 6, 2025
70df9fb
Fixed resend invite quantity screen
jbsession Nov 6, 2025
df3f53e
Empty state
jbsession Nov 6, 2025
59d87e9
Handle chevron search state
jbsession Nov 6, 2025
f8f4fb2
Updated list sorting order
jbsession Nov 6, 2025
1db4656
Cleanup
jbsession Nov 6, 2025
8ed326e
Cleanups
jbsession Nov 6, 2025
fd4391a
Merge branch 'dev' into ses-4753/manage-members
jbsession Nov 6, 2025
360682b
Revert heap size
jbsession Nov 6, 2025
676ad55
Cleanups
jbsession Nov 6, 2025
d315cb8
Updated icon dimens
jbsession Nov 6, 2025
398af1c
Updated nonAdminMember flow
jbsession Nov 7, 2025
65728c9
Fixed unsafe member config
jbsession Nov 7, 2025
682b84c
test
jbsession Nov 7, 2025
850ff03
Revert "test"
jbsession Nov 7, 2025
8a96d6a
test
jbsession Nov 7, 2025
e14871d
Revert "test"
jbsession Nov 7, 2025
a445363
Updated search with x and close button
jbsession Nov 7, 2025
f0e66ae
Added hasMembers flag
jbsession Nov 7, 2025
db69eca
Removed unused composables, flow and function
jbsession Nov 7, 2025
47256e1
Code cleanup for commands, and unused code, renamed item
jbsession Nov 7, 2025
8bdb2d9
Moved options to state, initial uistate
jbsession Nov 7, 2025
f90d1db
Added flows to UIState
jbsession Nov 10, 2025
97ec585
Added footer to the UiState
jbsession Nov 10, 2025
6731b93
updated icon
jbsession Nov 10, 2025
043e79d
persist job
jbsession Nov 10, 2025
d869eb7
Add InviteContactsJob Factory
jbsession Nov 10, 2025
84721d6
Fixed merge conflicts
jbsession Nov 11, 2025
4a71bbe
Merge branch 'dev' into ses-4753/manage-members
jbsession Nov 14, 2025
315656c
String ref fix
jbsession Nov 14, 2025
e8c6c5e
Merge branch 'dev' into ses-4753/manage-members
jbsession Dec 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ fun ConversationSettingsNavHost(

val viewModel =
hiltViewModel<ManageGroupMembersViewModel, ManageGroupMembersViewModel.Factory> { factory ->
factory.create(data.groupAddress)
factory.create(data.groupAddress, navigator)
}

ManageGroupMembersScreen(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,15 +90,15 @@ abstract class BaseGroupMembersViewModel(
).stateIn(viewModelScope, SharingStarted.Lazily, emptyList())

// Output: List of only NON-ADMINS
@OptIn(FlowPreview::class)
val nonAdminMembers: StateFlow<List<GroupMemberState>> = combine(
groupInfo.map { it?.second.orEmpty() },
mutableSearchQuery.debounce(100L),
::filterContacts
)
val nonAdminMembers: StateFlow<List<GroupMemberState>> = members
.map { list -> list.filter { !it.showAsAdmin } }
.stateIn(viewModelScope, SharingStarted.Lazily, emptyList())

val hasNonAdminMembers: StateFlow<Boolean> =
groupInfo
.map { pair -> pair?.second.orEmpty().any { !it.showAsAdmin } }
.stateIn(viewModelScope, SharingStarted.Lazily, false)

fun onSearchQueryChanged(query: String) {
mutableSearchQuery.value = query
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,18 @@ import org.session.libsession.utilities.StringSubstitutionConstants.GROUP_NAME_K
import org.session.libsession.utilities.StringSubstitutionConstants.NAME_KEY
import org.session.libsession.utilities.StringSubstitutionConstants.OTHER_NAME_KEY
import org.session.libsignal.utilities.AccountId
import org.thoughtcrime.securesms.conversation.v2.settings.ConversationSettingsDestination
import org.thoughtcrime.securesms.database.RecipientRepository
import org.thoughtcrime.securesms.ui.CollapsibleFooterItemData
import org.thoughtcrime.securesms.ui.GetString
import org.thoughtcrime.securesms.ui.UINavigator
import org.thoughtcrime.securesms.util.AvatarUtils


@HiltViewModel(assistedFactory = ManageGroupMembersViewModel.Factory::class)
class ManageGroupMembersViewModel @AssistedInject constructor(
@Assisted private val groupAddress: Address.Group,
@Assisted private val navigator: UINavigator<ConversationSettingsDestination>,
@param:ApplicationContext private val context: Context,
storage: StorageProtocol,
private val configFactory: ConfigFactoryProtocol,
Expand All @@ -65,10 +68,6 @@ class ManageGroupMembersViewModel @AssistedInject constructor(
private val mutableInProgress = MutableStateFlow(false)
val inProgress: StateFlow<Boolean> get() = mutableInProgress

// show action bottom sheet
private val _clickedMember: MutableStateFlow<GroupMemberState?> = MutableStateFlow(null)
val clickedMember: StateFlow<GroupMemberState?> get() = _clickedMember

// Output: errors
private val mutableError = MutableStateFlow<String?>(null)
val error: StateFlow<String?> get() = mutableError
Expand All @@ -89,6 +88,26 @@ class ManageGroupMembersViewModel @AssistedInject constructor(

private val footerCollapsed = MutableStateFlow(false)

private val optionsList: List<OptionsItem> by lazy {
listOf(
OptionsItem(
name = context.getString(R.string.membersInvite),
icon = R.drawable.ic_user_round_plus,
onClick = ::navigateInviteContacts
),
OptionsItem(
name = context.getString(R.string.accountIdOrOnsInvite),
icon = R.drawable.ic_user_round_search,
onClick = {
// TODO: Add navigation
}
)
)
}

private val _uiState = MutableStateFlow(UiState(options = optionsList))
val uiState: StateFlow<UiState> = _uiState

val collapsibleFooterState: StateFlow<CollapsibleFooterState> =
combine(_mutableSelectedMembers, footerCollapsed) { selected, isCollapsed ->
val count = selected.size
Expand Down Expand Up @@ -189,6 +208,17 @@ class ManageGroupMembersViewModel @AssistedInject constructor(
mutableSearchFocused.value = isFocused
}

private fun navigateInviteContacts() {
viewModelScope.launch {
navigator.navigate(
ConversationSettingsDestination.RouteInviteToGroup(
groupAddress,
excludingAccountIDsFromContactSelection.toList()
)
)
}
}

fun onContactSelected(contacts: Set<Address>) {
performGroupOperation(
showLoading = false,
Expand Down Expand Up @@ -222,12 +252,12 @@ class ManageGroupMembersViewModel @AssistedInject constructor(
}
) {
// Look up current member configs once
val membersCfg = configFactory.withGroupConfigs(groupId) { it.groupMembers }

// Build per-member invites with their own shareHistory flag
val invites = selectedMembers.value.distinct().map { member ->
val shareHistory = membersCfg?.getOrNull(member.accountId.hexString)?.supplement == true
MemberInvite(id = member.accountId, shareHistory = shareHistory)
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)
}
}

removeSearchState(true)
Expand Down Expand Up @@ -324,20 +354,6 @@ class ManageGroupMembersViewModel @AssistedInject constructor(
}
}

fun onMemberClicked(groupMember: GroupMemberState){
// if the member is clickable (ie, not 'you') but is an admin with no possible actions,
// show a toast mentioning they can't be removed
if(!groupMember.canEdit && groupMember.showAsAdmin){
mutableError.value = context.getString(R.string.adminCannotBeRemoved)
} else { // otherwise pass in the clicked member to display the action sheet
_clickedMember.value = groupMember
}
}

fun hideActionBottomSheet(){
_clickedMember.value = null
}

fun clearSelection(){
_mutableSelectedMembers.value = emptySet()
}
Expand All @@ -352,20 +368,37 @@ class ManageGroupMembersViewModel @AssistedInject constructor(
showRemoveMember.value = visible
}

fun onCommand(command : Commands){
when (command){
is Commands.ShowRemoveDialog -> {
toggleRemoveDialog(true)
}
is Commands.DismissRemoveDialog -> {
toggleRemoveDialog(false)
}
is Commands.RemoveMembers -> {
onRemoveContact(command.removeMessages)
}
fun onCommand(command: Commands) {
when (command) {
is Commands.ShowRemoveDialog -> toggleRemoveDialog(true)

is Commands.DismissRemoveDialog -> toggleRemoveDialog(false)

is Commands.RemoveMembers -> onRemoveContact(command.removeMessages)

is Commands.ClearSelection,

is Commands.CloseFooter -> clearSelection()

is Commands.ToggleFooter -> toggleFooter()

is Commands.DismissError -> onDismissError()

is Commands.DismissResend -> onDismissResend()

is Commands.MemberClick -> onMemberItemClicked(command.member)

is Commands.RemoveSearchState -> removeSearchState(command.clearSelection)

is Commands.SearchFocusChange -> onSearchFocusChanged(command.focus)

is Commands.SearchQueryChange -> onSearchQueryChanged(command.query)
}
}

data class UiState(
val options : List<OptionsItem> = emptyList()
)
data class CollapsibleFooterState(
val visible: Boolean = false,
val collapsed: Boolean = false,
Expand All @@ -390,11 +423,32 @@ class ManageGroupMembersViewModel @AssistedInject constructor(
sealed interface Commands {
data object ShowRemoveDialog : Commands
data object DismissRemoveDialog : Commands

data object DismissError : Commands

data object DismissResend : Commands

data object ToggleFooter : Commands

data object CloseFooter : Commands

data object ClearSelection : Commands

data class RemoveSearchState(val clearSelection : Boolean) : Commands

data class SearchQueryChange(val query : String) : Commands

data class SearchFocusChange(val focus : Boolean) : Commands
data class RemoveMembers(val removeMessages: Boolean) : Commands

data class MemberClick(val member: GroupMemberState) : Commands
}

@AssistedFactory
interface Factory {
fun create(groupAddress: Address.Group): ManageGroupMembersViewModel
fun create(
groupAddress: Address.Group,
navigator: UINavigator<ConversationSettingsDestination>
): ManageGroupMembersViewModel
}
}
Loading