From 419022ed4c6a2a2e80f5128069367823a46d6f83 Mon Sep 17 00:00:00 2001 From: Mojtaba Chenani Date: Wed, 21 Aug 2024 08:20:35 +0200 Subject: [PATCH 1/4] fix(conversation): disable addMember on a one-on-one conversation with deleted account --- .../details/GroupConversationDetailsScreen.kt | 7 ++++--- .../details/GroupConversationDetailsTopBarCollapsing.kt | 2 +- .../details/participants/GroupConversationParticipants.kt | 5 +++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsScreen.kt index fa1596bbdff..3801c8d5df5 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsScreen.kt @@ -18,6 +18,7 @@ package com.wire.android.ui.home.conversations.details +import SwipeableSnackbar import androidx.annotation.StringRes import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.LocalOverscrollConfiguration @@ -34,6 +35,7 @@ import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarHost import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider @@ -111,8 +113,6 @@ import com.wire.android.ui.theme.wireTypography import com.wire.android.util.ui.UIText import com.wire.kalium.logic.data.conversation.Conversation import kotlinx.coroutines.launch -import SwipeableSnackbar -import androidx.compose.material3.SnackbarHost @RootNavGraph @WireDestination( @@ -394,7 +394,8 @@ private fun GroupConversationDetailsContent( openFullListPressed = openFullListPressed, onAddParticipantsPressed = onAddParticipantsPressed, onProfilePressed = onProfilePressed, - lazyListState = lazyListStates[pageIndex] + lazyListState = lazyListStates[pageIndex], + allowAddMember = conversationSheetState.conversationSheetContent?.title?.isNotEmpty() ?:false ) } } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsTopBarCollapsing.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsTopBarCollapsing.kt index d6f098ce04d..29c18ac86eb 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsTopBarCollapsing.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsTopBarCollapsing.kt @@ -99,7 +99,7 @@ fun GroupConversationDetailsTopBarCollapsing( Text( text = title.ifBlank { if (isLoading) "" - else UIText.StringResource(R.string.group_unavailable_label).asString() + else UIText.StringResource(R.string.member_name_deleted_label).asString() }, overflow = TextOverflow.Ellipsis, maxLines = 1, diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/participants/GroupConversationParticipants.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/participants/GroupConversationParticipants.kt index d657a5a2f1e..219b9c9159c 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/participants/GroupConversationParticipants.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/participants/GroupConversationParticipants.kt @@ -61,7 +61,8 @@ fun GroupConversationParticipants( onProfilePressed: (UIParticipant) -> Unit, onAddParticipantsPressed: () -> Unit, groupParticipantsState: GroupConversationParticipantsState, - lazyListState: LazyListState = rememberLazyListState() + lazyListState: LazyListState = rememberLazyListState(), + allowAddMember: Boolean = true ) { val context = LocalContext.current Column { @@ -86,7 +87,7 @@ fun GroupConversationParticipants( groupParticipantsState.data.allCount.toString() ) ) - if (groupParticipantsState.data.isSelfAnAdmin) { + if (groupParticipantsState.data.isSelfAnAdmin && allowAddMember) { WirePrimaryButton( text = stringResource(R.string.conversation_details_group_participants_add), fillMaxWidth = true, From 2a93cd0ff9fc7a1d5cceaf9e87cbb11c9f18c38a Mon Sep 17 00:00:00 2001 From: Mojtaba Chenani Date: Wed, 21 Aug 2024 08:39:22 +0200 Subject: [PATCH 2/4] fix formatting --- .../conversations/details/GroupConversationDetailsScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsScreen.kt index 3801c8d5df5..13a464d1e77 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsScreen.kt @@ -395,7 +395,7 @@ private fun GroupConversationDetailsContent( onAddParticipantsPressed = onAddParticipantsPressed, onProfilePressed = onProfilePressed, lazyListState = lazyListStates[pageIndex], - allowAddMember = conversationSheetState.conversationSheetContent?.title?.isNotEmpty() ?:false + allowAddMember = conversationSheetState.conversationSheetContent?.title?.isNotEmpty() ?: false ) } } From 687a29174476d13bbbf18d2791b33a7b7de157ec Mon Sep 17 00:00:00 2001 From: Mojtaba Chenani Date: Thu, 22 Aug 2024 08:13:37 +0200 Subject: [PATCH 3/4] check if the empty group does not have more than 1 member --- .../conversations/details/GroupConversationDetailsScreen.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsScreen.kt index 13a464d1e77..4d4d1edc944 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsScreen.kt @@ -395,7 +395,8 @@ private fun GroupConversationDetailsContent( onAddParticipantsPressed = onAddParticipantsPressed, onProfilePressed = onProfilePressed, lazyListState = lazyListStates[pageIndex], - allowAddMember = conversationSheetState.conversationSheetContent?.title?.isNotEmpty() ?: false + allowAddMember = conversationSheetState.conversationSheetContent?.title?.isNotEmpty() == true + || groupParticipantsState.data.allCount > 1 ) } } From de9d18c7a659af2b5c30feb903b0ce3d124e7791 Mon Sep 17 00:00:00 2001 From: Mojtaba Chenani Date: Tue, 27 Aug 2024 08:36:32 +0200 Subject: [PATCH 4/4] extracted logic to a function and added tests --- .../conversation/ConversationSheetContent.kt | 2 + .../details/GroupConversationDetailsScreen.kt | 6 +- .../GroupConversationParticipants.kt | 4 +- .../ConversationSheetContentTest.kt | 91 +++++++++++++++++++ 4 files changed, 99 insertions(+), 4 deletions(-) create mode 100644 app/src/test/kotlin/com/wire/android/ui/common/bottomsheet/conversation/ConversationSheetContentTest.kt diff --git a/app/src/main/kotlin/com/wire/android/ui/common/bottomsheet/conversation/ConversationSheetContent.kt b/app/src/main/kotlin/com/wire/android/ui/common/bottomsheet/conversation/ConversationSheetContent.kt index 02445d535f5..8291470d512 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/bottomsheet/conversation/ConversationSheetContent.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/bottomsheet/conversation/ConversationSheetContent.kt @@ -152,4 +152,6 @@ data class ConversationSheetContent( fun canAddToFavourite(): Boolean = (conversationTypeDetail is ConversationTypeDetail.Private && conversationTypeDetail.blockingState != BlockingState.BLOCKED) || conversationTypeDetail is ConversationTypeDetail.Group + + fun isAbandonedOneOnOneConversation(participantsCount: Int): Boolean = title.isEmpty() && participantsCount == 1 } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsScreen.kt index 4d4d1edc944..100800fe8e5 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsScreen.kt @@ -395,8 +395,10 @@ private fun GroupConversationDetailsContent( onAddParticipantsPressed = onAddParticipantsPressed, onProfilePressed = onProfilePressed, lazyListState = lazyListStates[pageIndex], - allowAddMember = conversationSheetState.conversationSheetContent?.title?.isNotEmpty() == true - || groupParticipantsState.data.allCount > 1 + isAbandonedOneOnOneConversation = conversationSheetState.conversationSheetContent?.isAbandonedOneOnOneConversation( + groupParticipantsState.data.allCount + ) ?: false + ) } } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/participants/GroupConversationParticipants.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/participants/GroupConversationParticipants.kt index 219b9c9159c..6ecc81a210d 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/participants/GroupConversationParticipants.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/participants/GroupConversationParticipants.kt @@ -62,7 +62,7 @@ fun GroupConversationParticipants( onAddParticipantsPressed: () -> Unit, groupParticipantsState: GroupConversationParticipantsState, lazyListState: LazyListState = rememberLazyListState(), - allowAddMember: Boolean = true + isAbandonedOneOnOneConversation: Boolean = false ) { val context = LocalContext.current Column { @@ -87,7 +87,7 @@ fun GroupConversationParticipants( groupParticipantsState.data.allCount.toString() ) ) - if (groupParticipantsState.data.isSelfAnAdmin && allowAddMember) { + if (groupParticipantsState.data.isSelfAnAdmin && !isAbandonedOneOnOneConversation) { WirePrimaryButton( text = stringResource(R.string.conversation_details_group_participants_add), fillMaxWidth = true, diff --git a/app/src/test/kotlin/com/wire/android/ui/common/bottomsheet/conversation/ConversationSheetContentTest.kt b/app/src/test/kotlin/com/wire/android/ui/common/bottomsheet/conversation/ConversationSheetContentTest.kt new file mode 100644 index 00000000000..5b86e7dd5eb --- /dev/null +++ b/app/src/test/kotlin/com/wire/android/ui/common/bottomsheet/conversation/ConversationSheetContentTest.kt @@ -0,0 +1,91 @@ +/* + * 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.ui.common.bottomsheet.conversation + +import com.wire.android.ui.home.conversations.details.GroupConversationDetailsViewModelTest.Companion.testGroup +import com.wire.kalium.logic.data.conversation.Conversation +import com.wire.kalium.logic.data.id.TeamId +import kotlinx.coroutines.test.runTest +import org.amshove.kluent.internal.assertEquals +import org.junit.jupiter.api.Test + +class ConversationSheetContentTest { + + @Test + fun givenTitleIsEmptyAndTheGroupSizeIsOne_whenCallingIsTheGroupAbandoned_returnsTrue() = runTest { + val details = testGroup.copy(conversation = testGroup.conversation.copy(teamId = TeamId("team_id"))) + + val givenConversationSheetContent = ConversationSheetContent( + title = "", + conversationId = details.conversation.id, + mutingConversationState = details.conversation.mutedStatus, + conversationTypeDetail = ConversationTypeDetail.Group(details.conversation.id, details.isSelfUserCreator), + selfRole = Conversation.Member.Role.Member, + isTeamConversation = details.conversation.isTeamGroup(), + isArchived = false, + protocol = Conversation.ProtocolInfo.Proteus, + mlsVerificationStatus = Conversation.VerificationStatus.NOT_VERIFIED, + proteusVerificationStatus = Conversation.VerificationStatus.NOT_VERIFIED + ) + val givenParticipantsCount = 1 + + assertEquals(true, givenConversationSheetContent.isAbandonedOneOnOneConversation(givenParticipantsCount)) + } + + @Test + fun givenTitleIsEmptyAndTheGroupSizeIsGtOne_whenCallingIsTheGroupAbandoned_returnsFalse() = runTest { + val details = testGroup.copy(conversation = testGroup.conversation.copy(teamId = TeamId("team_id"))) + + val givenConversationSheetContent = ConversationSheetContent( + title = "", + conversationId = details.conversation.id, + mutingConversationState = details.conversation.mutedStatus, + conversationTypeDetail = ConversationTypeDetail.Group(details.conversation.id, details.isSelfUserCreator), + selfRole = Conversation.Member.Role.Member, + isTeamConversation = details.conversation.isTeamGroup(), + isArchived = false, + protocol = Conversation.ProtocolInfo.Proteus, + mlsVerificationStatus = Conversation.VerificationStatus.NOT_VERIFIED, + proteusVerificationStatus = Conversation.VerificationStatus.NOT_VERIFIED + ) + val givenParticipantsCount = 3 + + assertEquals(false, givenConversationSheetContent.isAbandonedOneOnOneConversation(givenParticipantsCount)) + } + + @Test + fun givenTitleIsNotEmptyAndTheGroupSizeIsOne_whenCallingIsTheGroupAbandoned_returnsFalse() = runTest { + val details = testGroup.copy(conversation = testGroup.conversation.copy(teamId = TeamId("team_id"))) + + val givenConversationSheetContent = ConversationSheetContent( + title = "notEmpty", + conversationId = details.conversation.id, + mutingConversationState = details.conversation.mutedStatus, + conversationTypeDetail = ConversationTypeDetail.Group(details.conversation.id, details.isSelfUserCreator), + selfRole = Conversation.Member.Role.Member, + isTeamConversation = details.conversation.isTeamGroup(), + isArchived = false, + protocol = Conversation.ProtocolInfo.Proteus, + mlsVerificationStatus = Conversation.VerificationStatus.NOT_VERIFIED, + proteusVerificationStatus = Conversation.VerificationStatus.NOT_VERIFIED + ) + val givenParticipantsCount = 3 + + assertEquals(false, givenConversationSheetContent.isAbandonedOneOnOneConversation(givenParticipantsCount)) + } +}