Skip to content

Commit b7c3673

Browse files
authored
fix(apps): consider protocol instead of feature flag for enabling apps interaction - cherry pick (WPB-21835) (#4430)
1 parent c149352 commit b7c3673

File tree

11 files changed

+164
-21
lines changed

11 files changed

+164
-21
lines changed

app/src/main/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsViewModel.kt

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import com.wire.android.ui.home.newconversation.channelaccess.ChannelAccessType
3030
import com.wire.android.ui.home.newconversation.channelaccess.ChannelAddPermissionType
3131
import com.wire.android.ui.home.newconversation.channelaccess.toUiEnum
3232
import com.wire.android.ui.navArgs
33+
import com.wire.android.util.debug.FeatureVisibilityFlags
3334
import com.wire.android.util.dispatchers.DispatcherProvider
3435
import com.wire.android.util.ui.UIText
3536
import com.wire.kalium.common.functional.getOrNull
@@ -126,6 +127,12 @@ class GroupConversationDetailsViewModel @Inject constructor(
126127
val channelPermissionType = groupDetails.getChannelPermissionType()
127128
val channelAccessType = groupDetails.getChannelAccessType()
128129

130+
// WPB-21835: Apps availability logic controlled by feature flag
131+
val isMLSConversation = groupDetails.conversation.protocol is Conversation.ProtocolInfo.MLS
132+
val isAppsAllowedForConversation = computeAppsEnabledStatus(groupDetails, isMLSConversation)
133+
val isUpdatingAppsAllowedForConversation =
134+
computeAppsAllowedStatus(canSelfPerformAdminTasks, isSelfInTeamThatOwnsConversation, isMLSConversation)
135+
129136
_isFetchingInitialData.value = false
130137

131138
updateState(
@@ -140,8 +147,8 @@ class GroupConversationDetailsViewModel @Inject constructor(
140147
isUpdatingNameAllowed = canSelfPerformAdminTasks && !isSelfExternalMember,
141148
isUpdatingGuestAllowed = canSelfPerformAdminTasks && isSelfInTeamThatOwnsConversation,
142149
isUpdatingChannelAccessAllowed = canSelfPerformAdminTasks && isSelfInTeamThatOwnsConversation,
143-
isAppsAllowed = groupDetails.conversation.isServicesAllowed(),
144-
isUpdatingAppsAllowed = canSelfPerformAdminTasks && isSelfInTeamThatOwnsConversation,
150+
isAppsAllowed = isAppsAllowedForConversation,
151+
isUpdatingAppsAllowed = isUpdatingAppsAllowedForConversation,
145152
isUpdatingReadReceiptAllowed = canSelfPerformAdminTasks && groupDetails.conversation.isTeamGroup(),
146153
isUpdatingSelfDeletingAllowed = canSelfPerformAdminTasks,
147154
mlsEnabled = isMLSEnabled(),
@@ -161,6 +168,37 @@ class GroupConversationDetailsViewModel @Inject constructor(
161168
}
162169
}
163170

171+
/**
172+
* Determine apps visibility based on feature flag and team settings
173+
* Or just should be protocol based in case of current logic
174+
*/
175+
private fun computeAppsAllowedStatus(
176+
canSelfPerformAdminTasks: Boolean,
177+
isSelfInTeamThatOwnsConversation: Boolean,
178+
isMLSConversation: Boolean
179+
) = if (FeatureVisibilityFlags.AppsBasedOnProtocol) {
180+
// current logic: based on protocol
181+
canSelfPerformAdminTasks && isSelfInTeamThatOwnsConversation && !isMLSConversation
182+
} else {
183+
// new logic: based on permissions
184+
canSelfPerformAdminTasks && isSelfInTeamThatOwnsConversation
185+
}
186+
187+
/**
188+
* Determine apps visibility based on feature flag and team settings
189+
* Or just should be protocol based in case of current logic
190+
*/
191+
private fun computeAppsEnabledStatus(
192+
groupDetails: ConversationDetails.Group,
193+
isMLSConversation: Boolean
194+
) = if (FeatureVisibilityFlags.AppsBasedOnProtocol) {
195+
// current logic: based on protocol (apps disabled for MLS)
196+
groupDetails.conversation.isServicesAllowed() && !isMLSConversation
197+
} else {
198+
// new logic: based on feature flags
199+
groupDetails.conversation.isServicesAllowed()
200+
}
201+
164202
fun shouldShowAddParticipantButton(): Boolean {
165203
val isSelfAdmin = groupParticipantsState.data.isSelfAnAdmin
166204
val isSelfGuest = groupParticipantsState.data.isSelfGuest

app/src/main/kotlin/com/wire/android/ui/home/conversations/details/updateappsaccess/UpdateAppsAccessViewModel.kt

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import androidx.lifecycle.ViewModel
2525
import androidx.lifecycle.viewModelScope
2626
import com.wire.android.ui.home.conversations.details.participants.usecase.ObserveParticipantsForConversationUseCase
2727
import com.wire.android.ui.navArgs
28+
import com.wire.android.util.debug.FeatureVisibilityFlags
2829
import com.wire.android.util.dispatchers.DispatcherProvider
2930
import com.wire.kalium.logic.data.conversation.Conversation
3031
import com.wire.kalium.logic.data.conversation.ConversationDetails
@@ -106,16 +107,54 @@ class UpdateAppsAccessViewModel @Inject constructor(
106107
(conversationDetails is ConversationDetails.Group.Channel && isTeamAdmin && isSelfInConversationTeam)
107108
val canSelfPerformAdminActions = isSelfAnAdmin || isSelfChannelTeamAdmin
108109

110+
// WPB-21835: Apps availability logic controlled by feature flag
111+
val isMLSConversation = conversationDetails.conversation.protocol is Conversation.ProtocolInfo.MLS
112+
val isAppAccessAllowed = computeAppsEnabledStatus(conversationDetails, isMLSConversation, isTeamAllowedToUseApps)
113+
val isUpdatingAppAccessAllowed =
114+
computeAppsAllowedStatus(canSelfPerformAdminActions, isMLSConversation, isTeamAllowedToUseApps)
115+
109116
updateAppsAccessState = updateAppsAccessState.copy(
110-
isAppAccessAllowed = conversationDetails.conversation.isServicesAllowed() && isTeamAllowedToUseApps,
111-
isUpdatingAppAccessAllowed = canSelfPerformAdminActions && isTeamAllowedToUseApps,
117+
isAppAccessAllowed = isAppAccessAllowed,
118+
isUpdatingAppAccessAllowed = isUpdatingAppAccessAllowed,
112119
isLoadingAppsOption = false,
113120
shouldShowDisableAppsConfirmationDialog = false
114121
)
115122
}
116123
}
117124
}
118125

126+
/**
127+
* Determine apps visibility based on feature flag and team settings
128+
* Or just should be protocol based in case of current logic
129+
*/
130+
private fun computeAppsEnabledStatus(
131+
conversationDetails: ConversationDetails,
132+
isMLSConversation: Boolean,
133+
isTeamAllowedToUseApps: Boolean
134+
) = if (FeatureVisibilityFlags.AppsBasedOnProtocol) {
135+
// New logic: based on protocol (apps disabled for MLS)o
136+
conversationDetails.conversation.isServicesAllowed() && !isMLSConversation
137+
} else {
138+
// Old logic: based on team settings and feature flags
139+
conversationDetails.conversation.isServicesAllowed() && isTeamAllowedToUseApps
140+
}
141+
142+
/**
143+
* Determine apps visibility based on feature flag and team settings
144+
* Or just should be protocol based in case of current logic
145+
*/
146+
private fun computeAppsAllowedStatus(
147+
canSelfPerformAdminActions: Boolean,
148+
isMLSConversation: Boolean,
149+
isTeamAllowedToUseApps: Boolean
150+
) = if (FeatureVisibilityFlags.AppsBasedOnProtocol) {
151+
// New logic: based on protocol
152+
canSelfPerformAdminActions && !isMLSConversation
153+
} else {
154+
// Old logic: based on permissions and team settings
155+
canSelfPerformAdminActions && isTeamAllowedToUseApps
156+
}
157+
119158
private data class CombineFour(
120159
val isAppsUsageAllowed: Boolean,
121160
val conversationDetails: ConversationDetails,

app/src/main/kotlin/com/wire/android/ui/home/conversations/search/adddembertoconversation/AddMembersSearchScreen.kt

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import com.wire.android.ui.home.conversations.search.AddMembersSearchNavArgs
3030
import com.wire.android.ui.home.conversations.search.SearchPeopleScreenType
3131
import com.wire.android.ui.home.conversations.search.SearchUsersAndAppsScreen
3232
import com.wire.android.ui.home.newconversation.model.Contact
33+
import com.wire.android.util.debug.FeatureVisibilityFlags
3334
import com.wire.kalium.logic.data.id.QualifiedID
3435
import com.wire.kalium.logic.data.user.BotService
3536

@@ -45,6 +46,10 @@ fun AddMembersSearchScreen(
4546
if (addMembersToConversationViewModel.newGroupState.isCompleted) {
4647
navigator.navigateBack()
4748
}
49+
50+
// WPB-21835: Apps tab visibility controlled by feature flag
51+
val isAppsTabVisible = computeAppsVisible(navArgs)
52+
4853
SearchUsersAndAppsScreen(
4954
searchTitle = stringResource(id = R.string.label_add_participants),
5055
onOpenUserProfile = { contact: Contact ->
@@ -61,9 +66,19 @@ fun AddMembersSearchScreen(
6166
},
6267
screenType = SearchPeopleScreenType.CONVERSATION_DETAILS,
6368
selectedContacts = addMembersToConversationViewModel.newGroupState.selectedContacts,
64-
isAppsTabVisible = navArgs.isSelfPartOfATeam,
69+
isAppsTabVisible = isAppsTabVisible,
6570
isUserAllowedToCreateChannels = false,
6671
shouldShowChannelPromotion = false,
6772
isConversationAppsEnabled = navArgs.isConversationAppsEnabled,
6873
)
6974
}
75+
76+
@Composable
77+
private fun computeAppsVisible(navArgs: AddMembersSearchNavArgs) =
78+
if (FeatureVisibilityFlags.AppsBasedOnProtocol) {
79+
// current logic: based on protocol (isConversationAppsEnabled represents non-MLS)
80+
navArgs.isConversationAppsEnabled
81+
} else {
82+
// new logic: based on team membership
83+
navArgs.isSelfPartOfATeam
84+
}

app/src/main/kotlin/com/wire/android/ui/home/conversations/search/apps/SearchAppsScreen.kt

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import com.wire.android.ui.common.rowitem.RowItemTemplate
4747
import com.wire.android.ui.common.upgradetoapps.UpgradeToGetAppsBanner
4848
import com.wire.android.ui.home.conversations.search.HighlightName
4949
import com.wire.android.ui.home.conversations.search.widget.SearchFailureBox
50+
import com.wire.android.util.debug.FeatureVisibilityFlags
5051
import com.wire.android.ui.home.conversationslist.model.Membership
5152
import com.wire.android.ui.home.newconversation.model.Contact
5253
import com.wire.android.ui.theme.WireTheme
@@ -158,11 +159,17 @@ private fun rememberAppsContentState(
158159
result: ImmutableList<Contact>
159160
): State<AppsContentState> = remember(isConversationAppsEnabled, isLoading, isTeamAllowedToUseApps, searchQuery, result) {
160161
derivedStateOf {
162+
// WPB-21835: Apps availability checks controlled by feature flag
163+
if (!FeatureVisibilityFlags.AppsBasedOnProtocol) {
164+
// new logic: check team and conversation settings first
165+
if (!isTeamAllowedToUseApps) return@derivedStateOf AppsContentState.TEAM_NOT_ALLOWED
166+
if (!isConversationAppsEnabled) return@derivedStateOf AppsContentState.APPS_NOT_ENABLED_FOR_CONVERSATION
167+
}
168+
// current logic: protocol-based, skip the above checks (screen shouldn't be accessible if apps disabled)
169+
161170
when {
162171
isLoading -> AppsContentState.LOADING
163-
!isTeamAllowedToUseApps -> AppsContentState.TEAM_NOT_ALLOWED
164-
!isConversationAppsEnabled -> AppsContentState.APPS_NOT_ENABLED_FOR_CONVERSATION
165-
searchQuery.isBlank() && result.isEmpty() -> AppsContentState.EMPTY_INITIAL
172+
searchQuery.isBlank() && result.isEmpty() -> AppsContentState.EMPTY_SEARCH
166173
searchQuery.isNotBlank() && result.isEmpty() -> AppsContentState.EMPTY_SEARCH
167174
else -> AppsContentState.SHOW_RESULTS
168175
}

app/src/main/kotlin/com/wire/android/ui/home/conversations/search/apps/SearchAppsViewModel.kt

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import com.wire.android.mapper.ContactMapper
2626
import com.wire.android.ui.common.DEFAULT_SEARCH_QUERY_DEBOUNCE
2727
import com.wire.android.ui.home.newconversation.model.Contact
2828
import com.wire.android.util.EMPTY
29+
import com.wire.android.util.debug.FeatureVisibilityFlags
2930
import com.wire.kalium.logic.data.user.type.isTeamAdmin
3031
import com.wire.kalium.logic.feature.featureConfig.ObserveIsAppsAllowedForUsageUseCase
3132
import com.wire.kalium.logic.feature.service.ObserveAllServicesUseCase
@@ -63,7 +64,8 @@ class SearchAppsViewModel @Inject constructor(
6364
isAppsAllowedForUsage(),
6465
searchQueryTextFlow.onStart { emit(String.EMPTY) }
6566
) { selfUser, isEnabled, query ->
66-
Triple(selfUser, isEnabled, query)
67+
val effectiveIsEnabled = computeAppsEnabledStatus(isEnabled)
68+
Triple(selfUser, effectiveIsEnabled, query)
6769
}.debounce(DEFAULT_SEARCH_QUERY_DEBOUNCE).collectLatest { (selfUser, isEnabled, query) ->
6870
state = state.copy(isTeamAllowedToUseApps = isEnabled, isSelfATeamAdmin = selfUser.userType.isTeamAdmin())
6971
if (isEnabled) {
@@ -75,6 +77,21 @@ class SearchAppsViewModel @Inject constructor(
7577
}
7678
}
7779

80+
/**
81+
* Determine apps visibility based on feature flag and team settings
82+
* Or just should be protocol based in case of current logic
83+
*/
84+
private fun computeAppsEnabledStatus(isEnabled: Boolean): Boolean {
85+
val effectiveIsEnabled = if (FeatureVisibilityFlags.AppsBasedOnProtocol) {
86+
// current logic: always enabled here (protocol check happens elsewhere)
87+
true
88+
} else {
89+
// new logic: use team feature flag
90+
isEnabled
91+
}
92+
return effectiveIsEnabled
93+
}
94+
7895
fun searchQueryChanged(searchQuery: String) {
7996
viewModelScope.launch {
8097
searchQueryTextFlow.emit(searchQuery)

app/src/main/kotlin/com/wire/android/ui/home/newconversation/NewConversationViewModel.kt

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import com.wire.android.ui.home.newconversation.channelhistory.ChannelHistoryTyp
3737
import com.wire.android.ui.home.newconversation.common.CreateGroupState
3838
import com.wire.android.ui.home.newconversation.groupOptions.GroupOptionState
3939
import com.wire.android.ui.home.newconversation.model.Contact
40+
import com.wire.android.util.debug.FeatureVisibilityFlags
4041
import com.wire.kalium.logic.data.conversation.Conversation
4142
import com.wire.kalium.logic.data.conversation.CreateConversationParam
4243
import com.wire.kalium.logic.data.user.UserId
@@ -82,7 +83,6 @@ class NewConversationViewModel @Inject constructor(
8283
var groupOptionsState: GroupOptionState by mutableStateOf(GroupOptionState())
8384
var isChannelCreationPossible: Boolean by mutableStateOf(true)
8485
var isFreemiumAccount: Boolean by mutableStateOf(false) // TODO: implement logic to determine if the account is freemium
85-
8686
var createGroupState: CreateGroupState by mutableStateOf(CreateGroupState.Default)
8787

8888
init {
@@ -96,14 +96,28 @@ class NewConversationViewModel @Inject constructor(
9696
viewModelScope.launch {
9797
observeIsAppsAllowedForUsage()
9898
.collectLatest { appsAllowed ->
99+
val isMLS = newGroupState.groupProtocol == CreateConversationParam.Protocol.MLS
100+
val isAppsAllowed = computeAppsAllowedStatus(isMLS, appsAllowed)
99101
groupOptionsState = groupOptionsState.copy(
100-
isTeamAllowedToUseApps = appsAllowed,
101-
isAllowAppsEnabled = appsAllowed
102+
isTeamAllowedToUseApps = isAppsAllowed,
103+
isAllowAppsEnabled = isAppsAllowed
102104
)
103105
}
104106
}
105107
}
106108

109+
/**
110+
* Determine apps visibility based on feature flag and team settings
111+
* Or just should be protocol based in case of current logic
112+
*/
113+
private fun computeAppsAllowedStatus(isMLS: Boolean, appsAllowed: Boolean) = if (FeatureVisibilityFlags.AppsBasedOnProtocol) {
114+
// current logic: based on protocol (apps disabled for MLS)
115+
!isMLS
116+
} else {
117+
// new logic: based on feature flags
118+
appsAllowed
119+
}
120+
107121
fun resetState() {
108122
newGroupNameTextState.clearText()
109123
newGroupState = GroupMetadataState().let {
@@ -178,9 +192,9 @@ class NewConversationViewModel @Inject constructor(
178192
} else {
179193
newGroupState = newGroupState.copy(
180194
selectedUsers = newGroupState.selectedUsers.filterNot {
181-
it.id == contact.id &&
182-
it.domain == contact.domain
183-
}.toImmutableSet()
195+
it.id == contact.id &&
196+
it.domain == contact.domain
197+
}.toImmutableSet()
184198
)
185199
}
186200
}

app/src/main/kotlin/com/wire/android/ui/home/newconversation/groupOptions/GroupOptionsScreen.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,9 @@ private fun GroupOptionState.GroupOptionsScreenMainContent(
220220
}
221221
}
222222
AllowGuestsOptions(groupMetadataState.isChannel, onAllowGuestChanged)
223-
AllowAppsOptions(onAllowServicesChanged)
223+
if (isTeamAllowedToUseApps) {
224+
AllowAppsOptions(onAllowServicesChanged)
225+
}
224226
if (groupMetadataState.groupProtocol != CreateConversationParam.Protocol.MLS || mlsReadReceiptsEnabled) {
225227
ReadReceiptsOptions(groupMetadataState.isChannel, onReadReceiptChanged)
226228
}

app/src/main/kotlin/com/wire/android/ui/home/newconversation/search/NewConversationSearchPeopleScreen.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import com.wire.android.ui.home.conversations.search.SearchPeopleScreenType
3333
import com.wire.android.ui.home.conversations.search.SearchUsersAndAppsScreen
3434
import com.wire.android.ui.home.newconversation.NewConversationViewModel
3535
import com.wire.android.ui.home.newconversation.common.NewConversationNavGraph
36+
import com.wire.kalium.logic.data.conversation.CreateConversationParam
3637
import com.wire.kalium.logic.data.id.QualifiedID
3738

3839
@NewConversationNavGraph(start = true)
@@ -72,7 +73,7 @@ fun NewConversationSearchPeopleScreen(
7273
onClose = navigator::navigateBack,
7374
screenType = SearchPeopleScreenType.NEW_CONVERSATION,
7475
selectedContacts = newConversationViewModel.newGroupState.selectedUsers,
75-
isAppsTabVisible = false,
76+
isAppsTabVisible = newConversationViewModel.newGroupState.groupProtocol != CreateConversationParam.Protocol.MLS,
7677
onAppClicked = { }
7778
)
7879

app/src/main/kotlin/com/wire/android/util/debug/FeatureVisibilityFlags.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,13 @@ object FeatureVisibilityFlags {
5656
const val MessageEditIcon = true
5757
const val SearchConversationMessages = true
5858
const val DrawingIcon = true
59+
60+
/**
61+
* WPB-21835: Controls how apps availability is determined.
62+
* When true: Apps are allowed based on protocol (disabled for MLS conversations)
63+
* When false: Apps are allowed based on feature flags (original logic with team settings)
64+
*/
65+
const val AppsBasedOnProtocol = true
5966
}
6067

6168
val LocalFeatureVisibilityFlags = staticCompositionLocalOf { FeatureVisibilityFlags }

app/src/test/kotlin/com/wire/android/ui/home/conversations/details/updateappsaccess/UpdateAppsAccessViewModelTest.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -253,8 +253,9 @@ class UpdateAppsAccessViewModelTest {
253253
advanceUntilIdle()
254254

255255
// Then
256-
assertEquals(false, viewModel.updateAppsAccessState.isUpdatingAppAccessAllowed)
257-
assertEquals(false, viewModel.updateAppsAccessState.isAppAccessAllowed)
256+
// WPB-21835: even if team does not allow apps, we still allow enabling/disabling apps based on protocol, later this will change
257+
assertEquals(true, viewModel.updateAppsAccessState.isUpdatingAppAccessAllowed)
258+
assertEquals(true, viewModel.updateAppsAccessState.isAppAccessAllowed)
258259
}
259260

260261
@Test

0 commit comments

Comments
 (0)