Skip to content

Commit

Permalink
feat: hide group creation and edit options for external user [WPB-574…
Browse files Browse the repository at this point in the history
…9] (#2794)
  • Loading branch information
saleniuk authored Mar 18, 2024
1 parent 2ff2e04 commit 40e0afb
Show file tree
Hide file tree
Showing 23 changed files with 384 additions and 189 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,8 @@ class MigrationMapper @Inject constructor() {
previewPicture = scalaUserData.pictureAssetId?.let { toQualifiedId(it, scalaUserData.domain, selfuser) },
completePicture = scalaUserData.pictureAssetId?.let { toQualifiedId(it, scalaUserData.domain, selfuser) },
availabilityStatus = mapUserAvailabilityStatus(scalaUserData.availability),
supportedProtocols = setOf(SupportedProtocol.PROTEUS)
supportedProtocols = setOf(SupportedProtocol.PROTEUS),
userType = UserType.INTERNAL,
)
} else {
val botService =
Expand Down
25 changes: 25 additions & 0 deletions app/src/main/kotlin/com/wire/android/model/ItemActionType.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Wire
* Copyright (C) 2024 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.wire.android.model

enum class ItemActionType {
CHECK, CLICK, CHECK_AND_CLICK;

val checkable: Boolean get() = this == CHECK || this == CHECK_AND_CLICK
val clickable: Boolean get() = this == CLICK || this == CHECK_AND_CLICK
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ data class GroupMetadataState(
val isLoading: Boolean = false,
val error: NewGroupError = NewGroupError.None,
val mode: GroupNameMode = GroupNameMode.CREATION,
val isSelfTeamMember: Boolean? = null
val isSelfTeamMember: Boolean? = null,
val isGroupCreatingAllowed: Boolean? = null,
) {
sealed interface NewGroupError {
object None : NewGroupError
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import com.wire.kalium.logic.data.user.SelfUser
import com.wire.kalium.logic.data.user.User
import com.wire.kalium.logic.data.user.UserAvailabilityStatus
import com.wire.kalium.logic.data.user.UserId
import com.wire.kalium.logic.data.user.type.UserType

fun List<User>.findUser(userId: UserId): User? = firstOrNull { it.id == userId }
fun List<MemberDetails>.findUser(userId: UserId): MemberDetails? = firstOrNull { it.user.id == userId }
Expand Down Expand Up @@ -61,12 +60,6 @@ fun User.avatar(wireSessionImageLoader: WireSessionImageLoader, connectionState:
connectionState = connectionState
)

val MemberDetails.userType: UserType
get() = when (this.user) {
is OtherUser -> (user as OtherUser).userType
is SelfUser -> UserType.INTERNAL
}

fun UserSummary.previewAsset(
wireSessionImageLoader: WireSessionImageLoader
) = UserAvatarData(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import com.wire.kalium.logic.data.conversation.ConversationDetails
import com.wire.kalium.logic.data.conversation.MutedConversationStatus
import com.wire.kalium.logic.data.id.ConversationId
import com.wire.kalium.logic.data.id.QualifiedID
import com.wire.kalium.logic.data.user.type.UserType
import com.wire.kalium.logic.feature.conversation.ArchiveStatusUpdateResult
import com.wire.kalium.logic.feature.conversation.ClearConversationContentUseCase
import com.wire.kalium.logic.feature.conversation.ConversationUpdateReceiptModeResult
Expand Down Expand Up @@ -135,6 +136,7 @@ class GroupConversationDetailsViewModel @Inject constructor(
.distinctUntilChanged()

val selfTeam = getSelfTeam().getOrNull()
val selfUser = observerSelfUser().first()

combine(
groupDetailsFlow,
Expand All @@ -143,6 +145,7 @@ class GroupConversationDetailsViewModel @Inject constructor(
) { groupDetails, isSelfAnAdmin, selfDeletionTimer ->

val isSelfInOwnerTeam = selfTeam?.id != null && selfTeam.id == groupDetails.conversation.teamId?.value
val isSelfExternalMember = selfUser.userType == UserType.EXTERNAL

conversationSheetContent = ConversationSheetContent(
title = groupDetails.conversation.name.orEmpty(),
Expand All @@ -157,25 +160,21 @@ class GroupConversationDetailsViewModel @Inject constructor(
proteusVerificationStatus = groupDetails.conversation.proteusVerificationStatus,
isUnderLegalHold = groupDetails.conversation.legalHoldStatus.showLegalHoldIndicator(),
)
val isGuestAllowed = groupDetails.conversation.isGuestAllowed() || groupDetails.conversation.isNonTeamMemberAllowed()
val isUpdatingReadReceiptAllowed = if (selfTeam == null) {
if (groupDetails.conversation.teamId != null) isSelfAnAdmin else false
} else {
isSelfAnAdmin
}

updateState(
groupOptionsState.value.copy(
groupName = groupDetails.conversation.name.orEmpty(),
protocolInfo = groupDetails.conversation.protocol,
areAccessOptionsAvailable = groupDetails.conversation.isTeamGroup(),
isGuestAllowed = isGuestAllowed,
isGuestAllowed = groupDetails.conversation.isGuestAllowed() || groupDetails.conversation.isNonTeamMemberAllowed(),
isServicesAllowed = groupDetails.conversation.isServicesAllowed(),
isUpdatingAllowed = isSelfAnAdmin,
isUpdatingNameAllowed = isSelfAnAdmin && !isSelfExternalMember,
isUpdatingGuestAllowed = isSelfAnAdmin && isSelfInOwnerTeam,
isUpdatingServicesAllowed = isSelfAnAdmin,
isUpdatingReadReceiptAllowed = isSelfAnAdmin && groupDetails.conversation.isTeamGroup(),
isUpdatingSelfDeletingAllowed = isSelfAnAdmin,
mlsEnabled = isMLSEnabled(),
isReadReceiptAllowed = groupDetails.conversation.receiptMode == Conversation.ReceiptMode.ENABLED,
isUpdatingReadReceiptAllowed = isUpdatingReadReceiptAllowed,
selfDeletionTimer = selfDeletionTimer
)
)
Expand Down Expand Up @@ -253,7 +252,7 @@ class GroupConversationDetailsViewModel @Inject constructor(
viewModelScope.launch {
val result = withContext(dispatcher.io()) {
updateConversationAccess(
enableGuestAndNonTeamMember = groupOptionsState.value.isGuestAllowed && groupOptionsState.value.isUpdatingGuestAllowed,
enableGuestAndNonTeamMember = groupOptionsState.value.isGuestAllowed,
enableServices = enableServices,
conversationId = conversationId
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.hilt.navigation.compose.hiltViewModel
import com.wire.android.BuildConfig
Expand All @@ -46,7 +45,9 @@ import com.wire.android.ui.home.conversations.details.GroupConversationDetailsVi
import com.wire.android.ui.home.conversations.selfdeletion.SelfDeletionMapper.toSelfDeletionDuration
import com.wire.android.ui.home.conversationslist.common.FolderHeader
import com.wire.android.ui.home.settings.SwitchState
import com.wire.android.ui.theme.WireTheme
import com.wire.android.ui.theme.wireColorScheme
import com.wire.android.util.ui.PreviewMultipleThemes
import com.wire.android.util.ui.UIText
import com.wire.kalium.logic.data.conversation.Conversation
import com.wire.kalium.logic.data.id.ConversationId
Expand Down Expand Up @@ -96,7 +97,7 @@ fun GroupConversationSettings(
item {
GroupNameItem(
groupName = state.groupName,
canBeChanged = state.isUpdatingAllowed,
canBeChanged = state.isUpdatingNameAllowed,
onClick = onEditGroupName,
)
}
Expand All @@ -108,14 +109,14 @@ fun GroupConversationSettings(
title = stringResource(id = R.string.conversation_options_guests_label),
subtitle = stringResource(id = R.string.conversation_details_guest_description),
switchState = SwitchState.TextOnly(value = state.isGuestAllowed),
arrowType = if (state.isUpdatingAllowed) ArrowType.TITLE_ALIGNED else ArrowType.NONE,
clickable = Clickable(enabled = state.isUpdatingAllowed, onClick = onGuestItemClicked, onLongClick = {}),
arrowType = if (state.isUpdatingGuestAllowed) ArrowType.TITLE_ALIGNED else ArrowType.NONE,
clickable = Clickable(enabled = state.isUpdatingGuestAllowed, onClick = onGuestItemClicked, onLongClick = {}),
)
}

item {
ServicesOption(
isSwitchEnabledAndVisible = state.isUpdatingAllowed,
isSwitchEnabledAndVisible = state.isUpdatingServicesAllowed,
switchState = state.isServicesAllowed,
isLoading = state.loadingServicesOption,
onCheckedChange = onServiceSwitchClicked
Expand All @@ -134,13 +135,13 @@ fun GroupConversationSettings(
null
},
switchState = SwitchState.TextOnly(value = state.selfDeletionTimer.isEnforced),
arrowType = if (state.isUpdatingAllowed && !state.selfDeletionTimer.isEnforcedByTeam) {
arrowType = if (state.isUpdatingSelfDeletingAllowed && !state.selfDeletionTimer.isEnforcedByTeam) {
ArrowType.TITLE_ALIGNED
} else {
ArrowType.NONE
},
clickable = Clickable(
enabled = state.isUpdatingAllowed && !state.selfDeletionTimer.isEnforcedByTeam,
enabled = state.isUpdatingSelfDeletingAllowed && !state.selfDeletionTimer.isEnforcedByTeam,
onClick = onSelfDeletingClicked,
onLongClick = {}
),
Expand Down Expand Up @@ -315,60 +316,93 @@ fun DisableConformationDialog(@StringRes title: Int, @StringRes text: Int, onCon
)
}

@Preview
@PreviewMultipleThemes
@Composable
fun PreviewAdminTeamGroupConversationOptions() {
fun PreviewAdminTeamGroupConversationOptions() = WireTheme {
GroupConversationSettings(
GroupConversationOptionsState(
conversationId = ConversationId("someValue", "someDomain"),
groupName = "Team Group Conversation",
areAccessOptionsAvailable = true,
isUpdatingAllowed = true,
isUpdatingNameAllowed = true,
isUpdatingGuestAllowed = true,
isUpdatingServicesAllowed = true,
isUpdatingSelfDeletingAllowed = true,
isUpdatingReadReceiptAllowed = true,
isGuestAllowed = true,
isServicesAllowed = true,
isUpdatingGuestAllowed = true
isReadReceiptAllowed = true,
),
{}, {}, {}, {}, {}
)
}

@Preview
@PreviewMultipleThemes
@Composable
fun PreviewGuestAdminTeamGroupConversationOptions() {
fun PreviewGuestAdminTeamGroupConversationOptions() = WireTheme {
GroupConversationSettings(
GroupConversationOptionsState(
conversationId = ConversationId("someValue", "someDomain"),
groupName = "Team Group Conversation",
areAccessOptionsAvailable = true,
isUpdatingAllowed = true,
isUpdatingNameAllowed = true,
isUpdatingGuestAllowed = false,
isUpdatingServicesAllowed = true,
isUpdatingSelfDeletingAllowed = true,
isUpdatingReadReceiptAllowed = true,
isGuestAllowed = true,
isServicesAllowed = true,
isUpdatingGuestAllowed = false
isReadReceiptAllowed = true,
),
{}, {}, {}, {}, {}
)
}

@Preview
@PreviewMultipleThemes
@Composable
fun PreviewMemberTeamGroupConversationOptions() {
GroupConversationSettings(
fun PreviewExternalMemberAdminTeamGroupConversationOptions() = WireTheme {
GroupConversationSettings(
GroupConversationOptionsState(
conversationId = ConversationId("someValue", "someDomain"),
groupName = "Team Group Conversation",
areAccessOptionsAvailable = true,
isUpdatingNameAllowed = false,
isUpdatingGuestAllowed = false,
isUpdatingServicesAllowed = true,
isUpdatingSelfDeletingAllowed = true,
isUpdatingReadReceiptAllowed = true,
isGuestAllowed = true,
isServicesAllowed = true,
isReadReceiptAllowed = true,
),
{}, {}, {}, {}, {}
)
}

@PreviewMultipleThemes
@Composable
fun PreviewMemberTeamGroupConversationOptions() = WireTheme {
GroupConversationSettings(
GroupConversationOptionsState(
conversationId = ConversationId("someValue", "someDomain"),
groupName = "Normal Group Conversation",
areAccessOptionsAvailable = true,
isUpdatingAllowed = false,
isUpdatingNameAllowed = false,
isUpdatingGuestAllowed = false,
isUpdatingServicesAllowed = false,
isUpdatingSelfDeletingAllowed = false,
isUpdatingReadReceiptAllowed = false,
isGuestAllowed = true,
isServicesAllowed = true,
isUpdatingGuestAllowed = false
isReadReceiptAllowed = true,
),
{}, {}, {}, {}, {}
)
}

@Preview
@PreviewMultipleThemes
@Composable
fun PreviewNormalGroupConversationOptions() {
fun PreviewNormalGroupConversationOptions() = WireTheme {
GroupConversationSettings(
GroupConversationOptionsState(
conversationId = ConversationId("someValue", "someDomain"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,19 @@ import com.wire.kalium.logic.data.conversation.Conversation
import com.wire.kalium.logic.data.id.ConversationId
import com.wire.kalium.logic.data.message.SelfDeletionTimer

/**
* State for the group conversation options screen.
*
* Fields related to updating should be set according to this table:
* | for given option to be allowed... | ...user needs to be |
* |--------------------------------------------|----------------------------------------------------|
* | add participants to group allowed | group admin & not external team member |
* | group name change allowed | group admin & not external team member |
* | group guests option change allowed | group admin & team member of the group owner team |
* | group services option change allowed | group admin |
* | self deleting option change allowed | group admin |
* | group read receipts option change allowed | group admin & group created by a team member |
*/
data class GroupConversationOptionsState(
val conversationId: ConversationId,
val groupName: String = "",
Expand All @@ -31,8 +44,10 @@ data class GroupConversationOptionsState(
val isGuestAllowed: Boolean = false,
val isServicesAllowed: Boolean = false,
val isReadReceiptAllowed: Boolean = false,
val isUpdatingAllowed: Boolean = false,
val isUpdatingNameAllowed: Boolean = false,
val isUpdatingGuestAllowed: Boolean = false,
val isUpdatingServicesAllowed: Boolean = false,
val isUpdatingSelfDeletingAllowed: Boolean = false,
val isUpdatingReadReceiptAllowed: Boolean = false,
val changeGuestOptionConfirmationRequired: Boolean = false,
val changeServiceOptionConfirmationRequired: Boolean = false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ fun GroupConversationParticipants(
groupParticipantsState.data.allCount.toString()
)
)
if (groupParticipantsState.data.isSelfAnAdmin) {
if (groupParticipantsState.addParticipantsEnabled) {
WirePrimaryButton(
text = stringResource(R.string.conversation_details_group_participants_add),
fillMaxWidth = true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ data class GroupConversationParticipantsState(
val data: ConversationParticipantsData = ConversationParticipantsData()
) {
val showAllVisible: Boolean get() = data.allParticipantsCount > data.participants.size || data.allAdminsCount > data.admins.size
val addParticipantsEnabled: Boolean get() = data.isSelfAnAdmin && !data.isSelfExternalMember

companion object {
val PREVIEW = GroupConversationParticipantsState(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ data class ConversationParticipantsData(
val participants: List<UIParticipant> = listOf(),
val allAdminsCount: Int = 0,
val allParticipantsCount: Int = 0,
val isSelfAnAdmin: Boolean = false
val isSelfAnAdmin: Boolean = false,
val isSelfExternalMember: Boolean = false,
) {
val allCount: Int = allAdminsCount + allParticipantsCount
val allParticipants: List<UIParticipant> = participants + admins
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,15 @@ class ObserveParticipantsForConversationUseCase @Inject constructor(
fun List<MemberDetails>.toUIParticipants() = this.map {
uiParticipantMapper.toUIParticipant(it.user, mlsVerificationMap[it.userId], legalHoldList.contains(it.userId))
}
val selfUser = (allParticipants + allAdminsWithoutServices).firstOrNull { it.user is SelfUser }

ConversationParticipantsData(
admins = visibleAdminsWithoutServices.toUIParticipants(),
participants = visibleParticipants.toUIParticipants(),
allAdminsCount = allAdminsWithoutServices.size,
allParticipantsCount = allParticipants.size,
isSelfAnAdmin = allAdminsWithoutServices.any { it.user is SelfUser }
isSelfAnAdmin = allAdminsWithoutServices.any { it.user is SelfUser },
isSelfExternalMember = selfUser?.user?.userType == UserType.EXTERNAL,
)
}
.flowOn(dispatchers.io())
Expand Down
Loading

0 comments on commit 40e0afb

Please sign in to comment.