diff --git a/app/src/main/kotlin/com/wire/android/di/ViewModelScoped.kt b/app/src/main/kotlin/com/wire/android/di/ViewModelScoped.kt index 0cba15fe843..0bb91bc8bee 100644 --- a/app/src/main/kotlin/com/wire/android/di/ViewModelScoped.kt +++ b/app/src/main/kotlin/com/wire/android/di/ViewModelScoped.kt @@ -60,7 +60,7 @@ fun scopedArgs(argsClass: KClass, argsContainer: SavedStateH @Suppress("BOUNDS_NOT_ALLOWED_IF_BOUNDED_BY_TYPE_PARAMETER") @Composable inline fun hiltViewModelScoped(arguments: R): S where T : ViewModel, T : S = when { - LocalInspectionMode.current -> ViewModelScopedPreviews.firstNotNullOf { it as S } + LocalInspectionMode.current -> ViewModelScopedPreviews.firstNotNullOf { it as? S } else -> hiltViewModelScoped(key = arguments.key, defaultArguments = Bundlizer.bundle(R::class.serializer(), arguments)) } diff --git a/app/src/main/kotlin/com/wire/android/ui/common/TextWithLearnMore.kt b/app/src/main/kotlin/com/wire/android/ui/common/TextWithLearnMore.kt new file mode 100644 index 00000000000..4f7ffb8bca1 --- /dev/null +++ b/app/src/main/kotlin/com/wire/android/ui/common/TextWithLearnMore.kt @@ -0,0 +1,86 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.android.ui.common + +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.TextLayoutResult +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.text.withStyle +import com.wire.android.R +import com.wire.android.ui.theme.WireTheme +import com.wire.android.util.CustomTabsHelper +import com.wire.android.util.ui.PreviewMultipleThemes +import com.wire.android.util.ui.toSpanStyle + +@Composable +fun TextWithLearnMore( + textAnnotatedString: AnnotatedString, + learnMoreLink: String, + onTextLayout: (TextLayoutResult) -> Unit = {}, + modifier: Modifier = Modifier, +) { + val context = LocalContext.current + val learnMoreText = stringResource(id = R.string.label_learn_more).replace(" ", "\u00A0") // non-breaking space + val learnMoreAnnotatedString = buildAnnotatedString { + append(learnMoreText) + addStyle( + style = SpanStyle( + color = MaterialTheme.colorScheme.primary, + textDecoration = TextDecoration.Underline + ), + start = 0, + end = learnMoreText.length + ) + addStringAnnotation( + tag = TAG_LEARN_MORE, + annotation = learnMoreLink, + start = 0, + end = learnMoreText.length + ) + } + val fullAnnotatedString = textAnnotatedString + AnnotatedString(" ") + learnMoreAnnotatedString + androidx.compose.foundation.text.ClickableText( + modifier = modifier, + text = fullAnnotatedString, + onTextLayout = onTextLayout, + onClick = { offset -> + fullAnnotatedString.getStringAnnotations(TAG_LEARN_MORE, offset, offset) + .firstOrNull()?.let { result -> CustomTabsHelper.launchUrl(context, result.item) } + }, + ) +} + +private const val TAG_LEARN_MORE = "tag_learn_more" + +@PreviewMultipleThemes +@Composable +fun PreviewTextWithLearnMore() = WireTheme { + TextWithLearnMore( + textAnnotatedString = buildAnnotatedString { + withStyle(toSpanStyle(typography().body01, colorsScheme().onBackground)) { append("This is text with a learn more link") } + }, + learnMoreLink = "https://www.wire.com", + ) +} diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt index eb95e4ed074..073c303e541 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt @@ -820,7 +820,8 @@ private fun ConversationScreenContent( onFailedMessageRetryClicked = onFailedMessageRetryClicked, onLinkClick = onLinkClick, selectedMessageId = selectedMessageId, - onNavigateToReplyOriginalMessage = onNavigateToReplyOriginalMessage + onNavigateToReplyOriginalMessage = onNavigateToReplyOriginalMessage, + interactionAvailability = messageComposerStateHolder.messageComposerViewState.value.interactionAvailability, ) }, onChangeSelfDeletionClicked = onChangeSelfDeletionClicked, @@ -890,7 +891,8 @@ fun MessageList( onFailedMessageCancelClicked: (String) -> Unit, onLinkClick: (String) -> Unit, selectedMessageId: String?, - onNavigateToReplyOriginalMessage: (UIMessage) -> Unit + onNavigateToReplyOriginalMessage: (UIMessage) -> Unit, + interactionAvailability: InteractionAvailability, ) { val prevItemCount = remember { mutableStateOf(lazyPagingMessages.itemCount) } LaunchedEffect(lazyPagingMessages.itemCount) { @@ -983,7 +985,8 @@ fun MessageList( onNavigateToReplyOriginalMessage(message) } ), - isSelectedMessage = (message.header.messageId == selectedMessageId) + isSelectedMessage = (message.header.messageId == selectedMessageId), + isInteractionAvailable = interactionAvailability == InteractionAvailability.ENABLED, ) } @@ -991,7 +994,8 @@ fun MessageList( message = message, onFailedMessageCancelClicked = onFailedMessageCancelClicked, onFailedMessageRetryClicked = onFailedMessageRetryClicked, - onSelfDeletingMessageRead = onSelfDeletingMessageRead + onSelfDeletingMessageRead = onSelfDeletingMessageRead, + isInteractionAvailable = interactionAvailability == InteractionAvailability.ENABLED, ) } } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/MessageItem.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/MessageItem.kt index 54886100840..902770e666b 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/MessageItem.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/MessageItem.kt @@ -120,7 +120,8 @@ fun MessageItem( shouldDisplayMessageStatus: Boolean = true, shouldDisplayFooter: Boolean = true, onReplyClickable: Clickable? = null, - isSelectedMessage: Boolean = false + isSelectedMessage: Boolean = false, + isInteractionAvailable: Boolean = true, ) { with(message) { val selfDeletionTimerState = rememberSelfDeletionTimer(header.messageStatus.expirationStatus) @@ -317,6 +318,7 @@ fun MessageItem( if (message.sendingFailed) { MessageSendFailureWarning( messageStatus = header.messageStatus.flowStatus as MessageFlowStatus.Failure.Send, + isInteractionAvailable = isInteractionAvailable, onRetryClick = remember { { onFailedMessageRetryClicked(header.messageId) } }, onCancelClick = remember { { onFailedMessageCancelClicked(header.messageId) } } ) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/MessageItemComponents.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/MessageItemComponents.kt index 2dd033dc669..458ea3f42dd 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/MessageItemComponents.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/MessageItemComponents.kt @@ -62,6 +62,7 @@ import kotlinx.collections.immutable.persistentMapOf @Composable internal fun MessageSendFailureWarning( messageStatus: MessageFlowStatus.Failure.Send, + isInteractionAvailable: Boolean, onRetryClick: () -> Unit, onCancelClick: () -> Unit ) { @@ -78,22 +79,24 @@ internal fun MessageSendFailureWarning( if (messageStatus is MessageFlowStatus.Failure.Send.Remotely) { OfflineBackendsLearnMoreLink() } - Row { - WireSecondaryButton( - text = stringResource(R.string.label_retry), - onClick = onRetryClick, - minSize = dimensions().buttonSmallMinSize, - minClickableSize = dimensions().buttonMinClickableSize, - fillMaxWidth = false - ) - HorizontalSpace.x8() - WireSecondaryButton( - text = stringResource(R.string.label_cancel), - onClick = onCancelClick, - minSize = dimensions().buttonSmallMinSize, - minClickableSize = dimensions().buttonMinClickableSize, - fillMaxWidth = false - ) + if (isInteractionAvailable) { + Row { + WireSecondaryButton( + text = stringResource(R.string.label_retry), + onClick = onRetryClick, + minSize = dimensions().buttonSmallMinSize, + minClickableSize = dimensions().buttonMinClickableSize, + fillMaxWidth = false + ) + HorizontalSpace.x8() + WireSecondaryButton( + text = stringResource(R.string.label_cancel), + onClick = onCancelClick, + minSize = dimensions().buttonSmallMinSize, + minClickableSize = dimensions().buttonMinClickableSize, + fillMaxWidth = false + ) + } } } } @@ -299,7 +302,15 @@ internal fun OfflineBackendsLearnMoreLink(context: Context = LocalContext.curren @Composable fun PreviewMessageSendFailureWarning() { WireTheme { - MessageSendFailureWarning(MessageFlowStatus.Failure.Send.Locally(false), {}, {}) + MessageSendFailureWarning(MessageFlowStatus.Failure.Send.Locally(false), true, {}, {}) + } +} + +@PreviewMultipleThemes +@Composable +fun PreviewMessageSendFailureWarningWithInteractionDisabled() { + WireTheme { + MessageSendFailureWarning(MessageFlowStatus.Failure.Send.Locally(false), false, {}, {}) } } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/SystemMessageItem.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/SystemMessageItem.kt index 8bdbe334d1d..94085de6d18 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/SystemMessageItem.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/SystemMessageItem.kt @@ -87,6 +87,7 @@ import kotlin.math.roundToInt fun SystemMessageItem( message: UIMessage.System, initiallyExpanded: Boolean = false, + isInteractionAvailable: Boolean = true, onFailedMessageRetryClicked: (String) -> Unit = {}, onFailedMessageCancelClicked: (String) -> Unit = {}, onSelfDeletingMessageRead: (UIMessage) -> Unit = {} @@ -214,6 +215,7 @@ fun SystemMessageItem( if (message.sendingFailed) { MessageSendFailureWarning( messageStatus = message.header.messageStatus.flowStatus as MessageFlowStatus.Failure.Send, + isInteractionAvailable = isInteractionAvailable, onRetryClick = remember { { onFailedMessageRetryClicked(message.header.messageId) } }, onCancelClick = remember { { onFailedMessageCancelClicked(message.header.messageId) } } ) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt index 2815888fc8c..f2a9b4f71ba 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt @@ -51,15 +51,18 @@ import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import com.sebaslogen.resaca.hilt.hiltViewModelScoped import com.wire.android.R +import com.wire.android.di.hiltViewModelScoped import com.wire.android.model.UserAvatarData import com.wire.android.ui.common.UserProfileAvatar import com.wire.android.ui.common.colorsScheme import com.wire.android.ui.common.dimensions import com.wire.android.ui.home.conversations.details.participants.model.UIParticipant +import com.wire.android.ui.home.conversations.typing.TypingIndicatorArgs import com.wire.android.ui.home.conversations.typing.TypingIndicatorViewModel +import com.wire.android.ui.home.conversations.typing.TypingIndicatorViewModelImpl import com.wire.android.ui.home.conversationslist.model.Membership +import com.wire.android.ui.theme.WireTheme import com.wire.android.ui.theme.wireTypography import com.wire.android.util.ui.PreviewMultipleThemes import com.wire.kalium.logic.data.id.ConversationId @@ -71,9 +74,11 @@ private const val ANIMATION_SPEED_MILLIS = 1_500 @Composable fun UsersTypingIndicatorForConversation( conversationId: ConversationId, - viewModel: TypingIndicatorViewModel = hiltViewModelScoped(conversationId), + viewModel: TypingIndicatorViewModel = hiltViewModelScoped( + TypingIndicatorArgs(conversationId) + ) ) { - UsersTypingIndicator(usersTyping = viewModel.usersTypingViewState.usersTyping) + UsersTypingIndicator(usersTyping = viewModel.state().usersTyping) } @Composable @@ -179,7 +184,7 @@ private fun HorizontalBouncingWritingPen( @PreviewMultipleThemes @Composable -fun PreviewUsersTypingOne() { +fun PreviewUsersTypingOne() = WireTheme { Column( modifier = Modifier .background(color = colorsScheme().background) @@ -210,7 +215,7 @@ fun PreviewUsersTypingOne() { @PreviewMultipleThemes @Composable -fun PreviewUsersTypingMoreThanOne() { +fun PreviewUsersTypingMoreThanOne() = WireTheme { Column( modifier = Modifier .background(color = colorsScheme().background) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/typing/TypingIndicatorViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/typing/TypingIndicatorViewModel.kt index 512f065bf8f..8c6289f375b 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/typing/TypingIndicatorViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/typing/TypingIndicatorViewModel.kt @@ -23,25 +23,31 @@ import androidx.compose.runtime.setValue import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.wire.android.ui.home.conversations.ConversationNavArgs +import com.wire.android.di.ScopedArgs +import com.wire.android.di.ViewModelScopedPreview +import com.wire.android.di.scopedArgs import com.wire.android.ui.home.conversations.usecase.ObserveUsersTypingInConversationUseCase -import com.wire.android.ui.navArgs import com.wire.kalium.logic.data.id.QualifiedID import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch +import kotlinx.serialization.Serializable import javax.inject.Inject +@ViewModelScopedPreview +interface TypingIndicatorViewModel { + fun state(): UsersTypingViewState = UsersTypingViewState() +} + @HiltViewModel -class TypingIndicatorViewModel @Inject constructor( +class TypingIndicatorViewModelImpl @Inject constructor( private val observeUsersTypingInConversation: ObserveUsersTypingInConversationUseCase, savedStateHandle: SavedStateHandle, -) : ViewModel() { - - private val conversationNavArgs: ConversationNavArgs = savedStateHandle.navArgs() - val conversationId: QualifiedID = conversationNavArgs.conversationId +) : TypingIndicatorViewModel, ViewModel() { - var usersTypingViewState by mutableStateOf(UsersTypingViewState()) - private set + private val args: TypingIndicatorArgs = savedStateHandle.scopedArgs() + val conversationId: QualifiedID = args.conversationId + private var usersTypingViewState by mutableStateOf(UsersTypingViewState()) + override fun state(): UsersTypingViewState = usersTypingViewState init { observeUsersTypingState() @@ -55,3 +61,9 @@ class TypingIndicatorViewModel @Inject constructor( } } } + +@Serializable +data class TypingIndicatorArgs(val conversationId: QualifiedID) : ScopedArgs { + override val key = "$ARGS_KEY:$conversationId" + companion object { const val ARGS_KEY = "TypingIndicatorArgsKey" } +} diff --git a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/MessageComposer.kt b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/MessageComposer.kt index 2b114c61ccd..004487da500 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/MessageComposer.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/MessageComposer.kt @@ -19,6 +19,7 @@ package com.wire.android.ui.home.messagecomposer import android.net.Uri +import androidx.annotation.StringRes import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -26,15 +27,16 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.material3.Divider import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector @@ -42,7 +44,9 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.TextLayoutResult import com.wire.android.R +import com.wire.android.ui.common.TextWithLearnMore import com.wire.android.ui.common.banner.SecurityClassificationBannerForConversation import com.wire.android.ui.common.bottomsheet.WireModalSheetState import com.wire.android.ui.common.colorsScheme @@ -68,6 +72,7 @@ import com.wire.kalium.logic.data.conversation.Conversation.TypingIndicatorMode import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.data.message.SelfDeletionTimer import com.wire.kalium.logic.feature.conversation.InteractionAvailability +import kotlin.math.roundToInt import kotlin.time.Duration @Composable @@ -89,12 +94,8 @@ fun MessageComposer( InteractionAvailability.BLOCKED_USER -> { DisabledInteractionMessageComposer( conversationId = conversationId, - warningText = LocalContext.current.resources.stringWithStyledArgs( + warningText = warningTextWithStyledArgs( R.string.label_system_message_blocked_user, - MaterialTheme.wireTypography.body01, - MaterialTheme.wireTypography.body02, - colorsScheme().secondaryText, - colorsScheme().onBackground, stringResource(id = R.string.member_name_you_label_titlecase) ), messageListContent = messageListContent @@ -103,25 +104,20 @@ fun MessageComposer( InteractionAvailability.DELETED_USER -> DisabledInteractionMessageComposer( conversationId = conversationId, - warningText = LocalContext.current.resources.stringWithStyledArgs( - R.string.label_system_message_user_not_available, - MaterialTheme.wireTypography.body01, - MaterialTheme.wireTypography.body02, - colorsScheme().secondaryText, - colorsScheme().onBackground, - ), + warningText = warningTextWithStyledArgs(R.string.label_system_message_user_not_available), messageListContent = messageListContent ) InteractionAvailability.UNSUPPORTED_PROTOCOL -> DisabledInteractionMessageComposer( conversationId = conversationId, - warningText = LocalContext.current.resources.stringWithStyledArgs( - R.string.label_system_message_unsupported_protocol, - MaterialTheme.wireTypography.body01, - MaterialTheme.wireTypography.body02, - colorsScheme().secondaryText, - colorsScheme().onBackground, - ), + warningText = warningTextWithStyledArgs(R.string.label_system_message_unsupported_protocol), + messageListContent = messageListContent + ) + + InteractionAvailability.LEGAL_HOLD -> DisabledInteractionMessageComposer( + conversationId = conversationId, + warningText = warningTextWithStyledArgs(R.string.legal_hold_system_message_interaction_disabled), + learnMoreLink = stringResource(id = R.string.url_legal_hold_learn_more), messageListContent = messageListContent ) @@ -158,10 +154,22 @@ fun MessageComposer( } } +@Composable +private fun warningTextWithStyledArgs(@StringRes stringResId: Int, vararg formatArgs: String) = + LocalContext.current.resources.stringWithStyledArgs( + stringResId = stringResId, + normalStyle = MaterialTheme.wireTypography.body01, + argsStyle = MaterialTheme.wireTypography.body02, + normalColor = colorsScheme().secondaryText, + argsColor = colorsScheme().onBackground, + formatArgs = formatArgs + ) + @Composable private fun DisabledInteractionMessageComposer( conversationId: ConversationId, warningText: AnnotatedString?, + learnMoreLink: String? = null, messageListContent: @Composable () -> Unit ) { Surface(color = colorsScheme().messageComposerBackgroundColor) { @@ -191,21 +199,36 @@ private fun DisabledInteractionMessageComposer( .padding(dimensions().spacing16x) ) { Icon( - imageVector = ImageVector.vectorResource(id = R.drawable.ic_conversation), + imageVector = ImageVector.vectorResource(id = R.drawable.ic_info), tint = MaterialTheme.colorScheme.onBackground, contentDescription = "", modifier = Modifier .padding(start = dimensions().spacing8x) - .size(dimensions().spacing12x) - ) - Text( - text = warningText, - style = MaterialTheme.wireTypography.body01, - maxLines = 1, - modifier = Modifier - .weight(weight = 1f, fill = false) - .padding(start = dimensions().spacing16x) + .alignBy { it.measuredHeight / 2 }, ) + val lineHeight = MaterialTheme.wireTypography.body01.lineHeight.value + var centerOfFirstLine by remember { mutableStateOf(lineHeight / 2f) } + val textModifier = Modifier + .weight(weight = 1f, fill = false) + .padding(start = dimensions().spacing16x) + .alignBy { centerOfFirstLine.roundToInt() } + val onTextLayout: (TextLayoutResult) -> Unit = { + centerOfFirstLine = if (it.lineCount == 0) 0f else ((it.getLineTop(0) + it.getLineBottom(0)) / 2) + } + if (learnMoreLink.isNullOrEmpty()) { + Text( + text = warningText, + modifier = textModifier, + onTextLayout = onTextLayout + ) + } else { + TextWithLearnMore( + textAnnotatedString = warningText, + learnMoreLink = learnMoreLink, + modifier = textModifier, + onTextLayout = onTextLayout + ) + } } } SecurityClassificationBannerForConversation(conversationId = conversationId) @@ -255,12 +278,30 @@ private fun BaseComposerPreview( @PreviewMultipleThemes @Composable -private fun UnsupportedProtocolComposerPreview() = WireTheme { +private fun PreviewMessageComposerDeletedUser() = WireTheme { + BaseComposerPreview(interactionAvailability = InteractionAvailability.DELETED_USER) +} + +@PreviewMultipleThemes +@Composable +private fun PreviewMessageComposerBlockedUser() = WireTheme { + BaseComposerPreview(interactionAvailability = InteractionAvailability.BLOCKED_USER) +} + +@PreviewMultipleThemes +@Composable +private fun PreviewMessageComposerUnsupportedProtocol() = WireTheme { BaseComposerPreview(interactionAvailability = InteractionAvailability.UNSUPPORTED_PROTOCOL) } @PreviewMultipleThemes @Composable -private fun EnabledComposerPreview() = WireTheme { +private fun PreviewMessageComposerLegalHold() = WireTheme { + BaseComposerPreview(interactionAvailability = InteractionAvailability.LEGAL_HOLD) +} + +@PreviewMultipleThemes +@Composable +private fun PreviewMessageComposerEnabled() = WireTheme { BaseComposerPreview(interactionAvailability = InteractionAvailability.ENABLED) } diff --git a/app/src/main/kotlin/com/wire/android/util/ui/StyledStringUtil.kt b/app/src/main/kotlin/com/wire/android/util/ui/StyledStringUtil.kt index c8f7c96e0b3..38bd19d142d 100644 --- a/app/src/main/kotlin/com/wire/android/util/ui/StyledStringUtil.kt +++ b/app/src/main/kotlin/com/wire/android/util/ui/StyledStringUtil.kt @@ -108,7 +108,7 @@ fun Resources.stringWithBoldArgs( } } -private fun toSpanStyle(textStyle: TextStyle, color: Color) = SpanStyle( +fun toSpanStyle(textStyle: TextStyle, color: Color) = SpanStyle( color = color, fontWeight = textStyle.fontWeight, fontSize = textStyle.fontSize, diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3d492a66983..2028a3618cb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1357,6 +1357,7 @@ Legal hold deactivated for %1$s. Legal hold is no longer active for this conversation. Legal hold is now active for this conversation. + You can’t write messages with this user, as they are not part of your team and one of you is subject to legal hold. Once legal hold is deactivated, you can write messages again. Conversation no longer verified diff --git a/app/src/test/kotlin/com/wire/android/ui/home/conversations/typing/TypingIndicatorViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/home/conversations/typing/TypingIndicatorViewModelTest.kt index 2f4cc8fb2b2..be9e5319fa3 100644 --- a/app/src/test/kotlin/com/wire/android/ui/home/conversations/typing/TypingIndicatorViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/home/conversations/typing/TypingIndicatorViewModelTest.kt @@ -20,14 +20,12 @@ package com.wire.android.ui.home.conversations.typing import androidx.lifecycle.SavedStateHandle import app.cash.turbine.test import com.wire.android.config.CoroutineTestExtension -import com.wire.android.config.NavigationTestExtension +import com.wire.android.config.ScopedArgsTestExtension +import com.wire.android.di.scopedArgs import com.wire.android.framework.TestConversation -import com.wire.android.ui.home.conversations.ConversationNavArgs import com.wire.android.ui.home.conversations.details.participants.model.UIParticipant import com.wire.android.ui.home.conversations.typing.TypingIndicatorViewModelTest.Arrangement.Companion.expectedUIParticipant import com.wire.android.ui.home.conversations.usecase.ObserveUsersTypingInConversationUseCase -import com.wire.android.ui.navArgs -import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.data.user.UserId import io.mockk.MockKAnnotations import io.mockk.coEvery @@ -43,7 +41,7 @@ import org.junit.jupiter.api.extension.ExtendWith @OptIn(ExperimentalCoroutinesApi::class) @ExtendWith(CoroutineTestExtension::class) -@ExtendWith(NavigationTestExtension::class) +@ExtendWith(ScopedArgsTestExtension::class) class TypingIndicatorViewModelTest { @Test @@ -72,8 +70,7 @@ class TypingIndicatorViewModelTest { init { MockKAnnotations.init(this, relaxUnitFun = true) - every { savedStateHandle.navArgs() } returns TestConversation.ID - every { savedStateHandle.navArgs() } returns ConversationNavArgs(conversationId = TestConversation.ID) + every { savedStateHandle.scopedArgs() } returns TypingIndicatorArgs(conversationId = TestConversation.ID) coEvery { observeUsersTypingInConversation(eq(TestConversation.ID)) } returns flowOf(emptyList()) } @@ -81,7 +78,7 @@ class TypingIndicatorViewModelTest { coEvery { observeUsersTypingInConversation(eq(TestConversation.ID)) } returns flowOf(usersTyping) } - fun arrange() = this to TypingIndicatorViewModel(observeUsersTypingInConversation, savedStateHandle) + fun arrange() = this to TypingIndicatorViewModelImpl(observeUsersTypingInConversation, savedStateHandle) companion object { val expectedUIParticipant = UIParticipant( diff --git a/kalium b/kalium index 1cfa01fb202..072542fc5c8 160000 --- a/kalium +++ b/kalium @@ -1 +1 @@ -Subproject commit 1cfa01fb2024a56a4494a757b174a056b3883acb +Subproject commit 072542fc5c84a3a4fd26d675a2cee16489163ae5