diff --git a/app/src/main/kotlin/com/wire/android/di/CoreLogicModule.kt b/app/src/main/kotlin/com/wire/android/di/CoreLogicModule.kt index f71d86aa684..8f6395a20fc 100644 --- a/app/src/main/kotlin/com/wire/android/di/CoreLogicModule.kt +++ b/app/src/main/kotlin/com/wire/android/di/CoreLogicModule.kt @@ -206,16 +206,6 @@ class UseCaseModule { fun provideGetServerConfigUserCase(@KaliumCoreLogic coreLogic: CoreLogic) = coreLogic.getGlobalScope().fetchServerConfigFromDeepLink - @ViewModelScoped - @Provides - fun provideFetchApiVersionUserCase(@KaliumCoreLogic coreLogic: CoreLogic) = - coreLogic.getGlobalScope().fetchApiVersion - - @ViewModelScoped - @Provides - fun provideObserveServerConfigUseCase(@KaliumCoreLogic coreLogic: CoreLogic) = - coreLogic.getGlobalScope().observeServerConfig - @ViewModelScoped @Provides fun provideUpdateApiVersionsUseCase(@KaliumCoreLogic coreLogic: CoreLogic) = diff --git a/app/src/main/kotlin/com/wire/android/migration/MigrationMapper.kt b/app/src/main/kotlin/com/wire/android/migration/MigrationMapper.kt index 7788bff7c2e..0b8762851ef 100644 --- a/app/src/main/kotlin/com/wire/android/migration/MigrationMapper.kt +++ b/app/src/main/kotlin/com/wire/android/migration/MigrationMapper.kt @@ -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 = diff --git a/app/src/main/kotlin/com/wire/android/migration/feature/MigrateActiveAccountsUseCase.kt b/app/src/main/kotlin/com/wire/android/migration/feature/MigrateActiveAccountsUseCase.kt index 29b8a5d7ffb..055000e9a0d 100644 --- a/app/src/main/kotlin/com/wire/android/migration/feature/MigrateActiveAccountsUseCase.kt +++ b/app/src/main/kotlin/com/wire/android/migration/feature/MigrateActiveAccountsUseCase.kt @@ -115,7 +115,11 @@ class MigrateActiveAccountsUseCase @Inject constructor( private suspend fun handleMissingData( serverConfig: ServerConfig, refreshToken: String, - ): Either = coreLogic.authenticationScope(serverConfig) { + ): Either = coreLogic.authenticationScope( + serverConfig, + // scala did not support proxy mode so we can pass null + proxyCredentials = null + ) { ssoLoginScope.getLoginSession(refreshToken) }.let { when (it) { diff --git a/app/src/main/kotlin/com/wire/android/migration/feature/MigrateServerConfigUseCase.kt b/app/src/main/kotlin/com/wire/android/migration/feature/MigrateServerConfigUseCase.kt index f2eb55a6690..1c68b612519 100644 --- a/app/src/main/kotlin/com/wire/android/migration/feature/MigrateServerConfigUseCase.kt +++ b/app/src/main/kotlin/com/wire/android/migration/feature/MigrateServerConfigUseCase.kt @@ -27,7 +27,7 @@ import com.wire.kalium.logic.StorageFailure import com.wire.kalium.logic.configuration.server.CommonApiVersionType import com.wire.kalium.logic.configuration.server.ServerConfig import com.wire.kalium.logic.failure.ServerConfigFailure -import com.wire.kalium.logic.feature.server.FetchApiVersionResult +import com.wire.kalium.logic.feature.auth.autoVersioningAuth.AutoVersionAuthScopeUseCase import com.wire.kalium.logic.feature.server.GetServerConfigResult import com.wire.kalium.logic.feature.server.StoreServerConfigResult import com.wire.kalium.logic.functional.Either @@ -66,13 +66,13 @@ class MigrateServerConfigUseCase @Inject constructor( } private suspend fun ServerConfig.Links.fetchApiVersionAndStore(): Either = - coreLogic.getGlobalScope().fetchApiVersion(this).let { // it also already stores the fetched config + // scala did not support proxy mode so we can pass null here + coreLogic.versionedAuthenticationScope(this)(null).let { // it also already stores the fetched config when (it) { - is FetchApiVersionResult.Success -> Either.Right(it.serverConfig) - FetchApiVersionResult.Failure.TooNewVersion -> Either.Left(ServerConfigFailure.NewServerVersion) - FetchApiVersionResult.Failure.UnknownServerVersion -> Either.Left(ServerConfigFailure.UnknownServerVersion) - is FetchApiVersionResult.Failure.Generic -> Either.Left(it.genericFailure) + is AutoVersionAuthScopeUseCase.Result.Failure.Generic -> Either.Left(it.genericFailure) + AutoVersionAuthScopeUseCase.Result.Failure.TooNewVersion -> Either.Left(ServerConfigFailure.NewServerVersion) + AutoVersionAuthScopeUseCase.Result.Failure.UnknownServerVersion -> Either.Left(ServerConfigFailure.UnknownServerVersion) + is AutoVersionAuthScopeUseCase.Result.Success -> Either.Right(it.authenticationScope.currentServerConfig()) } } - } diff --git a/app/src/main/kotlin/com/wire/android/model/ItemActionType.kt b/app/src/main/kotlin/com/wire/android/model/ItemActionType.kt new file mode 100644 index 00000000000..4afb13509ef --- /dev/null +++ b/app/src/main/kotlin/com/wire/android/model/ItemActionType.kt @@ -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 +} diff --git a/app/src/main/kotlin/com/wire/android/ui/WireActivityState.kt b/app/src/main/kotlin/com/wire/android/ui/WireActivityState.kt index db8e2921fa5..4916122814c 100644 --- a/app/src/main/kotlin/com/wire/android/ui/WireActivityState.kt +++ b/app/src/main/kotlin/com/wire/android/ui/WireActivityState.kt @@ -18,10 +18,11 @@ package com.wire.android.ui -sealed class WireActivityState { +sealed class +WireActivityState { - data class NavigationGraph(val startNavigationRoute: String, val navigationArguments: List): WireActivityState() - data class ClientUpdateRequired(val clientUpdateUrl: String): WireActivityState() - object ServerVersionNotSupported: WireActivityState() - object Loading: WireActivityState() + data class NavigationGraph(val startNavigationRoute: String, val navigationArguments: List) : WireActivityState() + data class ClientUpdateRequired(val clientUpdateUrl: String) : WireActivityState() + object ServerVersionNotSupported : WireActivityState() + object Loading : WireActivityState() } diff --git a/app/src/main/kotlin/com/wire/android/ui/WireActivityViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/WireActivityViewModel.kt index f66e31e1970..12ba8586963 100644 --- a/app/src/main/kotlin/com/wire/android/ui/WireActivityViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/WireActivityViewModel.kt @@ -186,7 +186,7 @@ class WireActivityViewModel @Inject constructor( } private fun observeUpdateAppState() { - viewModelScope.launch(dispatchers.io()) { + viewModelScope.launch { observeIfAppUpdateRequired(BuildConfig.VERSION_CODE) .distinctUntilChanged() .collect { diff --git a/app/src/main/kotlin/com/wire/android/ui/authentication/ServerTitle.kt b/app/src/main/kotlin/com/wire/android/ui/authentication/ServerTitle.kt index d8ce0da998f..d8524960afe 100644 --- a/app/src/main/kotlin/com/wire/android/ui/authentication/ServerTitle.kt +++ b/app/src/main/kotlin/com/wire/android/ui/authentication/ServerTitle.kt @@ -110,9 +110,8 @@ private fun ServerEnrollmentDialogContent( onDismiss: () -> Unit, onClick: () -> Unit, ) { - WireDialog( - title = stringResource(id = R.string.server_details_dialog_title), - text = LocalContext.current.resources.stringWithStyledArgs( + val text = if (serverLinks.apiProxy == null) { + LocalContext.current.resources.stringWithStyledArgs( R.string.server_details_dialog_body, MaterialTheme.wireTypography.body02, MaterialTheme.wireTypography.body02, @@ -120,7 +119,23 @@ private fun ServerEnrollmentDialogContent( argsColor = colorsScheme().onBackground, serverLinks.title, serverLinks.api - ), + ) + } else { + LocalContext.current.resources.stringWithStyledArgs( + R.string.server_details_dialog_body_with_proxy, + MaterialTheme.wireTypography.body02, + MaterialTheme.wireTypography.body02, + normalColor = colorsScheme().secondaryText, + argsColor = colorsScheme().onBackground, + serverLinks.title, + serverLinks.api, + serverLinks.apiProxy!!.host, + serverLinks.apiProxy!!.needsAuthentication.toString() + ) + } + WireDialog( + title = stringResource(id = R.string.server_details_dialog_title), + text = text, onDismiss = onDismiss, optionButton1Properties = WireDialogButtonProperties( stringResource(id = R.string.label_ok), diff --git a/app/src/main/kotlin/com/wire/android/ui/authentication/create/code/CreateAccountCodeViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/authentication/create/code/CreateAccountCodeViewModel.kt index 69011f6ed01..b2e1adf74e6 100644 --- a/app/src/main/kotlin/com/wire/android/ui/authentication/create/code/CreateAccountCodeViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/authentication/create/code/CreateAccountCodeViewModel.kt @@ -71,7 +71,8 @@ class CreateAccountCodeViewModel @Inject constructor( fun resendCode() { codeState = codeState.copy(loading = true) viewModelScope.launch { - val authScope = coreLogic.versionedAuthenticationScope(serverConfig)().let { + // create account does not support proxy yet + val authScope = coreLogic.versionedAuthenticationScope(serverConfig)(null).let { when (it) { is AutoVersionAuthScopeUseCase.Result.Success -> it.authenticationScope @@ -130,7 +131,8 @@ class CreateAccountCodeViewModel @Inject constructor( private fun onCodeContinue(onSuccess: () -> Unit) { codeState = codeState.copy(loading = true) viewModelScope.launch { - val authScope = coreLogic.versionedAuthenticationScope(serverConfig)().let { + // create account does not support proxy yet + val authScope = coreLogic.versionedAuthenticationScope(serverConfig)(null).let { when (it) { is AutoVersionAuthScopeUseCase.Result.Success -> it.authenticationScope diff --git a/app/src/main/kotlin/com/wire/android/ui/authentication/create/email/CreateAccountEmailViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/authentication/create/email/CreateAccountEmailViewModel.kt index 75fb10548db..5656091bdc5 100644 --- a/app/src/main/kotlin/com/wire/android/ui/authentication/create/email/CreateAccountEmailViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/authentication/create/email/CreateAccountEmailViewModel.kt @@ -33,8 +33,6 @@ import com.wire.kalium.logic.configuration.server.ServerConfig import com.wire.kalium.logic.feature.auth.ValidateEmailUseCase import com.wire.kalium.logic.feature.auth.autoVersioningAuth.AutoVersionAuthScopeUseCase import com.wire.kalium.logic.feature.register.RequestActivationCodeResult -import com.wire.kalium.logic.feature.server.FetchApiVersionResult -import com.wire.kalium.logic.feature.server.FetchApiVersionUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch import javax.inject.Inject @@ -44,7 +42,6 @@ import javax.inject.Inject class CreateAccountEmailViewModel @Inject constructor( savedStateHandle: SavedStateHandle, private val authServerConfigProvider: AuthServerConfigProvider, - private val fetchApiVersion: FetchApiVersionUseCase, private val validateEmail: ValidateEmailUseCase, @KaliumCoreLogic private val coreLogic: CoreLogic, ) : ViewModel() { @@ -69,25 +66,6 @@ class CreateAccountEmailViewModel @Inject constructor( fun onEmailContinue(onSuccess: () -> Unit) { emailState = emailState.copy(loading = true, continueEnabled = false) viewModelScope.launch { - fetchApiVersion(authServerConfigProvider.authServer.value).let { - when (it) { - is FetchApiVersionResult.Success -> {} - is FetchApiVersionResult.Failure.UnknownServerVersion -> { - emailState = emailState.copy(showServerVersionNotSupportedDialog = true) - return@launch - } - - is FetchApiVersionResult.Failure.TooNewVersion -> { - emailState = emailState.copy(showClientUpdateDialog = true) - return@launch - } - - is FetchApiVersionResult.Failure.Generic -> { - return@launch - } - } - } - val emailError = if (validateEmail(emailState.email.text.trim().lowercase())) CreateAccountEmailViewState.EmailError.None else CreateAccountEmailViewState.EmailError.TextFieldError.InvalidEmailError @@ -106,7 +84,7 @@ class CreateAccountEmailViewModel @Inject constructor( fun onTermsAccept(onSuccess: () -> Unit) { emailState = emailState.copy(loading = true, continueEnabled = false, termsDialogVisible = false, termsAccepted = true) viewModelScope.launch { - val authScope = coreLogic.versionedAuthenticationScope(serverConfig)().let { + val authScope = coreLogic.versionedAuthenticationScope(serverConfig)(null).let { when (it) { is AutoVersionAuthScopeUseCase.Result.Success -> it.authenticationScope diff --git a/app/src/main/kotlin/com/wire/android/ui/authentication/login/LoginState.kt b/app/src/main/kotlin/com/wire/android/ui/authentication/login/LoginState.kt index 578caa2d0c9..84eb7d9f42f 100644 --- a/app/src/main/kotlin/com/wire/android/ui/authentication/login/LoginState.kt +++ b/app/src/main/kotlin/com/wire/android/ui/authentication/login/LoginState.kt @@ -20,6 +20,7 @@ package com.wire.android.ui.authentication.login import androidx.compose.ui.text.input.TextFieldValue import com.wire.android.ui.common.dialogs.CustomServerDialogState +import com.wire.kalium.logic.data.auth.login.ProxyCredentials data class LoginState( val userIdentifier: TextFieldValue = TextFieldValue(""), @@ -36,7 +37,14 @@ data class LoginState( val loginError: LoginError = LoginError.None, val isProxyEnabled: Boolean = false, val customServerDialogState: CustomServerDialogState? = null, -) +) { + fun getProxyCredentials(): ProxyCredentials? = + if (proxyIdentifier.text.isNotBlank() && proxyPassword.text.isNotBlank()) { + ProxyCredentials(proxyIdentifier.text, proxyPassword.text) + } else { + null + } +} fun LoginState.updateEmailLoginEnabled() = copy( diff --git a/app/src/main/kotlin/com/wire/android/ui/authentication/login/LoginViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/authentication/login/LoginViewModel.kt index a0184f6d058..787b7e160c6 100644 --- a/app/src/main/kotlin/com/wire/android/ui/authentication/login/LoginViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/authentication/login/LoginViewModel.kt @@ -40,7 +40,6 @@ import com.wire.kalium.logic.data.user.UserId import com.wire.kalium.logic.feature.auth.AddAuthenticatedUserUseCase import com.wire.kalium.logic.feature.auth.AuthenticationResult import com.wire.kalium.logic.feature.auth.DomainLookupUseCase -import com.wire.kalium.logic.feature.auth.autoVersioningAuth.AutoVersionAuthScopeUseCase import com.wire.kalium.logic.feature.client.RegisterClientResult import com.wire.kalium.logic.feature.client.RegisterClientUseCase import dagger.hilt.android.lifecycle.HiltViewModel @@ -68,8 +67,6 @@ open class LoginViewModel @Inject constructor( } } - protected suspend fun authScope(): AutoVersionAuthScopeUseCase.Result = coreLogic.versionedAuthenticationScope(serverConfig)() - private val loginNavArgs: LoginNavArgs = savedStateHandle.navArgs() private val preFilledUserIdentifier: PreFilledUserIdentifierType = loginNavArgs.userHandle.let { if (it.isNullOrEmpty()) PreFilledUserIdentifierType.None else PreFilledUserIdentifierType.PreFilled(it) diff --git a/app/src/main/kotlin/com/wire/android/ui/authentication/login/email/LoginEmailViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/authentication/login/email/LoginEmailViewModel.kt index 85361e510b0..4fd15a6c2e3 100644 --- a/app/src/main/kotlin/com/wire/android/ui/authentication/login/email/LoginEmailViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/authentication/login/email/LoginEmailViewModel.kt @@ -36,7 +36,6 @@ import com.wire.android.ui.authentication.verificationcode.VerificationCodeState import com.wire.android.ui.common.textfield.CodeFieldValue import com.wire.android.util.dispatchers.DispatcherProvider import com.wire.kalium.logic.CoreLogic -import com.wire.kalium.logic.data.auth.login.ProxyCredentials import com.wire.kalium.logic.data.auth.verification.VerifiableAction import com.wire.kalium.logic.feature.auth.AddAuthenticatedUserUseCase import com.wire.kalium.logic.feature.auth.AuthenticationResult @@ -137,29 +136,27 @@ class LoginEmailViewModel @Inject constructor( private suspend fun resolveCurrentAuthScope(): AuthenticationScope? = coreLogic.versionedAuthenticationScope(serverConfig).invoke( - AutoVersionAuthScopeUseCase.ProxyAuthentication.UsernameAndPassword( - ProxyCredentials(loginState.proxyIdentifier.text, loginState.proxyPassword.text) - ) - ).let { - when (it) { - is AutoVersionAuthScopeUseCase.Result.Success -> it.authenticationScope - - is AutoVersionAuthScopeUseCase.Result.Failure.UnknownServerVersion -> { - updateEmailLoginError(LoginError.DialogError.ServerVersionNotSupported) - return null - } + loginState.getProxyCredentials() + ).let { + when (it) { + is AutoVersionAuthScopeUseCase.Result.Success -> it.authenticationScope + + is AutoVersionAuthScopeUseCase.Result.Failure.UnknownServerVersion -> { + updateEmailLoginError(LoginError.DialogError.ServerVersionNotSupported) + return null + } - is AutoVersionAuthScopeUseCase.Result.Failure.TooNewVersion -> { - updateEmailLoginError(LoginError.DialogError.ClientUpdateRequired) - return null - } + is AutoVersionAuthScopeUseCase.Result.Failure.TooNewVersion -> { + updateEmailLoginError(LoginError.DialogError.ClientUpdateRequired) + return null + } - is AutoVersionAuthScopeUseCase.Result.Failure.Generic -> { - updateEmailLoginError(LoginError.DialogError.GenericError(it.genericFailure)) - return null + is AutoVersionAuthScopeUseCase.Result.Failure.Generic -> { + updateEmailLoginError(LoginError.DialogError.GenericError(it.genericFailure)) + return null + } } } - } private suspend fun handleAuthenticationFailure(it: AuthenticationResult.Failure, authScope: AuthenticationScope) { when (it) { diff --git a/app/src/main/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOViewModel.kt index af0e111012c..0b8bc779420 100644 --- a/app/src/main/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOViewModel.kt @@ -90,7 +90,9 @@ class LoginSSOViewModel @Inject constructor( if (loginState.customServerDialogState != null) { authServerConfigProvider.updateAuthServer(loginState.customServerDialogState!!.serverLinks) - val authScope = coreLogic.versionedAuthenticationScope(loginState.customServerDialogState!!.serverLinks)().let { + // sso does not support proxy + // TODO: add proxy support + val authScope = coreLogic.versionedAuthenticationScope(loginState.customServerDialogState!!.serverLinks)(null).let { when (it) { is AutoVersionAuthScopeUseCase.Result.Failure.Generic, AutoVersionAuthScopeUseCase.Result.Failure.TooNewVersion, @@ -134,7 +136,9 @@ class LoginSSOViewModel @Inject constructor( val defaultAuthScope: AuthenticationScope = coreLogic.versionedAuthenticationScope( authServerConfigProvider.defaultServerLinks() - )().let { + // domain lockup does not support proxy + // TODO: add proxy support + )(null).let { when (it) { is AutoVersionAuthScopeUseCase.Result.Failure.Generic, AutoVersionAuthScopeUseCase.Result.Failure.TooNewVersion, @@ -168,7 +172,8 @@ class LoginSSOViewModel @Inject constructor( private fun ssoLoginWithCodeFlow() { viewModelScope.launch { val authScope = - authScope().let { + // sso does not support proxy + coreLogic.versionedAuthenticationScope(serverConfig)(null).let { when (it) { is AutoVersionAuthScopeUseCase.Result.Success -> it.authenticationScope @@ -207,7 +212,7 @@ class LoginSSOViewModel @Inject constructor( loginState = loginState.copy(ssoLoginLoading = true, loginError = LoginError.None).updateSSOLoginEnabled() viewModelScope.launch { val authScope = - authScope().let { + coreLogic.versionedAuthenticationScope(serverConfig)(null).let { when (it) { is AutoVersionAuthScopeUseCase.Result.Success -> it.authenticationScope diff --git a/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/OngoingCallScreen.kt b/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/OngoingCallScreen.kt index 85bf0c51f04..08fee2ef0e0 100644 --- a/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/OngoingCallScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/OngoingCallScreen.kt @@ -271,10 +271,14 @@ private fun OngoingCallContent( hideDoubleTapToast() FullScreenTile( selectedParticipant = selectedParticipantForFullScreen, - height = this@BoxWithConstraints.maxHeight - dimensions().spacing4x - ) { - shouldOpenFullScreen = !shouldOpenFullScreen - } + height = this@BoxWithConstraints.maxHeight - dimensions().spacing4x, + closeFullScreen = { + shouldOpenFullScreen = !shouldOpenFullScreen + }, + onBackButtonClicked = { + shouldOpenFullScreen = !shouldOpenFullScreen + } + ) } else { VerticalCallingPager( participants = participants, diff --git a/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/fullscreen/FullScreenTile.kt b/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/fullscreen/FullScreenTile.kt index f372fff0a49..1caadcf8213 100644 --- a/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/fullscreen/FullScreenTile.kt +++ b/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/fullscreen/FullScreenTile.kt @@ -17,6 +17,7 @@ */ package com.wire.android.ui.calling.ongoing.fullscreen +import androidx.activity.compose.BackHandler import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxWidth @@ -50,10 +51,15 @@ fun FullScreenTile( sharedCallingViewModel: SharedCallingViewModel = hiltViewModel(), selectedParticipant: SelectedParticipant, height: Dp, - onDoubleTap: (offset: Offset) -> Unit + closeFullScreen: (offset: Offset) -> Unit, + onBackButtonClicked: () -> Unit ) { var shouldShowDoubleTapToast by remember { mutableStateOf(false) } + BackHandler { + onBackButtonClicked() + } + sharedCallingViewModel.callState.participants.find { it.id == selectedParticipant.userId && it.clientId == selectedParticipant.clientId }?.let { @@ -64,7 +70,7 @@ fun FullScreenTile( .clipToBounds() .pointerInput(Unit) { detectTapGestures( - onDoubleTap = onDoubleTap + onDoubleTap = closeFullScreen ) } .height(height) @@ -114,6 +120,7 @@ fun PreviewFullScreenVideoCall() { FullScreenTile( selectedParticipant = SelectedParticipant(), height = 100.dp, - onDoubleTap = { } + closeFullScreen = {}, + onBackButtonClicked = {} ) } diff --git a/app/src/main/kotlin/com/wire/android/ui/common/dialogs/CustomServerDialog.kt b/app/src/main/kotlin/com/wire/android/ui/common/dialogs/CustomServerDialog.kt index a884f7266c8..aa130cbc332 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/dialogs/CustomServerDialog.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/dialogs/CustomServerDialog.kt @@ -86,6 +86,17 @@ internal fun CustomServerDialog( title = stringResource(id = R.string.custom_backend_dialog_body_backend_api), value = serverLinks.api ) + if (serverLinks.apiProxy != null) { + CustomServerPropertyInfo( + title = stringResource(id = R.string.custom_backend_dialog_body_backend_proxy_url), + value = serverLinks.apiProxy!!.host + ) + + CustomServerPropertyInfo( + title = stringResource(id = R.string.custom_backend_dialog_body_backend_proxy_authentication), + value = serverLinks.apiProxy!!.needsAuthentication.toString() + ) + } if (showDetails) { CustomServerPropertyInfo( title = stringResource(id = R.string.custom_backend_dialog_body_backend_websocket), diff --git a/app/src/main/kotlin/com/wire/android/ui/common/groupname/GroupMetadataState.kt b/app/src/main/kotlin/com/wire/android/ui/common/groupname/GroupMetadataState.kt index 0275832caea..394ea2db5d8 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/groupname/GroupMetadataState.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/groupname/GroupMetadataState.kt @@ -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 diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationMemberExt.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationMemberExt.kt index dde8953016b..9bf8c48b379 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationMemberExt.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationMemberExt.kt @@ -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.findUser(userId: UserId): User? = firstOrNull { it.id == userId } fun List.findUser(userId: UserId): MemberDetails? = firstOrNull { it.user.id == userId } @@ -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( diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsViewModel.kt index 1a9b4e65964..a76d4d5e61a 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsViewModel.kt @@ -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 @@ -135,6 +136,7 @@ class GroupConversationDetailsViewModel @Inject constructor( .distinctUntilChanged() val selfTeam = getSelfTeam().getOrNull() + val selfUser = observerSelfUser().first() combine( groupDetailsFlow, @@ -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(), @@ -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 ) ) @@ -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 ) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/options/GroupConversationOptions.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/options/GroupConversationOptions.kt index 98d9c3fcccd..220d7d0b04e 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/options/GroupConversationOptions.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/options/GroupConversationOptions.kt @@ -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 @@ -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 @@ -96,7 +97,7 @@ fun GroupConversationSettings( item { GroupNameItem( groupName = state.groupName, - canBeChanged = state.isUpdatingAllowed, + canBeChanged = state.isUpdatingNameAllowed, onClick = onEditGroupName, ) } @@ -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 @@ -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 = {} ), @@ -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"), diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/options/GroupConversationOptionsState.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/options/GroupConversationOptionsState.kt index fba1345b908..333cd1581f2 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/options/GroupConversationOptionsState.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/options/GroupConversationOptionsState.kt @@ -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 = "", @@ -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, 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..84527fba793 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 @@ -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, diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/participants/GroupConversationParticipantsState.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/participants/GroupConversationParticipantsState.kt index 5ae1254b151..57384dd6c23 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/participants/GroupConversationParticipantsState.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/participants/GroupConversationParticipantsState.kt @@ -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( diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/participants/model/ConversationParticipantsData.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/participants/model/ConversationParticipantsData.kt index e448122e6f6..b0024366c9f 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/participants/model/ConversationParticipantsData.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/participants/model/ConversationParticipantsData.kt @@ -23,7 +23,8 @@ data class ConversationParticipantsData( val participants: List = 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 = participants + admins diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/participants/usecase/ObserveParticipantsForConversationUseCase.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/participants/usecase/ObserveParticipantsForConversationUseCase.kt index 8b99ad3a2fd..a1feb2f62cb 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/participants/usecase/ObserveParticipantsForConversationUseCase.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/participants/usecase/ObserveParticipantsForConversationUseCase.kt @@ -69,13 +69,15 @@ class ObserveParticipantsForConversationUseCase @Inject constructor( fun List.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()) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/InternalContactSearchResultItem.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/InternalContactSearchResultItem.kt index af5bccd3e0d..5664fa3d2e3 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/InternalContactSearchResultItem.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/InternalContactSearchResultItem.kt @@ -29,6 +29,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.wire.android.appLogger import com.wire.android.model.Clickable +import com.wire.android.model.ItemActionType import com.wire.android.model.UserAvatarData import com.wire.android.ui.common.AddContactButton import com.wire.android.ui.common.ArrowRightIcon @@ -54,15 +55,18 @@ fun InternalContactSearchResultItem( onCheckChange: (Boolean) -> Unit, isAddedToGroup: Boolean, clickable: Clickable, + actionType: ItemActionType, modifier: Modifier = Modifier ) { RowItemTemplate( leadingIcon = { Row { - WireCheckbox( - checked = isAddedToGroup, - onCheckedChange = onCheckChange - ) + if (actionType.checkable) { + WireCheckbox( + checked = isAddedToGroup, + onCheckedChange = onCheckChange + ) + } UserProfileAvatar(avatarData) } }, @@ -87,15 +91,17 @@ fun InternalContactSearchResultItem( ) }, actions = { - Box( - modifier = Modifier - .wrapContentWidth() - .padding(end = 8.dp) - ) { - ArrowRightIcon(Modifier.align(Alignment.TopEnd)) + if (actionType.clickable) { + Box( + modifier = Modifier + .wrapContentWidth() + .padding(end = 8.dp) + ) { + ArrowRightIcon(Modifier.align(Alignment.TopEnd)) + } } }, - clickable = clickable, + clickable = clickable.copy(enabled = actionType.clickable), modifier = modifier ) } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/SearchAllPeopleScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/SearchAllPeopleScreen.kt index 793c4e7a731..0f6e6eb7b43 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/SearchAllPeopleScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/SearchAllPeopleScreen.kt @@ -42,6 +42,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.wire.android.R import com.wire.android.model.Clickable +import com.wire.android.model.ItemActionType import com.wire.android.ui.common.button.WireSecondaryButton import com.wire.android.ui.common.dimensions import com.wire.android.ui.common.progress.WireCircularProgressIndicator @@ -65,6 +66,7 @@ fun SearchAllPeopleScreen( contactsAddedToGroup: ImmutableSet, isLoading: Boolean, isSearchActive: Boolean, + actionType: ItemActionType, onChecked: (Boolean, Contact) -> Unit, onOpenUserProfile: (Contact) -> Unit, lazyListState: LazyListState = rememberLazyListState() @@ -85,7 +87,8 @@ fun SearchAllPeopleScreen( onOpenUserProfile = onOpenUserProfile, lazyListState = lazyListState, isSearchActive = isSearchActive, - isLoading = isLoading + isLoading = isLoading, + actionType = actionType, ) } } @@ -99,6 +102,7 @@ private fun SearchResult( publicSearchResult: ImmutableList, isLoading: Boolean, isSearchActive: Boolean, + actionType: ItemActionType, contactsAddedToGroup: ImmutableSet, onChecked: (Boolean, Contact) -> Unit, onOpenUserProfile: (Contact) -> Unit, @@ -125,6 +129,7 @@ private fun SearchResult( showAllItems = !isSearchActive || searchPeopleScreenState.contactsAllResultsCollapsed, onShowAllButtonClicked = searchPeopleScreenState::toggleShowAllContactsResult, onOpenUserProfile = onOpenUserProfile, + actionType = actionType, ) } @@ -149,6 +154,7 @@ private fun LazyListScope.internalSearchResults( searchQuery: String, contactsAddedToGroup: ImmutableSet, onChecked: (Boolean, Contact) -> Unit, + actionType: ItemActionType, isLoading: Boolean, contactSearchResult: ImmutableList, showAllItems: Boolean, @@ -169,7 +175,8 @@ private fun LazyListScope.internalSearchResults( searchResult = contactSearchResult, searchQuery = searchQuery, onShowAllButtonClicked = onShowAllButtonClicked, - onOpenUserProfile = onOpenUserProfile + onOpenUserProfile = onOpenUserProfile, + actionType = actionType, ) } } @@ -207,6 +214,7 @@ private fun LazyListScope.externalSearchResults( private fun LazyListScope.internalSuccessItem( searchTitle: String, showAllItems: Boolean, + actionType: ItemActionType, contactsAddedToGroup: ImmutableSet, onChecked: (Boolean, Contact) -> Unit, searchResult: ImmutableList, @@ -231,6 +239,7 @@ private fun LazyListScope.internalSuccessItem( connectionState = connectionState, isAddedToGroup = contactsAddedToGroup.contains(contact), onCheckChange = onClick, + actionType = actionType, clickable = remember { Clickable(enabled = true) { onOpenUserProfile(contact) } } ) } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/SearchPeopleRouter.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/SearchPeopleRouter.kt index 42110004980..48d5e946991 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/SearchPeopleRouter.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/SearchPeopleRouter.kt @@ -50,6 +50,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.TextFieldValue import androidx.hilt.navigation.compose.hiltViewModel import com.wire.android.R +import com.wire.android.model.ItemActionType import com.wire.android.ui.common.CollapsingTopBarScaffold import com.wire.android.ui.common.TabItem import com.wire.android.ui.common.WireTabRow @@ -84,7 +85,8 @@ fun SearchUsersAndServicesScreen( onOpenUserProfile: (Contact) -> Unit, onServiceClicked: (Contact) -> Unit, onClose: () -> Unit, - screenType: SearchPeopleScreenType + screenType: SearchPeopleScreenType, + actionType: ItemActionType, ) { val searchBarState = rememberSearchbarState() val scope = rememberCoroutineScope() @@ -166,7 +168,8 @@ fun SearchUsersAndServicesScreen( onOpenUserProfile = onOpenUserProfile, onContactChecked = onContactChecked, isSearchActive = isSearchActive, - isLoading = false // TODO: update correctly + isLoading = false, // TODO: update correctly + actionType = actionType, ) } @@ -186,7 +189,8 @@ fun SearchUsersAndServicesScreen( onContactChecked = onContactChecked, onOpenUserProfile = onOpenUserProfile, isSearchActive = isSearchActive, - isLoading = false // TODO: update correctly + isLoading = false, // TODO: update correctly + actionType = actionType, ) } } @@ -195,19 +199,21 @@ fun SearchUsersAndServicesScreen( } }, bottomBar = { - if (searchState.isGroupCreationContext) { - SelectParticipantsButtonsAlwaysEnabled( - count = selectedContacts.size, - mainButtonText = actionButtonTitle, - onMainButtonClick = onGroupSelectionSubmitAction - ) - } else { - if (pagerState.currentPage != SearchPeopleTabItem.SERVICES.ordinal) { - SelectParticipantsButtonsRow( - selectedParticipantsCount = selectedContacts.size, + if (actionType.checkable) { + if (searchState.isGroupCreationContext) { + SelectParticipantsButtonsAlwaysEnabled( + count = selectedContacts.size, mainButtonText = actionButtonTitle, onMainButtonClick = onGroupSelectionSubmitAction ) + } else { + if (pagerState.currentPage != SearchPeopleTabItem.SERVICES.ordinal) { + SelectParticipantsButtonsRow( + selectedParticipantsCount = selectedContacts.size, + mainButtonText = actionButtonTitle, + onMainButtonClick = onGroupSelectionSubmitAction + ) + } } } }, @@ -232,6 +238,7 @@ private fun SearchAllPeopleOrContactsScreen( contactsAddedToGroup: ImmutableSet, isLoading: Boolean, isSearchActive: Boolean, + actionType: ItemActionType, onOpenUserProfile: (Contact) -> Unit, onContactChecked: (Boolean, Contact) -> Unit, searchUserViewModel: SearchUserViewModel = hiltViewModel(), @@ -252,6 +259,7 @@ private fun SearchAllPeopleOrContactsScreen( onOpenUserProfile = onOpenUserProfile, lazyListState = lazyState, isSearchActive = isSearchActive, - isLoading = isLoading + isLoading = isLoading, + actionType = actionType, ) } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/adddembertoconversation/AddMembersSearchScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/adddembertoconversation/AddMembersSearchScreen.kt index 8f63ae0ee03..7b579a8dd5b 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/adddembertoconversation/AddMembersSearchScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/adddembertoconversation/AddMembersSearchScreen.kt @@ -32,6 +32,7 @@ import com.wire.android.ui.home.conversations.search.AddMembersSearchNavArgs import com.wire.android.ui.home.conversations.search.SearchPeopleScreenType import com.wire.android.ui.home.conversations.search.SearchUsersAndServicesScreen import com.wire.android.ui.home.conversations.search.SearchBarViewModel +import com.wire.android.model.ItemActionType import com.wire.android.ui.home.newconversation.model.Contact import com.wire.android.util.EMPTY import com.wire.kalium.logic.data.id.QualifiedID @@ -74,6 +75,7 @@ fun AddMembersSearchScreen( .let { navigator.navigate(NavigationCommand(it)) } }, screenType = SearchPeopleScreenType.CONVERSATION_DETAILS, - selectedContacts = addMembersToConversationViewModel.newGroupState.selectedContacts + selectedContacts = addMembersToConversationViewModel.newGroupState.selectedContacts, + actionType = ItemActionType.CHECK, ) } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/newconversation/NewConversationViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/home/newconversation/NewConversationViewModel.kt index e520c76a374..873daa8d556 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/newconversation/NewConversationViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/newconversation/NewConversationViewModel.kt @@ -35,11 +35,13 @@ import com.wire.kalium.logic.data.conversation.Conversation import com.wire.kalium.logic.data.conversation.ConversationOptions import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.data.user.UserId +import com.wire.kalium.logic.data.user.type.UserType import com.wire.kalium.logic.feature.conversation.CreateGroupConversationUseCase import com.wire.kalium.logic.feature.user.GetDefaultProtocolUseCase -import com.wire.kalium.logic.feature.user.IsSelfATeamMemberUseCase +import com.wire.kalium.logic.feature.user.GetSelfUserUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.collections.immutable.toImmutableSet +import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import javax.inject.Inject @@ -47,7 +49,7 @@ import javax.inject.Inject @HiltViewModel class NewConversationViewModel @Inject constructor( private val createGroupConversation: CreateGroupConversationUseCase, - private val isSelfATeamMember: IsSelfATeamMemberUseCase, + private val getSelfUser: GetSelfUserUseCase, getDefaultProtocol: GetDefaultProtocolUseCase ) : ViewModel() { @@ -65,8 +67,13 @@ class NewConversationViewModel @Inject constructor( init { viewModelScope.launch { - val isSelfTeamMember = isSelfATeamMember() - newGroupState = newGroupState.copy(isSelfTeamMember = isSelfTeamMember) + val selfUser = getSelfUser().first() + val isSelfTeamMember = selfUser.teamId != null + val isSelfExternalTeamMember = selfUser.userType == UserType.EXTERNAL + newGroupState = newGroupState.copy( + isSelfTeamMember = isSelfTeamMember, + isGroupCreatingAllowed = !isSelfExternalTeamMember + ) } } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/newconversation/search/NewConversationSearchPeopleScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/newconversation/search/NewConversationSearchPeopleScreen.kt index 9e002495fe0..fa7818d78cf 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/newconversation/search/NewConversationSearchPeopleScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/newconversation/search/NewConversationSearchPeopleScreen.kt @@ -31,6 +31,7 @@ import com.wire.android.ui.destinations.OtherUserProfileScreenDestination import com.wire.android.ui.home.conversations.search.SearchPeopleScreenType import com.wire.android.ui.home.conversations.search.SearchUsersAndServicesScreen import com.wire.android.ui.home.conversations.search.SearchBarViewModel +import com.wire.android.model.ItemActionType import com.wire.android.ui.home.newconversation.NewConversationViewModel import com.wire.android.ui.home.newconversation.common.NewConversationNavGraph import com.wire.android.util.EMPTY @@ -65,6 +66,10 @@ fun NewConversationSearchPeopleScreen( onClose = navigator::navigateBack, onServiceClicked = { }, screenType = SearchPeopleScreenType.NEW_CONVERSATION, - selectedContacts = newConversationViewModel.newGroupState.selectedUsers + selectedContacts = newConversationViewModel.newGroupState.selectedUsers, + actionType = when (newConversationViewModel.newGroupState.isGroupCreatingAllowed) { + true -> ItemActionType.CHECK_AND_CLICK + else -> ItemActionType.CLICK + } ) } diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 0a27a0ec731..4d8f017e478 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -210,6 +210,7 @@ Wire wird unabhängig geprüft und ist ISO-, CCPA-, DSGVO- und SOX-konform Team erstellen Backend-Name:\n%1$s\n\nBackend-URL:\n%2$s + Backend name:\n%1$s\n\nBackend URL:\n%2$s\n\nProxy-URL:\n%3$s\n\nProxy-Authentifizierung:\n%4$s Lokales Backend Willkommen in unserer neuen App 👋 Wir haben die App überarbeitet, um sie für alle benutzerfreundlicher zu machen.\n\nErfahren Sie mehr über die neu gestaltete App – zusätzliche Optionen und verbesserte Barrierefreiheit bei gleichbleibend hoher Sicherheit. diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2028a3618cb..218fda616d3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -226,6 +226,7 @@ Wire is independently audited and ISO, CCPA, GDPR, SOX-compliant Create a Team Backend name:\n%1$s\n\nBackend URL:\n%2$s + Backend name:\n%1$s\n\nBackend URL:\n%2$s\n\nProxy URL:\n%3$s\n\nProxy authentication:\n%4$s On-premises Backend Welcome To Our New Android App 👋 We rebuilt the app to make it more usable for everyone.\n\nFind out more about Wire’s redesigned app—new options and improved accessibility, with the same strong security. @@ -1013,6 +1014,8 @@ If you proceed, your client will be redirected to the following on-premises backend: Backend name: Backend URL: + Proxy URL: + Proxy authentication: Blacklist URL: Teams URL: Accounts URL: diff --git a/app/src/test/kotlin/com/wire/android/framework/TestUser.kt b/app/src/test/kotlin/com/wire/android/framework/TestUser.kt index 4e4cffafe57..1478c0667aa 100644 --- a/app/src/test/kotlin/com/wire/android/framework/TestUser.kt +++ b/app/src/test/kotlin/com/wire/android/framework/TestUser.kt @@ -46,7 +46,8 @@ object TestUser { previewPicture = UserAssetId("value", "domain"), completePicture = UserAssetId("value", "domain"), availabilityStatus = UserAvailabilityStatus.AVAILABLE, - supportedProtocols = setOf(SupportedProtocol.PROTEUS) + supportedProtocols = setOf(SupportedProtocol.PROTEUS), + userType = UserType.INTERNAL, ) val OTHER_USER = OtherUser( USER_ID.copy(value = "otherValue"), diff --git a/app/src/test/kotlin/com/wire/android/mapper/UIParticipantMapperTest.kt b/app/src/test/kotlin/com/wire/android/mapper/UIParticipantMapperTest.kt index b64b6b32990..c33e6700601 100644 --- a/app/src/test/kotlin/com/wire/android/mapper/UIParticipantMapperTest.kt +++ b/app/src/test/kotlin/com/wire/android/mapper/UIParticipantMapperTest.kt @@ -23,7 +23,6 @@ import com.wire.android.ui.home.conversations.details.participants.model.UIParti import com.wire.android.ui.home.conversations.handle import com.wire.android.ui.home.conversations.name import com.wire.android.ui.home.conversations.userId -import com.wire.android.ui.home.conversations.userType import com.wire.android.util.ui.WireSessionImageLoader import com.wire.kalium.logic.data.conversation.Conversation.Member import com.wire.kalium.logic.data.conversation.MemberDetails @@ -106,7 +105,7 @@ class UIParticipantMapperTest { && memberDetails.name == uiParticipant.name && memberDetails.handle == uiParticipant.handle && memberDetails.user.avatar(wireSessionImageLoader, connectionState) == uiParticipant.avatarData - && userTypeMapper.toMembership(memberDetails.userType) == uiParticipant.membership + && userTypeMapper.toMembership(memberDetails.user.userType) == uiParticipant.membership && memberDetails.user is SelfUser == uiParticipant.isSelf) } @@ -141,7 +140,8 @@ fun testSelfUser(i: Int): SelfUser = SelfUser( previewPicture = null, completePicture = null, availabilityStatus = UserAvailabilityStatus.NONE, - supportedProtocols = setOf(SupportedProtocol.PROTEUS) + supportedProtocols = setOf(SupportedProtocol.PROTEUS), + userType = UserType.INTERNAL, ) fun testOtherUser(i: Int): OtherUser = OtherUser( diff --git a/app/src/test/kotlin/com/wire/android/migration/MigrateServerConfigUseCaseTest.kt b/app/src/test/kotlin/com/wire/android/migration/MigrateServerConfigUseCaseTest.kt index 7509e8c1e8c..5b12c4da989 100644 --- a/app/src/test/kotlin/com/wire/android/migration/MigrateServerConfigUseCaseTest.kt +++ b/app/src/test/kotlin/com/wire/android/migration/MigrateServerConfigUseCaseTest.kt @@ -27,7 +27,8 @@ import com.wire.kalium.logic.CoreLogic import com.wire.kalium.logic.GlobalKaliumScope import com.wire.kalium.logic.StorageFailure import com.wire.kalium.logic.configuration.server.ServerConfig -import com.wire.kalium.logic.feature.server.FetchApiVersionResult +import com.wire.kalium.logic.feature.auth.AuthenticationScope +import com.wire.kalium.logic.feature.auth.autoVersioningAuth.AutoVersionAuthScopeUseCase import com.wire.kalium.logic.feature.server.GetServerConfigResult import com.wire.kalium.logic.feature.server.StoreServerConfigResult import com.wire.kalium.logic.functional.Either @@ -59,7 +60,6 @@ class MigrateServerConfigUseCaseTest { .arrange() val result = useCase() coVerify(exactly = 1) { arrangement.globalKaliumScope.storeServerConfig(expected.links, versionInfo) } - coVerify { arrangement.globalKaliumScope.fetchApiVersion(any()) wasNot Called } assert(result.isRight()) assertEquals(expected, (result as Either.Right).value) } @@ -69,10 +69,10 @@ class MigrateServerConfigUseCaseTest { val expected = Arrangement.serverConfig val (arrangement, useCase) = Arrangement() .withScalaServerConfig(ScalaServerConfig.Links(expected.links)) - .withFetchApiVersionResult(FetchApiVersionResult.Success(expected)) + .withCurrentServerConfig(expected) .arrange() + val result = useCase() - coVerify(exactly = 1) { arrangement.globalKaliumScope.fetchApiVersion(expected.links) } assert(result.isRight()) assertEquals(expected, (result as Either.Right).value) } @@ -84,11 +84,11 @@ class MigrateServerConfigUseCaseTest { val (arrangement, useCase) = Arrangement() .withScalaServerConfig(ScalaServerConfig.ConfigUrl(customConfigUrl)) .withFetchServerConfigFromDeepLinkResult(GetServerConfigResult.Success(expected.links)) - .withFetchApiVersionResult(FetchApiVersionResult.Success(expected)) + .withCurrentServerConfig(expected) .arrange() + val result = useCase() coVerify(exactly = 1) { arrangement.globalKaliumScope.fetchServerConfigFromDeepLink(customConfigUrl) } - coVerify(exactly = 1) { arrangement.globalKaliumScope.fetchApiVersion(expected.links) } assert(result.isRight()) assertEquals(expected, (result as Either.Right).value) } @@ -107,8 +107,10 @@ class MigrateServerConfigUseCaseTest { private class Arrangement { @MockK lateinit var coreLogic: CoreLogic + @MockK lateinit var scalaServerConfigDAO: ScalaServerConfigDAO + @MockK lateinit var globalKaliumScope: GlobalKaliumScope @@ -116,27 +118,37 @@ class MigrateServerConfigUseCaseTest { MigrateServerConfigUseCase(coreLogic, scalaServerConfigDAO) } + @MockK + lateinit var autoVersionAuthScopeUseCase: AutoVersionAuthScopeUseCase + + @MockK + lateinit var authScope: AuthenticationScope + init { MockKAnnotations.init(this, relaxUnitFun = true) every { coreLogic.getGlobalScope() } returns globalKaliumScope + every { coreLogic.versionedAuthenticationScope(any()) } returns autoVersionAuthScopeUseCase + coEvery { autoVersionAuthScopeUseCase(any()) } returns AutoVersionAuthScopeUseCase.Result.Success(authScope) + } + + fun withCurrentServerConfig(serverConfig: ServerConfig) = apply { + every { authScope.currentServerConfig() } returns serverConfig } fun withScalaServerConfig(scalaServerConfig: ScalaServerConfig): Arrangement { every { scalaServerConfigDAO.scalaServerConfig } returns scalaServerConfig return this } - fun withStoreServerConfigResult(result : StoreServerConfigResult): Arrangement { + + fun withStoreServerConfigResult(result: StoreServerConfigResult): Arrangement { coEvery { globalKaliumScope.storeServerConfig(any(), any()) } returns result return this } - fun withFetchServerConfigFromDeepLinkResult(result : GetServerConfigResult): Arrangement { + + fun withFetchServerConfigFromDeepLinkResult(result: GetServerConfigResult): Arrangement { coEvery { globalKaliumScope.fetchServerConfigFromDeepLink(any()) } returns result return this } - fun withFetchApiVersionResult(result : FetchApiVersionResult): Arrangement { - coEvery { globalKaliumScope.fetchApiVersion(any()) } returns result - return this - } fun arrange() = this to useCase diff --git a/app/src/test/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOViewModelTest.kt index e6e33468d51..3d4ca18db66 100644 --- a/app/src/test/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/authentication/login/sso/LoginSSOViewModelTest.kt @@ -139,7 +139,7 @@ class LoginSSOViewModelTest { authServerConfigProvider.updateAuthServer(newServerConfig(1).links) coEvery { - autoVersionAuthScopeUseCase() + autoVersionAuthScopeUseCase(null) } returns AutoVersionAuthScopeUseCase.Result.Success( authenticationScope ) diff --git a/app/src/test/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsViewModelTest.kt index 4e239c67838..4a0a102f04a 100644 --- a/app/src/test/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsViewModelTest.kt @@ -26,6 +26,7 @@ import com.wire.android.framework.TestUser import com.wire.android.mapper.testUIParticipant import com.wire.android.ui.common.bottomsheet.conversation.ConversationSheetContent import com.wire.android.ui.common.bottomsheet.conversation.ConversationTypeDetail +import com.wire.android.ui.home.conversations.details.options.GroupConversationOptionsState import com.wire.android.ui.home.conversations.details.participants.GroupConversationAllParticipantsNavArgs import com.wire.android.ui.home.conversations.details.participants.model.ConversationParticipantsData import com.wire.android.ui.home.conversations.details.participants.usecase.ObserveParticipantsForConversationUseCase @@ -38,6 +39,8 @@ import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.data.id.TeamId import com.wire.kalium.logic.data.message.SelfDeletionTimer import com.wire.kalium.logic.data.team.Team +import com.wire.kalium.logic.data.user.SelfUser +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 @@ -256,17 +259,26 @@ class GroupConversationDetailsViewModelTest { // When - Then assertEquals(details.conversation.name, viewModel.groupOptionsState.value.groupName) - assertEquals(conversationParticipantsData.isSelfAnAdmin, viewModel.groupOptionsState.value.isUpdatingAllowed) assertEquals(details.conversation.name, viewModel.groupOptionsState.value.groupName) assertEquals(details.conversation.isTeamGroup(), viewModel.groupOptionsState.value.areAccessOptionsAvailable) assertEquals( (details.conversation.isGuestAllowed() || details.conversation.isNonTeamMemberAllowed()), viewModel.groupOptionsState.value.isGuestAllowed ) + assertEquals( + conversationParticipantsData.isSelfAnAdmin && !conversationParticipantsData.isSelfExternalMember, + viewModel.groupOptionsState.value.isUpdatingNameAllowed + ) assertEquals( conversationParticipantsData.isSelfAnAdmin && details.conversation.teamId?.value == selfTeam.id, viewModel.groupOptionsState.value.isUpdatingGuestAllowed ) + assertEquals(conversationParticipantsData.isSelfAnAdmin, viewModel.groupOptionsState.value.isUpdatingServicesAllowed) + assertEquals(conversationParticipantsData.isSelfAnAdmin, viewModel.groupOptionsState.value.isUpdatingSelfDeletingAllowed) + assertEquals( + conversationParticipantsData.isSelfAnAdmin && details.conversation.isTeamGroup(), + viewModel.groupOptionsState.value.isUpdatingReadReceiptAllowed + ) } @Test @@ -296,7 +308,7 @@ class GroupConversationDetailsViewModelTest { } @Test - fun `when no guests and enabling services, use case is called with the correct values`() = runTest { + fun `when no guests allowed and enabling services, use case is called with the correct values`() = runTest { // Given val members = buildList { for (i in 1..5) { @@ -305,10 +317,18 @@ class GroupConversationDetailsViewModelTest { } val conversationParticipantsData = ConversationParticipantsData( participants = members.take(GroupConversationDetailsViewModel.MAX_NUMBER_OF_PARTICIPANTS), - allParticipantsCount = members.size + allParticipantsCount = members.size, ) - val details = testGroup + val details = testGroup.copy( + conversation = testGroup.conversation.copy( + accessRole = Conversation.defaultGroupAccessRoles.toMutableList().apply { + remove(Conversation.AccessRole.NON_TEAM_MEMBER) + remove(Conversation.AccessRole.GUEST) + }, + access = listOf() + ) + ) val (arrangement, viewModel) = GroupConversationDetailsViewModelArrangement() .withUpdateConversationAccessUseCaseReturns( @@ -334,7 +354,7 @@ class GroupConversationDetailsViewModelTest { } @Test - fun `when no guests and disable service dialog confirmed, then use case is called with the correct values`() = runTest { + fun `when no guests allowed and disable service dialog confirmed, then use case is called with the correct values`() = runTest { // Given val members = buildList { for (i in 1..5) { @@ -346,7 +366,15 @@ class GroupConversationDetailsViewModelTest { allParticipantsCount = members.size ) - val details = testGroup + val details = testGroup.copy( + conversation = testGroup.conversation.copy( + accessRole = Conversation.defaultGroupAccessRoles.toMutableList().apply { + remove(Conversation.AccessRole.NON_TEAM_MEMBER) + remove(Conversation.AccessRole.GUEST) + }, + access = listOf() + ) + ) val (arrangement, viewModel) = GroupConversationDetailsViewModelArrangement() .withUpdateConversationAccessUseCaseReturns( @@ -469,104 +497,110 @@ class GroupConversationDetailsViewModelTest { } } - @Test - fun `given user has no teamId and conversation no teamId, when init group options, then read receipt toggle is disabled`() = runTest { - // given - // when - val details = testGroup.copy(conversation = testGroup.conversation.copy(teamId = null)) + private fun testUpdatingAllowedFields( + isTeamGroup: Boolean = true, + isSelfAnAdmin: Boolean = true, + isSelfAMemberOfGroupOwnerTeam: Boolean = true, + selfUserType: UserType = UserType.INTERNAL, + assertResult: (GroupConversationOptionsState) -> Unit + ) = runTest { + val members = buildList { for (i in 1..5) { add(testUIParticipant(i)) } } + val conversationParticipantsData = ConversationParticipantsData( + participants = members.take(GroupConversationDetailsViewModel.MAX_NUMBER_OF_PARTICIPANTS), + allParticipantsCount = members.size, + isSelfAnAdmin = isSelfAnAdmin + ) + val details = testGroup.copy(conversation = testGroup.conversation.copy(teamId = if (isTeamGroup) TeamId("team_id") else null)) + val selfTeamId = if (isTeamGroup && isSelfAMemberOfGroupOwnerTeam) details.conversation.teamId else TeamId("other_team_id") + val self = TestUser.SELF_USER.copy(userType = selfUserType, teamId = selfTeamId) val (_, viewModel) = GroupConversationDetailsViewModelArrangement() - .withUpdateConversationReceiptModeReturningSuccess() .withConversationDetailUpdate(details) - .withSelfTeamUseCaseReturns(result = null) + .withConversationMembersUpdate(conversationParticipantsData) + .withObserveSelfUserReturns(self) + .withSelfTeamUseCaseReturns(selfTeamId?.let { Team(it.value, "team_name", "icon") }) .arrange() - - // then - assertEquals(false, viewModel.groupOptionsState.value.isUpdatingReadReceiptAllowed) + assertResult(viewModel.groupOptionsState.value) } @Test - fun `given user has no teamId, is admin and conversation has teamId, when init group options, then read receipt toggle is enabled`() = - runTest { - // given - val members = buildList { - for (i in 1..5) { - add(testUIParticipant(i)) - } - } - val conversationParticipantsData = ConversationParticipantsData( - participants = members.take(GroupConversationDetailsViewModel.MAX_NUMBER_OF_PARTICIPANTS), - allParticipantsCount = members.size, - isSelfAnAdmin = true - ) - val details = testGroup.copy(conversation = testGroup.conversation.copy(teamId = TeamId("team_id"))) - - // when - val (_, viewModel) = GroupConversationDetailsViewModelArrangement() - .withUpdateConversationReceiptModeReturningSuccess() - .withConversationDetailUpdate(details) - .withConversationMembersUpdate(conversationParticipantsData) - .withSelfTeamUseCaseReturns(result = null) - .arrange() - - // then - assertEquals(true, viewModel.groupOptionsState.value.isUpdatingReadReceiptAllowed) + fun `given user is admin and external team member, when init group options, then group name update is not allowed`() = + testUpdatingAllowedFields(isSelfAnAdmin = true, selfUserType = UserType.EXTERNAL) { + assertEquals(false, it.isUpdatingNameAllowed) } - @Test - fun `given user has no teamId, not admin and conversation has teamId, when init group options, then read receipt toggle is enabled`() = - runTest { - // given - val members = buildList { - for (i in 1..5) { - add(testUIParticipant(i)) - } - } - val conversationParticipantsData = ConversationParticipantsData( - participants = members.take(GroupConversationDetailsViewModel.MAX_NUMBER_OF_PARTICIPANTS), - allParticipantsCount = members.size, - isSelfAnAdmin = true - ) - val details = testGroup.copy(conversation = testGroup.conversation.copy(teamId = TeamId("team_id"))) - - // when - val (_, viewModel) = GroupConversationDetailsViewModelArrangement() - .withUpdateConversationReceiptModeReturningSuccess() - .withConversationDetailUpdate(details) - .withConversationMembersUpdate(conversationParticipantsData) - .withSelfTeamUseCaseReturns(result = null) - .arrange() - - // then - assertEquals(true, viewModel.groupOptionsState.value.isUpdatingReadReceiptAllowed) + fun `given user is admin and internal team member, when init group options, then group name update is allowed`() = + testUpdatingAllowedFields(isSelfAnAdmin = true, selfUserType = UserType.INTERNAL) { + assertEquals(true, it.isUpdatingNameAllowed) } - @Test - fun `given user has teamId, is admin and conversation teamId, when init group options, then read receipt toggle is enabled`() = - runTest { - // given - val members = buildList { - for (i in 1..5) { - add(testUIParticipant(i)) - } - } - val conversationParticipantsData = ConversationParticipantsData( - participants = members.take(GroupConversationDetailsViewModel.MAX_NUMBER_OF_PARTICIPANTS), - allParticipantsCount = members.size, - isSelfAnAdmin = true - ) - val details = testGroup.copy(conversation = testGroup.conversation.copy(teamId = TeamId("team_id"))) - val selfTeam = Team("team_id", "team_name", "icon") - - // when - val (_, viewModel) = GroupConversationDetailsViewModelArrangement() - .withUpdateConversationReceiptModeReturningSuccess() - .withConversationDetailUpdate(details) - .withConversationMembersUpdate(conversationParticipantsData) - .withSelfTeamUseCaseReturns(result = selfTeam) - .arrange() - - // then - assertEquals(true, viewModel.groupOptionsState.value.isUpdatingReadReceiptAllowed) + fun `given user is not admin and external team member, when init group options, then group name update is not allowed`() = + testUpdatingAllowedFields(isSelfAnAdmin = false, selfUserType = UserType.EXTERNAL) { + assertEquals(false, it.isUpdatingNameAllowed) + } + @Test + fun `given user is not admin and internal team member, when init group options, then group name update is not allowed`() = + testUpdatingAllowedFields(isSelfAnAdmin = false, selfUserType = UserType.INTERNAL) { + assertEquals(false, it.isUpdatingNameAllowed) + } + @Test + fun `given user is admin and member of group owner team, when init group options, then guests update is allowed`() = + testUpdatingAllowedFields(isSelfAnAdmin = true, isSelfAMemberOfGroupOwnerTeam = true) { + assertEquals(true, it.isUpdatingGuestAllowed) + } + @Test + fun `given user is admin and not member of group owner team, when init group options, then guests update is not allowed`() = + testUpdatingAllowedFields(isSelfAnAdmin = true, isSelfAMemberOfGroupOwnerTeam = false) { + assertEquals(false, it.isUpdatingGuestAllowed) + } + @Test + fun `given user is not admin and member of group owner team, when init group options, then guests update is not allowed`() = + testUpdatingAllowedFields(isSelfAnAdmin = false, isSelfAMemberOfGroupOwnerTeam = true) { + assertEquals(false, it.isUpdatingGuestAllowed) + } + @Test + fun `given user is not admin and not member of group owner team, when init group options, then guests update is not allowed`() = + testUpdatingAllowedFields(isSelfAnAdmin = false, isSelfAMemberOfGroupOwnerTeam = false) { + assertEquals(false, it.isUpdatingGuestAllowed) + } + @Test + fun `given user is admin, when init group options, then services update is allowed`() = + testUpdatingAllowedFields(isSelfAnAdmin = true) { + assertEquals(true, it.isUpdatingServicesAllowed) + } + @Test + fun `given user is not admin, when init group options, then services update is not allowed`() = + testUpdatingAllowedFields(isSelfAnAdmin = false) { + assertEquals(false, it.isUpdatingServicesAllowed) + } + @Test + fun `given user is admin, when init group options, then self deleting update is allowed`() = + testUpdatingAllowedFields(isSelfAnAdmin = true) { + assertEquals(true, it.isUpdatingSelfDeletingAllowed) + } + @Test + fun `given user is not admin, when init group options, then self deleting update is not allowed`() = + testUpdatingAllowedFields(isSelfAnAdmin = false) { + assertEquals(false, it.isUpdatingSelfDeletingAllowed) + } + @Test + fun `given user is admin and team group, when init group options, then read receipts update is allowed`() = + testUpdatingAllowedFields(isSelfAnAdmin = true, isTeamGroup = true) { + assertEquals(true, it.isUpdatingReadReceiptAllowed) + } + @Test + fun `given user is admin and not team group, when init group options, then read receipts update is not allowed`() = + testUpdatingAllowedFields(isSelfAnAdmin = true, isTeamGroup = false) { + assertEquals(false, it.isUpdatingReadReceiptAllowed) + } + @Test + fun `given user is not admin and team group, when init group options, then read receipts update is not allowed`() = + testUpdatingAllowedFields(isSelfAnAdmin = false, isTeamGroup = true) { + assertEquals(false, it.isUpdatingReadReceiptAllowed) + } + @Test + fun `given user is not admin and not team group, when init group options, then read receipts update is not allowed`() = + testUpdatingAllowedFields(isSelfAnAdmin = false, isTeamGroup = false) { + assertEquals(false, it.isUpdatingReadReceiptAllowed) } companion object { @@ -702,6 +736,10 @@ internal class GroupConversationDetailsViewModelArrangement { coEvery { updateConversationArchivedStatus(any(), any(), any()) } returns ArchiveStatusUpdateResult.Success } + suspend fun withObserveSelfUserReturns(user: SelfUser) = apply { + coEvery { observerSelfUser() } returns flowOf(user) + } + suspend fun withConversationDetailUpdate(conversationDetails: ConversationDetails) = apply { coEvery { observeConversationDetails(any()) } returns conversationDetailsChannel.consumeAsFlow() .map { ObserveConversationDetailsUseCase.Result.Success(it) } diff --git a/app/src/test/kotlin/com/wire/android/ui/home/newconversation/NewConversationViewModelArrangement.kt b/app/src/test/kotlin/com/wire/android/ui/home/newconversation/NewConversationViewModelArrangement.kt index 1c30b5ec750..fc65ca1e428 100644 --- a/app/src/test/kotlin/com/wire/android/ui/home/newconversation/NewConversationViewModelArrangement.kt +++ b/app/src/test/kotlin/com/wire/android/ui/home/newconversation/NewConversationViewModelArrangement.kt @@ -29,18 +29,20 @@ import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.data.id.TeamId import com.wire.kalium.logic.data.user.ConnectionState import com.wire.kalium.logic.data.user.OtherUser +import com.wire.kalium.logic.data.user.SelfUser import com.wire.kalium.logic.data.user.SupportedProtocol import com.wire.kalium.logic.data.user.UserAssetId import com.wire.kalium.logic.data.user.UserAvailabilityStatus import com.wire.kalium.logic.data.user.type.UserType import com.wire.kalium.logic.feature.conversation.CreateGroupConversationUseCase import com.wire.kalium.logic.feature.user.GetDefaultProtocolUseCase +import com.wire.kalium.logic.feature.user.GetSelfUserUseCase import com.wire.kalium.logic.feature.user.IsMLSEnabledUseCase -import com.wire.kalium.logic.feature.user.IsSelfATeamMemberUseCaseImpl import io.mockk.MockKAnnotations import io.mockk.coEvery import io.mockk.every import io.mockk.impl.annotations.MockK +import kotlinx.coroutines.flow.flowOf internal class NewConversationViewModelArrangement { init { @@ -60,7 +62,7 @@ internal class NewConversationViewModelArrangement { lateinit var isMLSEnabledUseCase: IsMLSEnabledUseCase @MockK - lateinit var isSelfTeamMember: IsSelfATeamMemberUseCaseImpl + lateinit var getSelfUserUseCase: GetSelfUserUseCase @MockK(relaxed = true) lateinit var onGroupCreated: (ConversationId) -> Unit @@ -137,6 +139,22 @@ internal class NewConversationViewModelArrangement { isProteusVerified = false, supportedProtocols = setOf(SupportedProtocol.PROTEUS) ) + + val SELF_USER = SelfUser( + TestUser.USER_ID, + name = "username", + handle = "handle", + email = "email", + phone = "phone", + accentId = 0, + teamId = TeamId("teamId"), + connectionStatus = ConnectionState.ACCEPTED, + previewPicture = UserAssetId("value", "domain"), + completePicture = UserAssetId("value", "domain"), + availabilityStatus = UserAvailabilityStatus.AVAILABLE, + userType = UserType.INTERNAL, + supportedProtocols = setOf(SupportedProtocol.PROTEUS), + ) } fun withSyncFailureOnCreatingGroup() = apply { @@ -155,8 +173,11 @@ internal class NewConversationViewModelArrangement { ) } - fun withIsSelfTeamMember(result: Boolean) = apply { - coEvery { isSelfTeamMember() } returns result + fun withGetSelfUser(isTeamMember: Boolean, userType: UserType = UserType.INTERNAL) = apply { + coEvery { getSelfUserUseCase() } returns flowOf(SELF_USER.copy( + teamId = if (isTeamMember) TeamId("teamId") else null, + userType = userType, + )) } fun withGuestEnabled(isGuestModeEnabled: Boolean) = apply { @@ -173,7 +194,7 @@ internal class NewConversationViewModelArrangement { fun arrange() = this to NewConversationViewModel( createGroupConversation = createGroupConversation, - isSelfATeamMember = isSelfTeamMember, + getSelfUser = getSelfUserUseCase, getDefaultProtocol = getDefaultProtocol ).also { it.groupOptionsState = groupOptionsState diff --git a/app/src/test/kotlin/com/wire/android/ui/home/newconversation/NewConversationViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/home/newconversation/NewConversationViewModelTest.kt index 73199955b5b..6ce5fce124a 100644 --- a/app/src/test/kotlin/com/wire/android/ui/home/newconversation/NewConversationViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/home/newconversation/NewConversationViewModelTest.kt @@ -26,6 +26,7 @@ import com.wire.kalium.logic.data.conversation.Conversation import com.wire.kalium.logic.data.conversation.ConversationOptions import com.wire.kalium.logic.data.user.SupportedProtocol import com.wire.kalium.logic.data.user.UserId +import com.wire.kalium.logic.data.user.type.UserType import io.mockk.coVerify import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.advanceUntilIdle @@ -43,7 +44,7 @@ class NewConversationViewModelTest { @Test fun `given sync failure, when creating group, then should update options state with connectivity error`() = runTest { val (arrangement, viewModel) = NewConversationViewModelArrangement() - .withIsSelfTeamMember(true) + .withGetSelfUser(isTeamMember = true) .withSyncFailureOnCreatingGroup() .arrange() @@ -56,7 +57,7 @@ class NewConversationViewModelTest { @Test fun `given unknown failure, when creating group, then should update options state with unknown error`() = runTest { val (arrangement, viewModel) = NewConversationViewModelArrangement() - .withIsSelfTeamMember(true) + .withGetSelfUser(isTeamMember = true) .withUnknownFailureOnCreatingGroup() .arrange() @@ -69,7 +70,7 @@ class NewConversationViewModelTest { @Test fun `given no failure, when creating group, then options state should have no error`() = runTest { val (arrangement, viewModel) = NewConversationViewModelArrangement() - .withIsSelfTeamMember(true) + .withGetSelfUser(isTeamMember = true) .arrange() viewModel.createGroup(arrangement.onGroupCreated) @@ -82,7 +83,7 @@ class NewConversationViewModelTest { fun `given create group conflicted backends error, when clicked on dismiss, then error should be cleaned`() = runTest { val (_, viewModel) = NewConversationViewModelArrangement() - .withIsSelfTeamMember(true) + .withGetSelfUser(isTeamMember = true) .withConflictingBackendsFailure() .arrange() @@ -94,7 +95,7 @@ class NewConversationViewModelTest { @Test fun `given self is not a team member, when creating group, then the group is created with the correct values`() = runTest { val (arrangement, viewModel) = NewConversationViewModelArrangement() - .withIsSelfTeamMember(false) + .withGetSelfUser(isTeamMember = false) .arrange() viewModel.createGroup(arrangement.onGroupCreated) @@ -121,7 +122,7 @@ class NewConversationViewModelTest { fun `given self is team member and guests are enabled, when creating group, then the group is created with the correct values`() = runTest { val (arrangement, viewModel) = NewConversationViewModelArrangement() - .withIsSelfTeamMember(true) + .withGetSelfUser(isTeamMember = true) .withServicesEnabled(false) .withGuestEnabled(true) .arrange() @@ -151,7 +152,7 @@ class NewConversationViewModelTest { // given val (_, viewModel) = NewConversationViewModelArrangement() .withDefaultProtocol(SupportedProtocol.MLS) - .withIsSelfTeamMember(true) + .withGetSelfUser(isTeamMember = true) .withServicesEnabled(false) .withGuestEnabled(true) .arrange() @@ -165,4 +166,29 @@ class NewConversationViewModelTest { result ) } + + @Test + fun `given self is external team member, when creating group, then creating group should not be allowed`() = runTest { + // given + val (_, viewModel) = NewConversationViewModelArrangement() + .withGetSelfUser(isTeamMember = true, userType = UserType.EXTERNAL) + .arrange() + advanceUntilIdle() + // when + val result = viewModel.newGroupState.isGroupCreatingAllowed + // then + assertEquals(false, result) + } + @Test + fun `given self is internal team member, when creating group, then creating group should be allowed`() = runTest { + // given + val (_, viewModel) = NewConversationViewModelArrangement() + .withGetSelfUser(isTeamMember = true, userType = UserType.INTERNAL) + .arrange() + advanceUntilIdle() + // when + val result = viewModel.newGroupState.isGroupCreatingAllowed + // then + assertEquals(true, result) + } }