diff --git a/app/src/main/kotlin/com/wire/android/ui/authentication/devices/DeviceItem.kt b/app/src/main/kotlin/com/wire/android/ui/authentication/devices/DeviceItem.kt index 8a23a1b86b7..afccc77eb5a 100644 --- a/app/src/main/kotlin/com/wire/android/ui/authentication/devices/DeviceItem.kt +++ b/app/src/main/kotlin/com/wire/android/ui/authentication/devices/DeviceItem.kt @@ -42,6 +42,8 @@ import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.semantics.onClick +import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import com.wire.android.BuildConfig @@ -101,6 +103,8 @@ private fun DeviceItemContent( shouldShowE2EIInfo: Boolean, modifier: Modifier = Modifier, ) { + val openDetailsDescription = stringResource(id = R.string.content_description_user_profile_open_device_btn) + Row( verticalAlignment = Alignment.Top, modifier = modifier @@ -109,6 +113,9 @@ private fun DeviceItemContent( onClickAction?.invoke(device) } } + .semantics { + if (isWholeItemClickable) onClick(openDetailsDescription) { false } + } ) { Row( modifier = Modifier @@ -118,7 +125,7 @@ private fun DeviceItemContent( Icon( modifier = Modifier.shimmerPlaceholder(visible = placeholder), imageVector = ImageVector.vectorResource(id = R.drawable.ic_devices), - contentDescription = stringResource(R.string.content_description_remove_devices_screen_device_item_icon) + contentDescription = null ) Column( horizontalAlignment = Alignment.Start, @@ -199,7 +206,8 @@ private fun ColumnScope.DeviceItemTexts( ProteusVerifiedIcon( Modifier .wrapContentWidth() - .align(Alignment.CenterVertically)) + .align(Alignment.CenterVertically) + ) } } } diff --git a/app/src/main/kotlin/com/wire/android/ui/common/CopyButton.kt b/app/src/main/kotlin/com/wire/android/ui/common/CopyButton.kt index 4bcb2aa4e87..f0393a13de3 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/CopyButton.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/CopyButton.kt @@ -18,6 +18,7 @@ package com.wire.android.ui.common +import androidx.annotation.StringRes import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import com.wire.android.R @@ -27,13 +28,14 @@ import com.wire.android.ui.common.button.WireSecondaryIconButton @Composable fun CopyButton( onCopyClicked: () -> Unit, - state: WireButtonState = WireButtonState.Default, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, + @StringRes contentDescription: Int = R.string.content_description_copy, + state: WireButtonState = WireButtonState.Default ) { WireSecondaryIconButton( onButtonClicked = onCopyClicked, iconResource = R.drawable.ic_copy, - contentDescription = R.string.content_description_copy, + contentDescription = contentDescription, state = state, modifier = modifier ) diff --git a/app/src/main/kotlin/com/wire/android/ui/common/SearchBar.kt b/app/src/main/kotlin/com/wire/android/ui/common/SearchBar.kt index 527eb74113e..241cf8b59fe 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/SearchBar.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/SearchBar.kt @@ -59,7 +59,8 @@ fun SearchBarInput( placeholderAlignment: Alignment.Horizontal = Alignment.CenterHorizontally, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, textStyle: TextStyle = LocalTextStyle.current, - isLoading: Boolean = false + isLoading: Boolean = false, + semanticDescription: String? = null ) { WireTextField( @@ -108,6 +109,7 @@ fun SearchBarInput( placeholderAlignment = placeholderAlignment, placeholderText = placeholderText, lineLimits = TextFieldLineLimits.SingleLine, + semanticDescription = semanticDescription ) } diff --git a/app/src/main/kotlin/com/wire/android/ui/common/textfield/WireTextField.kt b/app/src/main/kotlin/com/wire/android/ui/common/textfield/WireTextField.kt index 1c6234ea6ac..0bf843388d9 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/textfield/WireTextField.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/textfield/WireTextField.kt @@ -71,6 +71,7 @@ internal fun WireTextField( labelText: String? = null, labelMandatoryIcon: Boolean = false, descriptionText: String? = null, + semanticDescription: String? = null, leadingIcon: @Composable (() -> Unit)? = null, trailingIcon: @Composable (() -> Unit)? = null, state: WireTextFieldState = WireTextFieldState.Default, @@ -115,6 +116,7 @@ internal fun WireTextField( labelText = labelText, labelMandatoryIcon = labelMandatoryIcon, descriptionText = descriptionText, + semanticDescription = semanticDescription, leadingIcon = leadingIcon, trailingIcon = trailingIcon, state = state, diff --git a/app/src/main/kotlin/com/wire/android/ui/common/textfield/WireTextFieldLayout.kt b/app/src/main/kotlin/com/wire/android/ui/common/textfield/WireTextFieldLayout.kt index 8d5b12612d2..a427da998b9 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/textfield/WireTextFieldLayout.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/textfield/WireTextFieldLayout.kt @@ -41,6 +41,7 @@ import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Shape import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.testTag +import androidx.compose.ui.semantics.clearAndSetSemantics import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.TextStyle @@ -55,6 +56,12 @@ import com.wire.android.ui.theme.wireTypography import com.wire.android.util.EMPTY import io.github.esentsov.PackagePrivate +/** + * Priority in which fields are used for SemanticContentDescription: + * [semanticDescription] -> [labelText] -> [placeholderText] -> [descriptionText]. + * If you need to make empty SemanticContentDescription (which is definitely bad idea for TextView) + * set [semanticDescription] = "" + */ @PackagePrivate @Composable internal fun WireTextFieldLayout( @@ -65,6 +72,7 @@ internal fun WireTextFieldLayout( labelText: String? = null, labelMandatoryIcon: Boolean = false, descriptionText: String? = null, + semanticDescription: String? = null, leadingIcon: @Composable (() -> Unit)? = null, trailingIcon: @Composable (() -> Unit)? = null, state: WireTextFieldState = WireTextFieldState.Default, @@ -101,8 +109,8 @@ internal fun WireTextFieldLayout( .fillMaxWidth() .background(color = colors.backgroundColor(state).value, shape = shape) .border(width = dimensions().spacing1x, color = colors.borderColor(state, interactionSource).value, shape = shape) - .semantics { - (labelText ?: placeholderText ?: descriptionText)?.let { + .semantics(mergeDescendants = true) { + (semanticDescription ?: labelText ?: placeholderText ?: descriptionText)?.let { contentDescription = it } } @@ -178,7 +186,9 @@ private fun InnerTextLayout( text = placeholderText, style = placeholderTextStyle, color = colors.placeholderColor(style).value, - modifier = Modifier.align(placeholderAlignment.toAlignment()) + modifier = Modifier + .align(placeholderAlignment.toAlignment()) + .clearAndSetSemantics {} ) } Box( diff --git a/app/src/main/kotlin/com/wire/android/ui/common/topappbar/search/SearchTopBar.kt b/app/src/main/kotlin/com/wire/android/ui/common/topappbar/search/SearchTopBar.kt index d098338d0e0..123a72f9995 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/topappbar/search/SearchTopBar.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/topappbar/search/SearchTopBar.kt @@ -71,6 +71,7 @@ fun SearchTopBar( searchQueryTextState: TextFieldState, modifier: Modifier = Modifier, isLoading: Boolean = false, + searchBarDescription: String? = null, onCloseSearchClicked: (() -> Unit)? = null, onActiveChanged: (isActive: Boolean) -> Unit = {}, bottomContent: @Composable ColumnScope.() -> Unit = {} @@ -107,6 +108,7 @@ fun SearchTopBar( SearchBarInput( placeholderText = searchBarHint, + semanticDescription = searchBarDescription, textState = searchQueryTextState, isLoading = isLoading, leadingIcon = { @@ -118,7 +120,7 @@ fun SearchTopBar( ) { Icon( painter = painterResource(R.drawable.ic_search), - contentDescription = stringResource(R.string.content_description_conversation_search_icon), + contentDescription = null, tint = MaterialTheme.wireColorScheme.onBackground, ) } @@ -129,7 +131,7 @@ fun SearchTopBar( ) { Icon( painter = rememberVectorPainter(image = Icons.Filled.ArrowBack), - contentDescription = stringResource(R.string.content_description_back_button), + contentDescription = stringResource(id = R.string.content_description_add_participants_back_btn), tint = MaterialTheme.wireColorScheme.onBackground, ) } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/media/ConversationMediaButton.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/media/ConversationMediaButton.kt index 0ba31e94ebc..d04d6a3ee70 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/media/ConversationMediaButton.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/media/ConversationMediaButton.kt @@ -43,10 +43,11 @@ fun ConversationMediaButton( leadingIcon = { Icon( painter = painterResource(id = R.drawable.ic_gallery), - contentDescription = stringResource(R.string.label_conversation_media), + contentDescription = null, tint = MaterialTheme.colorScheme.onBackground, modifier = Modifier.padding(end = dimensions().spacing8x) ) - } + }, + onClickDescription = stringResource(id = R.string.content_description_see_media_in_conversation_btn) ) } 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 d0dad094d0b..1109bb6a272 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 @@ -26,6 +26,10 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment.Companion.CenterVertically import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.semantics.stateDescription +import com.wire.android.R import com.wire.android.appLogger import com.wire.android.model.Clickable import com.wire.android.model.ItemActionType @@ -53,16 +57,15 @@ fun InternalContactSearchResultItem( membership: Membership, searchQuery: String, connectionState: ConnectionState, - onCheckChange: (Boolean) -> Unit, + onCheckClickable: Clickable, isSelected: Boolean, clickable: Clickable, actionType: ItemActionType, modifier: Modifier = Modifier ) { + val selectedDescription = stringResource(id = R.string.content_description_selected_label) RowItemTemplate( - leadingIcon = { - UserProfileAvatar(avatarData) - }, + leadingIcon = { UserProfileAvatar(avatarData) }, titleStartPadding = dimensions().spacing0x, title = { Row(verticalAlignment = CenterVertically) { @@ -101,14 +104,16 @@ fun InternalContactSearchResultItem( } }, clickable = - if (actionType.clickable) { - clickable - } else { - Clickable { - onCheckChange(!isSelected) - } - }, - modifier = modifier.padding(start = dimensions().spacing8x) + if (actionType.clickable) { + clickable + } else { + onCheckClickable + }, + modifier = modifier + .padding(start = dimensions().spacing8x) + .semantics { + if (actionType.checkable && isSelected) stateDescription = selectedDescription + } ) } @@ -155,15 +160,20 @@ fun ExternalContactSearchResultItem( when (connectionState) { ConnectionState.NOT_CONNECTED, ConnectionState.CANCELLED -> AddContactButton(userId, name) + ConnectionState.PENDING, ConnectionState.IGNORED -> Box(modifier = Modifier.padding(horizontal = dimensions().spacing12x)) { ConnectRequestBadge() } + ConnectionState.SENT -> Box(modifier = Modifier.padding(horizontal = dimensions().spacing12x)) { ConnectPendingRequestBadge() } + ConnectionState.BLOCKED -> { } + ConnectionState.MISSING_LEGALHOLD_CONSENT -> { appLogger.e("Unhandled ConnectionState.MISSING_LEGALHOLD_CONSENT in ExternalContactSearchResultItem") } + ConnectionState.ACCEPTED -> { appLogger.e("ConnectionState.ACCEPTED should not appear in ExternalContactSearchResultItem") } @@ -184,12 +194,13 @@ fun PreviewInternalContactSearchResultItemCheckable() = WireTheme { membership = Membership.None, searchQuery = "", connectionState = ConnectionState.ACCEPTED, - onCheckChange = {}, + onCheckClickable = Clickable {}, isSelected = false, clickable = Clickable {}, actionType = ItemActionType.CHECK, ) } + @PreviewMultipleThemes @Composable fun PreviewInternalContactSearchResultItemClickable() = WireTheme { @@ -200,7 +211,7 @@ fun PreviewInternalContactSearchResultItemClickable() = WireTheme { membership = Membership.None, searchQuery = "", connectionState = ConnectionState.ACCEPTED, - onCheckChange = {}, + onCheckClickable = Clickable {}, isSelected = false, clickable = Clickable {}, actionType = ItemActionType.CLICK, 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 d9cff9e3617..a1f22068e10 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 @@ -52,8 +52,8 @@ import com.wire.android.ui.home.newconversation.model.Contact import com.wire.android.ui.theme.WireTheme import com.wire.android.util.extension.FolderType import com.wire.android.util.extension.folderWithElements -import com.wire.android.util.ui.keepOnTopWhenNotScrolled import com.wire.android.util.ui.PreviewMultipleThemes +import com.wire.android.util.ui.keepOnTopWhenNotScrolled import com.wire.kalium.logic.data.user.ConnectionState import com.wire.kalium.logic.data.user.UserId import kotlinx.collections.immutable.ImmutableList @@ -287,7 +287,13 @@ private fun LazyListScope.internalSuccessItem( folderType = FolderType.Collapsing(expanded = expanded, onChanged = onExpansionChanged), ) { (contact, isSelected) -> with(contact) { - val onClick = remember { { isChecked: Boolean -> onChecked(isChecked, this) } } + val onCheckDescription = stringResource( + id = if (isSelected) R.string.content_description_unselect_label else R.string.content_description_select_label + ) + val clickDescription = stringResource(id = R.string.content_description_open_user_profile_label) + val onCheckClickable = remember(isSelected) { + Clickable(onClickDescription = onCheckDescription) { onChecked(!isSelected, this) } + } InternalContactSearchResultItem( avatarData = avatarData, name = name, @@ -296,9 +302,9 @@ private fun LazyListScope.internalSuccessItem( searchQuery = searchQuery, connectionState = connectionState, isSelected = isSelected, - onCheckChange = onClick, + onCheckClickable = onCheckClickable, actionType = actionType, - clickable = remember { Clickable(enabled = true) { onOpenUserProfile(contact) } } + clickable = remember { Clickable(onClickDescription = clickDescription) { onOpenUserProfile(contact) } } ) } } @@ -344,6 +350,7 @@ private fun LazyListScope.externalSuccessItem( folderType = FolderType.Collapsing(expanded = expanded, onChanged = onExpansionChanged), ) { contact -> with(contact) { + val clickDescription = stringResource(id = R.string.content_description_open_user_profile_label) ExternalContactSearchResultItem( avatarData = avatarData, userId = UserId(id, domain), @@ -352,7 +359,7 @@ private fun LazyListScope.externalSuccessItem( membership = membership, connectionState = connectionState, searchQuery = searchQuery, - clickable = remember { Clickable(enabled = true) { onOpenUserProfile(contact) } } + clickable = remember { Clickable(onClickDescription = clickDescription) { onOpenUserProfile(contact) } } ) } } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/SearchAllServicesScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/SearchAllServicesScreen.kt index 50885b115a9..a9d0a1aafec 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/SearchAllServicesScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/SearchAllServicesScreen.kt @@ -29,6 +29,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource import androidx.hilt.navigation.compose.hiltViewModel import com.wire.android.R import com.wire.android.model.Clickable @@ -110,6 +111,7 @@ private fun SuccessServicesList( folderWithElements( items = services.associateBy { it.id } ) { + val clickDescription = stringResource(id = R.string.content_description_open_service_label) RowItemTemplate( leadingIcon = { Row { @@ -132,7 +134,7 @@ private fun SuccessServicesList( } }, actions = {}, - clickable = remember(it) { Clickable(enabled = true) { onServiceClicked(it) } }, + clickable = remember(it) { Clickable(onClickDescription = clickDescription) { onServiceClicked(it) } }, modifier = Modifier.padding(start = dimensions().spacing8x) ) } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/SearchUsersAndServicesScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/SearchUsersAndServicesScreen.kt index e78efd42179..bcfd4e52bdd 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/SearchUsersAndServicesScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/SearchUsersAndServicesScreen.kt @@ -114,11 +114,14 @@ fun SearchUsersAndServicesScreen( elevation = dimensions().spacing0x, // CollapsingTopBarScaffold already manages elevation title = searchTitle, navigationIconType = when (screenType) { - SearchPeopleScreenType.CONVERSATION_DETAILS -> NavigationIconType.Close() + SearchPeopleScreenType.CONVERSATION_DETAILS -> + NavigationIconType.Close(R.string.content_description_add_participants_close) + SearchPeopleScreenType.NEW_CONVERSATION -> NavigationIconType.Close() SearchPeopleScreenType.NEW_GROUP_CONVERSATION -> NavigationIconType.Back() }, - onNavigationPressed = onClose + onNavigationPressed = onClose, + titleContentDescription = stringResource(id = R.string.content_description_add_participants_heading) ) } } @@ -127,6 +130,7 @@ fun SearchUsersAndServicesScreen( SearchTopBar( isSearchActive = searchBarState.isSearchActive, searchBarHint = stringResource(R.string.label_search_people), + searchBarDescription = stringResource(R.string.content_description_add_participants_search_field), searchQueryTextState = searchBarState.searchQueryTextState, onActiveChanged = searchBarState::searchActiveChanged, ) @@ -220,6 +224,7 @@ fun SearchUsersAndServicesScreen( enum class SearchPeopleTabItem(@StringRes val titleResId: Int) : TabItem { PEOPLE(R.string.label_add_member_people), SERVICES(R.string.label_add_member_services); + override val title: UIText = UIText.StringResource(titleResId) } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/messages/SearchConversationMessagesButton.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/messages/SearchConversationMessagesButton.kt index 4acbd545072..46f970d62be 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/messages/SearchConversationMessagesButton.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/messages/SearchConversationMessagesButton.kt @@ -58,10 +58,11 @@ private fun SearchConversationMessagesButtonContent( leadingIcon = { Icon( painter = painterResource(id = R.drawable.ic_search), - contentDescription = stringResource(R.string.label_search_messages), + contentDescription = null, tint = MaterialTheme.colorScheme.onBackground, modifier = Modifier.padding(end = dimensions().spacing8x) ) - } + }, + onClickDescription = stringResource(id = R.string.content_description_search_text_in_conversation_btn) ) } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/common/FolderHeader.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/common/FolderHeader.kt index 33d29e86937..a6e7a51de9a 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/common/FolderHeader.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/common/FolderHeader.kt @@ -35,6 +35,8 @@ import androidx.compose.ui.draw.rotate import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.semantics.onClick +import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.Dp import com.wire.android.R @@ -66,17 +68,19 @@ fun CollapsingFolderHeader( arrowHorizontalPadding: Dp = dimensions().avatarClickablePadding, ) { val arrowRotation: Float by animateFloatAsState(if (expanded) 180f else 90f, label = "CollapsingArrowRotationAnimation") + val expandDescription = stringResource( + id = if (expanded) R.string.content_description_collapse_label else R.string.content_description_expand_label + ) Row( verticalAlignment = Alignment.CenterVertically, modifier = modifier - .clickable { - onClicked(!expanded) - } + .semantics { onClick(expandDescription) { false } } + .clickable { onClicked(!expanded) } .padding(horizontal = dimensions().spacing8x, vertical = dimensions().spacing16x) ) { Icon( imageVector = ImageVector.vectorResource(R.drawable.ic_collapse), - contentDescription = stringResource(R.string.change), + contentDescription = null, tint = MaterialTheme.wireColorScheme.labelText, modifier = Modifier .padding(horizontal = arrowHorizontalPadding) diff --git a/app/src/main/kotlin/com/wire/android/ui/settings/devices/SelfDevicesScreen.kt b/app/src/main/kotlin/com/wire/android/ui/settings/devices/SelfDevicesScreen.kt index f7845dd9102..bda1155ac22 100644 --- a/app/src/main/kotlin/com/wire/android/ui/settings/devices/SelfDevicesScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/settings/devices/SelfDevicesScreen.kt @@ -159,7 +159,7 @@ private fun LazyListScope.folderDeviceItems( .background(MaterialTheme.wireColorScheme.surface), placeholder = placeholders, onClickAction = onDeviceClick, - icon = { ArrowRightIcon() }, + icon = { ArrowRightIcon(contentDescription = R.string.content_description_empty) }, isWholeItemClickable = true, shouldShowVerifyLabel = shouldShowVerifyLabel, isCurrentClient = isCurrentClient, diff --git a/app/src/main/kotlin/com/wire/android/ui/userprofile/other/OtherUserDevicesScreen.kt b/app/src/main/kotlin/com/wire/android/ui/userprofile/other/OtherUserDevicesScreen.kt index 4c1b655bf24..46eb1fda5c5 100644 --- a/app/src/main/kotlin/com/wire/android/ui/userprofile/other/OtherUserDevicesScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/userprofile/other/OtherUserDevicesScreen.kt @@ -119,7 +119,7 @@ private fun OtherUserDevicesContent( modifier = Modifier.background(MaterialTheme.wireColorScheme.surface), isWholeItemClickable = true, onClickAction = onDeviceClick, - icon = { ArrowRightIcon() }, + icon = { ArrowRightIcon(contentDescription = R.string.content_description_empty) }, shouldShowVerifyLabel = true, shouldShowE2EIInfo = item.mlsClientIdentity != null ) diff --git a/app/src/main/kotlin/com/wire/android/ui/userprofile/other/OtherUserProfileDetails.kt b/app/src/main/kotlin/com/wire/android/ui/userprofile/other/OtherUserProfileDetails.kt index 4adeb4c5c02..d8353245aa5 100644 --- a/app/src/main/kotlin/com/wire/android/ui/userprofile/other/OtherUserProfileDetails.kt +++ b/app/src/main/kotlin/com/wire/android/ui/userprofile/other/OtherUserProfileDetails.kt @@ -18,6 +18,7 @@ package com.wire.android.ui.userprofile.other +import androidx.annotation.StringRes import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn @@ -53,7 +54,8 @@ fun OtherUserProfileDetails( UserDetailInformation( title = stringResource(R.string.settings_myaccount_domain), value = state.userId.domain, - onCopy = null + onCopy = null, + copyBtnContentDescription = null ) } if (state.email.isNotEmpty()) { @@ -61,7 +63,8 @@ fun OtherUserProfileDetails( UserDetailInformation( title = stringResource(R.string.email_label), value = state.email, - onCopy = { otherUserProfileScreenState.copy(it, context) } + onCopy = { otherUserProfileScreenState.copy(it, context) }, + copyBtnContentDescription = R.string.content_description_user_profile_copy_email_btn ) } } @@ -70,7 +73,8 @@ fun OtherUserProfileDetails( UserDetailInformation( title = stringResource(R.string.phone_label), value = state.phone, - onCopy = { otherUserProfileScreenState.copy(it, context) } + onCopy = { otherUserProfileScreenState.copy(it, context) }, + copyBtnContentDescription = R.string.content_description_user_profile_copy_phone_btn ) } } @@ -81,7 +85,8 @@ fun OtherUserProfileDetails( private fun UserDetailInformation( title: String, value: String, - onCopy: ((String) -> Unit)? + onCopy: ((String) -> Unit)?, + @StringRes copyBtnContentDescription: Int? ) { RowItemTemplate( modifier = Modifier.padding(horizontal = dimensions().spacing8x), @@ -99,7 +104,14 @@ private fun UserDetailInformation( text = value ) }, - actions = { onCopy?.let { CopyButton(onCopyClicked = { onCopy(value) }) } }, + actions = { + onCopy?.let { + CopyButton( + onCopyClicked = { onCopy(value) }, + contentDescription = copyBtnContentDescription ?: R.string.content_description_copy + ) + } + }, clickable = Clickable(enabled = false) {} ) } diff --git a/app/src/main/kotlin/com/wire/android/ui/userprofile/other/OtherUserProfileGroup.kt b/app/src/main/kotlin/com/wire/android/ui/userprofile/other/OtherUserProfileGroup.kt index ec311425034..99223358fcd 100644 --- a/app/src/main/kotlin/com/wire/android/ui/userprofile/other/OtherUserProfileGroup.kt +++ b/app/src/main/kotlin/com/wire/android/ui/userprofile/other/OtherUserProfileGroup.kt @@ -167,7 +167,7 @@ fun EditButton(onEditClicked: () -> Unit, modifier: Modifier = Modifier) { WireSecondaryIconButton( onButtonClicked = onEditClicked, iconResource = R.drawable.ic_edit, - contentDescription = R.string.content_description_edit, + contentDescription = R.string.content_description_user_profile_edit_role_btn, modifier = modifier ) } diff --git a/app/src/main/kotlin/com/wire/android/ui/userprofile/other/OtherUserProfileScreen.kt b/app/src/main/kotlin/com/wire/android/ui/userprofile/other/OtherUserProfileScreen.kt index 1c84f02bb15..ce8ea215c78 100644 --- a/app/src/main/kotlin/com/wire/android/ui/userprofile/other/OtherUserProfileScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/userprofile/other/OtherUserProfileScreen.kt @@ -84,6 +84,7 @@ import com.wire.android.ui.common.snackbar.LocalSnackbarHostState import com.wire.android.ui.common.spacers.VerticalSpace import com.wire.android.ui.common.topappbar.NavigationIconType import com.wire.android.ui.common.topappbar.WireCenterAlignedTopAppBar +import com.wire.android.ui.common.topappbar.WireTopAppBarTitle import com.wire.android.ui.common.visbility.rememberVisibilityState import com.wire.android.ui.connection.ConnectionActionButton import com.wire.android.ui.destinations.ConversationMediaScreenDestination @@ -98,6 +99,7 @@ import com.wire.android.ui.legalhold.banner.LegalHoldSubjectBanner import com.wire.android.ui.legalhold.dialog.subject.LegalHoldSubjectProfileDialog import com.wire.android.ui.theme.WireTheme import com.wire.android.ui.theme.wireColorScheme +import com.wire.android.ui.theme.wireTypography import com.wire.android.ui.userprofile.common.EditableState import com.wire.android.ui.userprofile.common.UserProfileInfo import com.wire.android.ui.userprofile.group.RemoveConversationMemberState @@ -193,7 +195,6 @@ fun OtherUserProfileScreen( }, onSearchConversationMessagesClick = onSearchConversationMessagesClick, navigateBack = navigator::navigateBack, - navigationIconType = NavigationIconType.Close(), onConversationMediaClick = onConversationMediaClick, onLegalHoldLearnMoreClick = remember { { legalHoldSubjectDialogState.show(Unit) } }, ) @@ -222,7 +223,6 @@ fun OtherUserProfileScreen( fun OtherProfileScreenContent( scope: CoroutineScope, state: OtherUserProfileState, - navigationIconType: NavigationIconType, requestInProgress: Boolean, sheetState: WireModalSheetState, openBottomSheet: () -> Unit, @@ -299,7 +299,6 @@ fun OtherProfileScreenContent( topBarHeader = { TopBarHeader( state = state, - navigationIconType = navigationIconType, elevation = dimensions().spacing0x, // CollapsingTopBarScaffold already manages elevation onNavigateBack = navigateBack, openConversationBottomSheet = openConversationBottomSheet @@ -393,19 +392,32 @@ fun OtherProfileScreenContent( @Composable private fun TopBarHeader( state: OtherUserProfileState, - navigationIconType: NavigationIconType, elevation: Dp, onNavigateBack: () -> Unit, openConversationBottomSheet: () -> Unit ) { + val navigationIconType = if (state.groupState == null) { + NavigationIconType.Close() + } else { + NavigationIconType.Close(R.string.content_description_user_profile_close_btn) + } + WireCenterAlignedTopAppBar( onNavigationPressed = onNavigateBack, navigationIconType = navigationIconType, - title = stringResource(id = R.string.user_profile_title), + titleContent = { + WireTopAppBarTitle( + title = stringResource(id = R.string.user_profile_title), + style = MaterialTheme.wireTypography.title01, + maxLines = 2, + contentDescription = stringResource(id = R.string.content_description_user_profile_heading) + ) + }, elevation = elevation, actions = { if (state.conversationSheetContent != null) { MoreOptionIcon( + contentDescription = R.string.content_description_user_profile_more_btn, onButtonClicked = openConversationBottomSheet, state = if (state.isMetadataEmpty()) WireButtonState.Disabled else WireButtonState.Default ) @@ -610,7 +622,6 @@ fun PreviewOtherProfileScreenGroupMemberContent() { connectionState = ConnectionState.ACCEPTED, isUnderLegalHold = true, ), - navigationIconType = NavigationIconType.Back(), requestInProgress = false, sheetState = rememberWireModalSheetState(), openBottomSheet = {}, @@ -634,7 +645,6 @@ fun PreviewOtherProfileScreenContent() { isUnderLegalHold = true, groupState = null ), - navigationIconType = NavigationIconType.Back(), requestInProgress = false, sheetState = rememberWireModalSheetState(), openBottomSheet = {}, @@ -657,7 +667,6 @@ fun PreviewOtherProfileScreenContentNotConnected() { connectionState = ConnectionState.CANCELLED, isUnderLegalHold = true, ), - navigationIconType = NavigationIconType.Back(), requestInProgress = false, sheetState = rememberWireModalSheetState(), openBottomSheet = {}, @@ -682,7 +691,6 @@ fun PreviewOtherProfileScreenTempUser() { isUnderLegalHold = true, expiresAt = Instant.DISTANT_FUTURE ), - navigationIconType = NavigationIconType.Back(), requestInProgress = false, sheetState = rememberWireModalSheetState(), openBottomSheet = {}, 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 38bd19d142d..a203618c0dc 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 @@ -27,10 +27,13 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.LinkAnnotation import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.TextLinkStyles import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.text.withLink import androidx.compose.ui.text.withStyle import androidx.core.text.bold import androidx.core.text.buildSpannedString @@ -165,11 +168,16 @@ private fun createAnnotatedString(data: List): AnnotatedString { tag = linkTextData.tag, annotation = linkTextData.annotation, ) - withStyle( - style = SpanStyle( - color = MaterialTheme.wireColorScheme.primary, - textDecoration = TextDecoration.Underline, - ), + withLink( + LinkAnnotation.Url( + linkTextData.annotation, + TextLinkStyles( + style = SpanStyle( + color = MaterialTheme.wireColorScheme.primary, + textDecoration = TextDecoration.Underline, + ) + ) + ) ) { append(linkTextData.text) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index aeab4483e9e..d79b90f2240 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -186,8 +186,24 @@ Close conversation details adjust guest access adjust self-deleting time + User Profile, heading + Open conversation options + Go back to conversation details + Edit role + Copy phone + Copy email + open device details + Search text messages + Open overview of pictures and files + Add participant, heading + Close add partipants view + Search people by name or username + Go back to add participants view + collapse + expand edit select + unselect selected toggle setting Guests, heading @@ -195,6 +211,7 @@ Self-deleting messages, heading Go back to conversation details open profile + open service open notification settings diff --git a/core/ui-common/src/main/kotlin/com/wire/android/ui/common/button/WireButton.kt b/core/ui-common/src/main/kotlin/com/wire/android/ui/common/button/WireButton.kt index eba73bd9a11..00b8e95b805 100644 --- a/core/ui-common/src/main/kotlin/com/wire/android/ui/common/button/WireButton.kt +++ b/core/ui-common/src/main/kotlin/com/wire/android/ui/common/button/WireButton.kt @@ -49,6 +49,8 @@ import androidx.compose.ui.graphics.Shape import androidx.compose.ui.layout.layout import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.semantics.onClick +import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpSize @@ -88,7 +90,8 @@ fun WireButton( horizontal = MaterialTheme.wireDimensions.buttonHorizontalContentPadding, vertical = MaterialTheme.wireDimensions.buttonVerticalContentPadding ), - interactionSource: MutableInteractionSource = remember { MutableInteractionSource() } + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + onClickDescription: String? = null ) { val border = when { borderWidth > 0.dp -> BorderStroke(width = borderWidth, color = colors.outlineColor(state).value) @@ -120,7 +123,8 @@ fun WireButton( val centerY = ((height - placeable.height) / 2f).roundToInt() placeable.place(centerX, centerY) } - }, + } + .semantics { onClickDescription?.let { onClick(it) { false } } }, enabled = state != WireButtonState.Disabled, interactionSource = interactionSource, elevation = elevation, diff --git a/core/ui-common/src/main/kotlin/com/wire/android/ui/common/button/WirePrimaryButton.kt b/core/ui-common/src/main/kotlin/com/wire/android/ui/common/button/WirePrimaryButton.kt index cc854a4a5d1..48b08791764 100644 --- a/core/ui-common/src/main/kotlin/com/wire/android/ui/common/button/WirePrimaryButton.kt +++ b/core/ui-common/src/main/kotlin/com/wire/android/ui/common/button/WirePrimaryButton.kt @@ -70,7 +70,8 @@ fun WirePrimaryButton( horizontal = MaterialTheme.wireDimensions.buttonHorizontalContentPadding, vertical = MaterialTheme.wireDimensions.buttonVerticalContentPadding ), - interactionSource: MutableInteractionSource = remember { MutableInteractionSource() } + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + onClickDescription: String? = null ) = WireButton( onClick = onClick, loading = loading, @@ -91,7 +92,8 @@ fun WirePrimaryButton( borderWidth = borderWidth, contentPadding = contentPadding, interactionSource = interactionSource, - modifier = modifier + modifier = modifier, + onClickDescription = onClickDescription ) @Preview(name = "Default WirePrimaryButton") diff --git a/core/ui-common/src/main/kotlin/com/wire/android/ui/common/button/WireSecondaryButton.kt b/core/ui-common/src/main/kotlin/com/wire/android/ui/common/button/WireSecondaryButton.kt index b62ee3198f3..4045fa2d517 100644 --- a/core/ui-common/src/main/kotlin/com/wire/android/ui/common/button/WireSecondaryButton.kt +++ b/core/ui-common/src/main/kotlin/com/wire/android/ui/common/button/WireSecondaryButton.kt @@ -69,7 +69,8 @@ fun WireSecondaryButton( horizontal = MaterialTheme.wireDimensions.buttonHorizontalContentPadding, vertical = MaterialTheme.wireDimensions.buttonVerticalContentPadding ), - interactionSource: MutableInteractionSource = remember { MutableInteractionSource() } + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + onClickDescription: String? = null ) = WireButton( onClick = onClick, loading = loading, @@ -90,7 +91,8 @@ fun WireSecondaryButton( borderWidth = borderWidth, contentPadding = contentPadding, interactionSource = interactionSource, - modifier = modifier + modifier = modifier, + onClickDescription = onClickDescription ) @Preview(name = "Default WireSecondaryButton") diff --git a/core/ui-common/src/main/kotlin/com/wire/android/ui/common/button/WireTertiaryButton.kt b/core/ui-common/src/main/kotlin/com/wire/android/ui/common/button/WireTertiaryButton.kt index 9f341a2b4ab..bcd77ea67bb 100644 --- a/core/ui-common/src/main/kotlin/com/wire/android/ui/common/button/WireTertiaryButton.kt +++ b/core/ui-common/src/main/kotlin/com/wire/android/ui/common/button/WireTertiaryButton.kt @@ -44,6 +44,7 @@ import com.wire.android.ui.theme.wireTypography @Composable fun WireTertiaryButton( onClick: () -> Unit, + modifier: Modifier = Modifier, loading: Boolean = false, leadingIcon: @Composable (() -> Unit)? = null, leadingIconAlignment: IconAlignment = IconAlignment.Center, @@ -65,7 +66,7 @@ fun WireTertiaryButton( vertical = MaterialTheme.wireDimensions.buttonVerticalContentPadding ), interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, - modifier: Modifier = Modifier, + onClickDescription: String? = null ) = WireButton( onClick = onClick, loading = loading, @@ -86,7 +87,8 @@ fun WireTertiaryButton( borderWidth = borderWidth, contentPadding = contentPadding, interactionSource = interactionSource, - modifier = modifier + modifier = modifier, + onClickDescription = onClickDescription ) @Preview(name = "Default WireSecondaryButton")