From 0390bbf5b6b0ac74bcbcd8355ae666314a6624e7 Mon Sep 17 00:00:00 2001 From: Mohamad Jaara Date: Wed, 13 Nov 2024 15:42:01 +0100 Subject: [PATCH 1/8] chore: update kalium --- kalium | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kalium b/kalium index 83207c730fd..2c1b2da06b9 160000 --- a/kalium +++ b/kalium @@ -1 +1 @@ -Subproject commit 83207c730fd36ac4aa2c6fdc11338dd18fc51333 +Subproject commit 2c1b2da06b9a0699264f55adc7f96a96d5e7b8c0 From cca1fa5f26222c19b172dbd2a465ec8926468d7a Mon Sep 17 00:00:00 2001 From: Mohamad Jaara Date: Wed, 13 Nov 2024 18:30:41 +0100 Subject: [PATCH 2/8] chore: enable conversation list pagination for internal app [WPB-14295] (#3626) --- .github/workflows/build-edge-env.yml | 3 ++- default.json | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-edge-env.yml b/.github/workflows/build-edge-env.yml index 6d1de717470..3b091321437 100644 --- a/.github/workflows/build-edge-env.yml +++ b/.github/workflows/build-edge-env.yml @@ -95,4 +95,5 @@ jobs: serviceAccountJson: service_account.json packageName: com.wire.internal releaseFiles: app/build/outputs/bundle/internalCompat/*.aab - track: alpha + track: production + status: completed diff --git a/default.json b/default.json index 94c1f143236..a2ba34da13d 100644 --- a/default.json +++ b/default.json @@ -80,7 +80,8 @@ "analytics_enabled": true, "picture_in_picture_enabled": true, "analytics_app_key": "8ffae535f1836ed5f58fd5c8a11c00eca07c5438", - "analytics_server_url": "https://countly.wire.com/" + "analytics_server_url": "https://countly.wire.com/", + "paginated_conversation_list_enabled": true }, "fdroid": { "application_id": "com.wire", From 4e756aed40814d8a2cc401f65a22199ba9ac8795 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Saleniuk?= <30429749+saleniuk@users.noreply.github.com> Date: Thu, 14 Nov 2024 10:27:53 +0100 Subject: [PATCH 3/8] fix: blank lists or flickering on Home Destinations - wrong lazyListStates [WPB-14276] (#3627) --- .../com/wire/android/ui/home/HomeScreen.kt | 2 +- .../wire/android/ui/home/HomeStateHolder.kt | 38 ++++++++++--------- .../android/ui/home/archive/ArchiveScreen.kt | 3 +- .../all/AllConversationsScreen.kt | 3 +- .../ui/home/settings/SettingsScreen.kt | 3 +- .../ui/home/whatsnew/WhatsNewScreen.kt | 3 +- 6 files changed, 30 insertions(+), 22 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/HomeScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/HomeScreen.kt index 74dcec0ba5b..282fac94e23 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/HomeScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/HomeScreen.kt @@ -305,7 +305,7 @@ fun HomeContent( } }, collapsingEnabled = !searchBarState.isSearchActive, - contentLazyListState = homeStateHolder.currentLazyListState, + contentLazyListState = homeStateHolder.lazyListStateFor(currentNavigationItem), content = { /** * This "if" is a workaround, otherwise it can crash because of the SubcomposeLayout's nature. diff --git a/app/src/main/kotlin/com/wire/android/ui/home/HomeStateHolder.kt b/app/src/main/kotlin/com/wire/android/ui/home/HomeStateHolder.kt index 459a3127f52..034c10e9250 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/HomeStateHolder.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/HomeStateHolder.kt @@ -24,6 +24,8 @@ import androidx.compose.material3.DrawerState import androidx.compose.material3.DrawerValue import androidx.compose.material3.rememberDrawerState import androidx.compose.runtime.Composable +import androidx.compose.runtime.State +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope @@ -42,12 +44,15 @@ class HomeStateHolder( val coroutineScope: CoroutineScope, val navController: NavHostController, val drawerState: DrawerState, - val currentNavigationItem: HomeDestination, val searchBarState: SearchBarState, val navigator: Navigator, - lazyListStates: Map, + private val currentNavigationItemState: State, + private val lazyListStates: Map, ) { - val currentLazyListState = lazyListStates[currentNavigationItem] ?: error("No LazyListState found for $currentNavigationItem") + val currentNavigationItem + get() = currentNavigationItemState.value + fun lazyListStateFor(destination: HomeDestination): LazyListState = + lazyListStates[destination] ?: error("No LazyListState found for $destination") fun closeDrawer() { coroutineScope.launch { @@ -73,23 +78,22 @@ fun rememberHomeScreenState( ): HomeStateHolder { val searchBarState = rememberSearchbarState() val navBackStackEntry by navController.currentBackStackEntryAsState() - val currentRoute = navBackStackEntry?.destination?.route - val currentNavigationItem = currentRoute?.let { HomeDestination.fromRoute(it) } ?: HomeDestination.Conversations + val currentNavigationItemState = remember { + derivedStateOf { + navBackStackEntry?.destination?.route?.let { HomeDestination.fromRoute(it) } ?: HomeDestination.Conversations + } + } val lazyListStates = HomeDestination.values().associateWith { rememberLazyListState() } - val homeState = remember( - currentNavigationItem - ) { + return remember { HomeStateHolder( - coroutineScope, - navController, - drawerState, - currentNavigationItem, - searchBarState, - navigator, - lazyListStates + coroutineScope = coroutineScope, + navController = navController, + drawerState = drawerState, + searchBarState = searchBarState, + navigator = navigator, + currentNavigationItemState = currentNavigationItemState, + lazyListStates = lazyListStates ) } - - return homeState } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/archive/ArchiveScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/archive/ArchiveScreen.kt index 52b0c0f2bc1..597f2d26390 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/archive/ArchiveScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/archive/ArchiveScreen.kt @@ -20,6 +20,7 @@ package com.wire.android.ui.home.archive import androidx.compose.foundation.text.input.TextFieldState import androidx.compose.runtime.Composable +import com.wire.android.navigation.HomeDestination import com.wire.android.navigation.HomeNavGraph import com.wire.android.navigation.WireDestination import com.wire.android.navigation.rememberNavigator @@ -42,7 +43,7 @@ fun ArchiveScreen(homeStateHolder: HomeStateHolder) { navigator = navigator, searchBarState = searchBarState, conversationsSource = ConversationsSource.ARCHIVE, - lazyListState = currentLazyListState, + lazyListState = lazyListStateFor(HomeDestination.Archive), emptyListContent = { ArchiveEmptyContent() } ) } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/all/AllConversationsScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/all/AllConversationsScreen.kt index a1b757e968c..f354558d5cf 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/all/AllConversationsScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/all/AllConversationsScreen.kt @@ -20,6 +20,7 @@ package com.wire.android.ui.home.conversationslist.all import androidx.compose.foundation.text.input.TextFieldState import androidx.compose.runtime.Composable +import com.wire.android.navigation.HomeDestination import com.wire.android.navigation.HomeNavGraph import com.wire.android.navigation.WireDestination import com.wire.android.navigation.rememberNavigator @@ -42,7 +43,7 @@ fun AllConversationsScreen(homeStateHolder: HomeStateHolder) { navigator = navigator, searchBarState = searchBarState, conversationsSource = ConversationsSource.MAIN, - lazyListState = currentLazyListState, + lazyListState = lazyListStateFor(HomeDestination.Conversations), emptyListContent = { AllConversationsEmptyContent() } ) } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/settings/SettingsScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/settings/SettingsScreen.kt index 3617c3028d5..4d6892641d2 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/settings/SettingsScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/settings/SettingsScreen.kt @@ -33,6 +33,7 @@ import com.wire.android.R import com.wire.android.appLogger import com.wire.android.model.Clickable import com.wire.android.navigation.BackStackMode +import com.wire.android.navigation.HomeDestination import com.wire.android.navigation.HomeNavGraph import com.wire.android.navigation.NavigationCommand import com.wire.android.navigation.WireDestination @@ -62,7 +63,7 @@ fun SettingsScreen( val context = LocalContext.current SettingsScreenContent( - lazyListState = homeStateHolder.currentLazyListState, + lazyListState = homeStateHolder.lazyListStateFor(HomeDestination.Settings), settingsState = viewModel.state, onItemClicked = remember { { diff --git a/app/src/main/kotlin/com/wire/android/ui/home/whatsnew/WhatsNewScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/whatsnew/WhatsNewScreen.kt index 049e3021265..de06e11106b 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/whatsnew/WhatsNewScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/whatsnew/WhatsNewScreen.kt @@ -33,6 +33,7 @@ import androidx.hilt.navigation.compose.hiltViewModel import com.wire.android.BuildConfig import com.wire.android.R import com.wire.android.model.Clickable +import com.wire.android.navigation.HomeDestination import com.wire.android.navigation.HomeNavGraph import com.wire.android.navigation.NavigationCommand import com.wire.android.navigation.WireDestination @@ -51,7 +52,7 @@ fun WhatsNewScreen( val context = LocalContext.current WhatsNewScreenContent( state = whatsNewViewModel.state, - lazyListState = homeStateHolder.currentLazyListState, + lazyListState = homeStateHolder.lazyListStateFor(HomeDestination.WhatsNew), onItemClicked = remember { { it.direction.handleNavigation( From bb1b2367367a712a8bf2e43fcf7a94ad8daf99aa Mon Sep 17 00:00:00 2001 From: Mohamad Jaara Date: Thu, 14 Nov 2024 11:56:41 +0100 Subject: [PATCH 4/8] fix: lateinit property selfUserId has not been initialized in ConversationInfoViewModel [WPB-14317] (#3632) --- .../info/ConversationInfoViewModel.kt | 14 ++------------ .../info/ConversationInfoViewModelArrangement.kt | 12 ++---------- .../info/ConversationInfoViewModelTest.kt | 14 -------------- 3 files changed, 4 insertions(+), 36 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/info/ConversationInfoViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/info/ConversationInfoViewModel.kt index 3495deb7662..5e36411a498 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/info/ConversationInfoViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/info/ConversationInfoViewModel.kt @@ -25,6 +25,7 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope import com.wire.android.R import com.wire.android.appLogger +import com.wire.android.di.CurrentAccount import com.wire.android.model.ImageAsset import com.wire.android.navigation.SavedStateViewModel import com.wire.android.ui.home.conversations.ConversationNavArgs @@ -40,9 +41,7 @@ import com.wire.kalium.logic.data.user.ConnectionState import com.wire.kalium.logic.data.user.UserId import com.wire.kalium.logic.feature.conversation.ObserveConversationDetailsUseCase import com.wire.kalium.logic.feature.e2ei.usecase.FetchConversationMLSVerificationStatusUseCase -import com.wire.kalium.logic.feature.user.GetSelfUserUseCase import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import javax.inject.Inject @@ -52,9 +51,9 @@ class ConversationInfoViewModel @Inject constructor( private val qualifiedIdMapper: QualifiedIdMapper, override val savedStateHandle: SavedStateHandle, private val observeConversationDetails: ObserveConversationDetailsUseCase, - private val observerSelfUser: GetSelfUserUseCase, private val fetchConversationMLSVerificationStatus: FetchConversationMLSVerificationStatusUseCase, private val wireSessionImageLoader: WireSessionImageLoader, + @CurrentAccount private val selfUserId: UserId, ) : SavedStateViewModel(savedStateHandle) { private val conversationNavArgs: ConversationNavArgs = savedStateHandle.navArgs() @@ -62,10 +61,7 @@ class ConversationInfoViewModel @Inject constructor( var conversationInfoViewState by mutableStateOf(ConversationInfoViewState(conversationId)) - private lateinit var selfUserId: UserId - init { - getSelfUserId() fetchMLSVerificationStatus() } @@ -75,12 +71,6 @@ class ConversationInfoViewModel @Inject constructor( } } - private fun getSelfUserId() { - viewModelScope.launch { - selfUserId = observerSelfUser().first().id - } - } - /* If this would be collected in the scope of this ViewModel (in `init` for instance) then there would be a race condition. [MessageComposerViewModel] handles the navigating back after removing a group and here it would navigate to home if the group diff --git a/app/src/test/kotlin/com/wire/android/ui/home/conversations/info/ConversationInfoViewModelArrangement.kt b/app/src/test/kotlin/com/wire/android/ui/home/conversations/info/ConversationInfoViewModelArrangement.kt index 397c3e3107b..931b5f6280d 100644 --- a/app/src/test/kotlin/com/wire/android/ui/home/conversations/info/ConversationInfoViewModelArrangement.kt +++ b/app/src/test/kotlin/com/wire/android/ui/home/conversations/info/ConversationInfoViewModelArrangement.kt @@ -32,7 +32,6 @@ import com.wire.kalium.logic.data.id.QualifiedIdMapper import com.wire.kalium.logic.data.user.UserId import com.wire.kalium.logic.feature.conversation.ObserveConversationDetailsUseCase import com.wire.kalium.logic.feature.e2ei.usecase.FetchConversationMLSVerificationStatusUseCase -import com.wire.kalium.logic.feature.user.GetSelfUserUseCase import io.mockk.MockKAnnotations import io.mockk.coEvery import io.mockk.every @@ -57,9 +56,6 @@ class ConversationInfoViewModelArrangement { @MockK lateinit var observeConversationDetails: ObserveConversationDetailsUseCase - @MockK - lateinit var observerSelfUser: GetSelfUserUseCase - @MockK lateinit var fetchConversationMLSVerificationStatus: FetchConversationMLSVerificationStatusUseCase @@ -74,9 +70,9 @@ class ConversationInfoViewModelArrangement { qualifiedIdMapper, savedStateHandle, observeConversationDetails, - observerSelfUser, fetchConversationMLSVerificationStatus, - wireSessionImageLoader + wireSessionImageLoader, + selfUserId = TestUser.SELF_USER_ID, ) } @@ -105,10 +101,6 @@ class ConversationInfoViewModelArrangement { coEvery { observeConversationDetails(any()) } returns flowOf(ObserveConversationDetailsUseCase.Result.Failure(failure)) } - suspend fun withSelfUser() = apply { - coEvery { observerSelfUser() } returns flowOf(TestUser.SELF_USER) - } - fun withMentionedUserId(id: UserId) = apply { every { qualifiedIdMapper.fromStringToQualifiedID(id.toString()) } returns id } diff --git a/app/src/test/kotlin/com/wire/android/ui/home/conversations/info/ConversationInfoViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/home/conversations/info/ConversationInfoViewModelTest.kt index d4243d490e4..76132e209bb 100644 --- a/app/src/test/kotlin/com/wire/android/ui/home/conversations/info/ConversationInfoViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/home/conversations/info/ConversationInfoViewModelTest.kt @@ -47,7 +47,6 @@ class ConversationInfoViewModelTest { val groupConversationDetails = mockConversationDetailsGroup("Conversation Name Goes Here") val (_, viewModel) = ConversationInfoViewModelArrangement() .withConversationDetailUpdate(conversationDetails = groupConversationDetails) - .withSelfUser() .withMentionedUserId(TestUser.SELF_USER.id) .arrange() // When @@ -62,7 +61,6 @@ class ConversationInfoViewModelTest { val groupConversationDetails = mockConversationDetailsGroup("Conversation Name Goes Here") val (_, viewModel) = ConversationInfoViewModelArrangement() .withConversationDetailUpdate(conversationDetails = groupConversationDetails) - .withSelfUser() .withMentionedUserId(TestUser.OTHER_USER.id) .arrange() // When @@ -79,7 +77,6 @@ class ConversationInfoViewModelTest { .withConversationDetailUpdate( conversationDetails = oneToOneConversationDetails ) - .withSelfUser() .arrange() launch { viewModel.observeConversationDetails {} }.run { advanceUntilIdle() @@ -101,7 +98,6 @@ class ConversationInfoViewModelTest { .withConversationDetailUpdate( conversationDetails = oneToOneConversationDetails ) - .withSelfUser() .arrange() launch { viewModel.observeConversationDetails {} }.run { advanceUntilIdle() @@ -117,7 +113,6 @@ class ConversationInfoViewModelTest { val groupConversationDetails = mockConversationDetailsGroup("Conversation Name Goes Here") val (_, viewModel) = ConversationInfoViewModelArrangement() .withConversationDetailUpdate(conversationDetails = groupConversationDetails) - .withSelfUser() .arrange() launch { viewModel.observeConversationDetails {} }.run { advanceUntilIdle() @@ -140,7 +135,6 @@ class ConversationInfoViewModelTest { .withConversationDetailUpdate( conversationDetails = firstConversationDetails ) - .withSelfUser() .arrange() launch { viewModel.observeConversationDetails {} }.run { advanceUntilIdle() @@ -169,7 +163,6 @@ class ConversationInfoViewModelTest { runTest { // Given val (_, viewModel) = ConversationInfoViewModelArrangement() - .withSelfUser() .arrange() // When - Then @@ -185,7 +178,6 @@ class ConversationInfoViewModelTest { .withConversationDetailUpdate( conversationDetails = oneToOneConversationDetails ) - .withSelfUser() .arrange() launch { viewModel.observeConversationDetails {} }.run { advanceUntilIdle() @@ -202,7 +194,6 @@ class ConversationInfoViewModelTest { val otherUserAvatar = conversationDetails.otherUser.previewPicture val (_, viewModel) = ConversationInfoViewModelArrangement() .withConversationDetailUpdate(conversationDetails = conversationDetails) - .withSelfUser() .arrange() launch { viewModel.observeConversationDetails {} }.run { advanceUntilIdle() @@ -220,7 +211,6 @@ class ConversationInfoViewModelTest { val groupConversationDetails = mockConversationDetailsGroup("Conversation Name Goes Here") val (_, viewModel) = ConversationInfoViewModelArrangement() .withConversationDetailUpdate(conversationDetails = groupConversationDetails) - .withSelfUser() .arrange() // then @@ -244,7 +234,6 @@ class ConversationInfoViewModelTest { val groupConversationDetails = mockConversationDetailsGroup("Conversation Name Goes Here") val (_, viewModel) = ConversationInfoViewModelArrangement() .withConversationDetailUpdate(conversationDetails = groupConversationDetails) - .withSelfUser() .arrange() // then @@ -268,7 +257,6 @@ class ConversationInfoViewModelTest { val groupConversationDetails = mockConversationDetailsGroup("Conversation Name Goes Here") val (_, viewModel) = ConversationInfoViewModelArrangement() .withConversationDetailUpdate(conversationDetails = groupConversationDetails) - .withSelfUser() .arrange() // then @@ -290,7 +278,6 @@ class ConversationInfoViewModelTest { fun `given Failure while getting an MLS conversation's verification status, then mlsVerificationStatus is null`() = runTest { // Given val (_, viewModel) = ConversationInfoViewModelArrangement() - .withSelfUser() .arrange() // then @@ -309,7 +296,6 @@ class ConversationInfoViewModelTest { // Given val (arrangement, viewModel) = ConversationInfoViewModelArrangement() .withConversationDetailFailure(StorageFailure.DataNotFound) - .withSelfUser() .arrange() launch { viewModel.observeConversationDetails(arrangement.onNotFound) }.run { advanceUntilIdle() From 7a6c3c6e35d5448b381ea67faeec1b157de4d465 Mon Sep 17 00:00:00 2001 From: Mohamad Jaara Date: Thu, 14 Nov 2024 12:40:00 +0100 Subject: [PATCH 5/8] chore: update kalium --- kalium | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kalium b/kalium index 2c1b2da06b9..7440f44a015 160000 --- a/kalium +++ b/kalium @@ -1 +1 @@ -Subproject commit 2c1b2da06b9a0699264f55adc7f96a96d5e7b8c0 +Subproject commit 7440f44a0153dd4527e88991610db0260fab4762 From 740c99f9dd98df86cb6d1f7481b77a743fd74330 Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Tue, 19 Nov 2024 15:22:12 +0100 Subject: [PATCH 6/8] chore: bump next 4.9.1 --- build-logic/plugins/src/main/kotlin/AndroidCoordinates.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build-logic/plugins/src/main/kotlin/AndroidCoordinates.kt b/build-logic/plugins/src/main/kotlin/AndroidCoordinates.kt index a93b618ee19..51888ade39a 100644 --- a/build-logic/plugins/src/main/kotlin/AndroidCoordinates.kt +++ b/build-logic/plugins/src/main/kotlin/AndroidCoordinates.kt @@ -26,7 +26,7 @@ object AndroidSdk { object AndroidApp { const val id = "com.wire.android" - const val versionName = "4.9.0" + const val versionName = "4.9.1" val versionCode by lazy { Versionizer(_rootDir).versionCode } From 9a791bfd9fd009b831a7fa875fee44a85058923a Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Fri, 22 Nov 2024 16:00:06 +0100 Subject: [PATCH 7/8] chore: enabling pagination for beta --- default.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/default.json b/default.json index a2ba34da13d..5787cb41974 100644 --- a/default.json +++ b/default.json @@ -68,7 +68,8 @@ "encrypt_proteus_storage": true, "analytics_enabled": true, "analytics_app_key": "8ffae535f1836ed5f58fd5c8a11c00eca07c5438", - "analytics_server_url": "https://countly.wire.com/" + "analytics_server_url": "https://countly.wire.com/", + "paginated_conversation_list_enabled": true }, "internal": { "application_id": "com.wire.internal", From b5ec0276fe8a1869580cc2ca44d8003923b57793 Mon Sep 17 00:00:00 2001 From: boris Date: Tue, 26 Nov 2024 12:35:37 +0200 Subject: [PATCH 8/8] fix: Accessibility strings founded issues #WPB-9784 (#3657) --- .../com/wire/android/model/UserAvatarData.kt | 8 +++++ .../overview/CreateAccountOverviewParams.kt | 3 +- .../CreatePersonalAccountOverviewScreen.kt | 13 +++++++-- .../ui/authentication/devices/DeviceItem.kt | 29 ++++++++++++++++++- .../android/ui/common/UserProfileAvatar.kt | 15 ++++++++-- .../wire/android/ui/common/WireDropDown.kt | 21 ++++++++++---- .../ConversationParticipantItem.kt | 14 +++++++-- .../search/HighLightSubtTitle.kt | 6 +++- .../search/SearchUsersAndServicesScreen.kt | 7 +++-- .../common/ConversationItemFactory.kt | 25 +++++++++++----- .../ui/home/messagecomposer/MessageActions.kt | 2 +- .../recordaudio/RecordAudioButtons.kt | 2 +- .../self/dialog/LogoutOptionsDialog.kt | 3 +- app/src/main/res/values/strings.xml | 5 ++++ .../com/wire/android/ui/common/WireDialog.kt | 12 ++++---- .../bottomsheet/ModalSheetHeaderItem.kt | 4 +-- .../android/ui/common/button/WireButton.kt | 16 +++++++--- .../android/ui/common/button/WireItemLabel.kt | 6 ++-- .../ui/common/button/WirePrimaryButton.kt | 6 ++-- .../ui/common/button/WireSecondaryButton.kt | 6 ++-- .../ui/common/button/WireTertiaryButton.kt | 6 ++-- .../common/topappbar/NavigationIconButton.kt | 4 +-- .../ui-common/src/main/res/values/strings.xml | 2 ++ 23 files changed, 166 insertions(+), 49 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/model/UserAvatarData.kt b/app/src/main/kotlin/com/wire/android/model/UserAvatarData.kt index 5d49d78d5d3..372e2ee474f 100644 --- a/app/src/main/kotlin/com/wire/android/model/UserAvatarData.kt +++ b/app/src/main/kotlin/com/wire/android/model/UserAvatarData.kt @@ -19,6 +19,7 @@ package com.wire.android.model import androidx.compose.runtime.Stable +import com.wire.android.R import com.wire.android.ui.home.conversationslist.model.Membership import com.wire.android.util.EMPTY import com.wire.kalium.logic.data.user.ConnectionState @@ -37,6 +38,13 @@ data class UserAvatarData( return asset == null && nameBasedAvatar != null && nameBasedAvatar.initials.isEmpty().not() && membership != Membership.Service } + + fun getAvailabilityStatusDescriptionId(): Int? = when (availabilityStatus) { + UserAvailabilityStatus.NONE -> null + UserAvailabilityStatus.AVAILABLE -> R.string.user_profile_status_available + UserAvailabilityStatus.AWAY -> R.string.user_profile_status_away + UserAvailabilityStatus.BUSY -> R.string.user_profile_status_busy + } } /** diff --git a/app/src/main/kotlin/com/wire/android/ui/authentication/create/overview/CreateAccountOverviewParams.kt b/app/src/main/kotlin/com/wire/android/ui/authentication/create/overview/CreateAccountOverviewParams.kt index 51d3d2336e0..d59243c3c77 100644 --- a/app/src/main/kotlin/com/wire/android/ui/authentication/create/overview/CreateAccountOverviewParams.kt +++ b/app/src/main/kotlin/com/wire/android/ui/authentication/create/overview/CreateAccountOverviewParams.kt @@ -26,5 +26,6 @@ data class CreateAccountOverviewParams( val contentText: String = "", @DrawableRes val contentIconResId: Int = 0, val learnMoreText: String = "", - val learnMoreUrl: String = "" + val learnMoreUrl: String = "", + val isContentTextSemanticAccessible: Boolean = false ) diff --git a/app/src/main/kotlin/com/wire/android/ui/authentication/create/overview/CreatePersonalAccountOverviewScreen.kt b/app/src/main/kotlin/com/wire/android/ui/authentication/create/overview/CreatePersonalAccountOverviewScreen.kt index ba76da0c892..7e2237c1ed3 100644 --- a/app/src/main/kotlin/com/wire/android/ui/authentication/create/overview/CreatePersonalAccountOverviewScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/authentication/create/overview/CreatePersonalAccountOverviewScreen.kt @@ -109,7 +109,8 @@ fun CreateTeamAccountOverviewScreen( contentText = stringResource(id = overviewResources.overviewContentTextResId), contentIconResId = overviewResources.overviewContentIconResId, learnMoreText = stringResource(id = overviewResources.overviewLearnMoreTextResId), - learnMoreUrl = viewModel.learnMoreUrl() + learnMoreUrl = viewModel.learnMoreUrl(), + isContentTextSemanticAccessible = true ) ) } @@ -193,7 +194,15 @@ private fun OverviewTexts( text = overviewParams.contentText, style = MaterialTheme.wireTypography.body02, textAlign = TextAlign.Center, - modifier = Modifier.fillMaxWidth().clearAndSetSemantics {} + modifier = Modifier + .fillMaxWidth() + .run { + if (overviewParams.isContentTextSemanticAccessible) { + this + } else { + clearAndSetSemantics {} + } + } ) Text( text = overviewParams.learnMoreText, 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 a3a940da9e5..b0a7d95a2b4 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.clearAndSetSemantics +import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import com.wire.android.BuildConfig @@ -189,13 +191,22 @@ private fun ColumnScope.DeviceItemTexts( .fillMaxWidth() .shimmerPlaceholder(visible = placeholder) ) { + val deviceName = device.name.asString() + val shouldAddNotVerifiedLabel = shouldShowVerifyLabel && !shouldShowE2EIInfo && !(device.isVerifiedProteus && !isCurrentClient) + val semantic = if (shouldAddNotVerifiedLabel) { + val notVerifiedLabel = stringResource(R.string.label_client_unverified) + Modifier.clearAndSetSemantics { contentDescription = "$deviceName, $notVerifiedLabel" } + } else { + Modifier + } Text( style = MaterialTheme.wireTypography.body02, color = MaterialTheme.wireColorScheme.onBackground, - text = device.name.asString(), + text = deviceName, modifier = Modifier .wrapContentWidth() .shimmerPlaceholder(visible = placeholder) + .then(semantic) ) if (shouldShowVerifyLabel) { if (shouldShowE2EIInfo) { @@ -223,6 +234,16 @@ private fun ColumnScope.DeviceItemTexts( Spacer(modifier = Modifier.height(MaterialTheme.wireDimensions.removeDeviceItemTitleVerticalPadding)) + MLSDetails(device, placeholder) + + ProteusDetails(device, placeholder) +} + +@Composable +private fun MLSDetails( + device: Device, + placeholder: Boolean +) { device.mlsClientIdentity?.let { identity -> Text( style = MaterialTheme.wireTypography.subline01, @@ -238,7 +259,13 @@ private fun ColumnScope.DeviceItemTexts( .shimmerPlaceholder(visible = placeholder) ) } +} +@Composable +private fun ProteusDetails( + device: Device, + placeholder: Boolean +) { val proteusDetails: String = if (!device.registrationTime.isNullOrBlank()) { if (device.lastActiveInWholeWeeks != null) { stringResource( diff --git a/app/src/main/kotlin/com/wire/android/ui/common/UserProfileAvatar.kt b/app/src/main/kotlin/com/wire/android/ui/common/UserProfileAvatar.kt index abe895cabba..ccaf7a8eb44 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/UserProfileAvatar.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/UserProfileAvatar.kt @@ -57,7 +57,10 @@ import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.clearAndSetSemantics import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.role import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview @@ -248,12 +251,19 @@ private fun DefaultInitialsAvatar( type: UserProfileAvatarType, size: Dp, modifier: Modifier = Modifier, + contentDescription: String? = null ) { - val contentDescription = stringResource(R.string.content_description_user_avatar) + val semantics = if (contentDescription != null) { + Modifier.semantics { + this.contentDescription = contentDescription + this.role = Role.Image + } + } else { + Modifier.clearAndSetSemantics { } + } Box( contentAlignment = Alignment.Center, modifier = modifier - .semantics { this.contentDescription = contentDescription } .size(size) .clip(CircleShape) .background( @@ -266,6 +276,7 @@ private fun DefaultInitialsAvatar( ) } ) + .then(semantics) ) { Text( text = nameBasedAvatar.initials, diff --git a/app/src/main/kotlin/com/wire/android/ui/common/WireDropDown.kt b/app/src/main/kotlin/com/wire/android/ui/common/WireDropDown.kt index 42d3a910900..d05b7c7e741 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/WireDropDown.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/WireDropDown.kt @@ -56,6 +56,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.onClick +import androidx.compose.ui.semantics.paneTitle import androidx.compose.ui.semantics.selected import androidx.compose.ui.semantics.semantics import androidx.compose.ui.tooling.preview.Preview @@ -164,6 +165,8 @@ private fun MenuPopUp( hidePopUp: () -> Unit, onChange: (selectedIndex: Int) -> Unit ) { + val dropdownDescription = stringResource(R.string.content_description_drop_down) + MaterialTheme(shapes = MaterialTheme.shapes.copy(extraSmall = shape)) { // we want PopUp to cover the selection field, so we set this offset. // "- 8.dp" is because DropdownMenu has inner top padding, which can't be changed, @@ -178,14 +181,15 @@ private fun MenuPopUp( .width(with(LocalDensity.current) { textFieldWidth.width.toDp() }) .background(color = MaterialTheme.wireColorScheme.secondaryButtonEnabled) .border(width = 1.dp, color = borderColor, shape) + .semantics { paneTitle = dropdownDescription } ) { SelectionField( - Modifier.clickable(onClickLabel = stringResource(R.string.content_description_close_dropdown)) { hidePopUp() }, leadingCompose, selectedIndex, selectionText, - arrowRotation + arrowRotation, + Modifier.clickable(onClickLabel = stringResource(R.string.content_description_close_dropdown)) { hidePopUp() } ) List(items.size) { index -> @@ -210,11 +214,11 @@ private fun MenuPopUp( @Composable private fun SelectionField( - modifier: Modifier = Modifier, leadingCompose: @Composable ((index: Int) -> Unit)?, selectedIndex: Int, text: String, - arrowRotation: Float + arrowRotation: Float, + modifier: Modifier = Modifier ) { Row( modifier @@ -262,6 +266,7 @@ private fun DropdownItem( onClick: () -> Unit ) { val selectLabel = stringResource(R.string.content_description_select_label) + val closeDropdownLabel = stringResource(R.string.content_description_close_dropdown) return DropdownMenuItem( text = { Text( @@ -281,8 +286,12 @@ private fun DropdownItem( onClick = onClick, modifier = Modifier .semantics { - onClick(selectLabel) { false } - if (isSelected) selected = true + if (isSelected) { + selected = true + onClick(closeDropdownLabel) { false } + } else { + onClick(selectLabel) { false } + } } .background( color = if (isSelected) MaterialTheme.wireColorScheme.secondaryButtonSelected diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/participants/ConversationParticipantItem.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/participants/ConversationParticipantItem.kt index 846eedf887a..6fb983b7ae7 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/participants/ConversationParticipantItem.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/participants/ConversationParticipantItem.kt @@ -28,6 +28,8 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.semantics import com.wire.android.BuildConfig import com.wire.android.R import com.wire.android.model.Clickable @@ -124,10 +126,18 @@ fun ConversationParticipantItem( } }, subtitle = { + val userName = processUsername(uiParticipant) + // Availability status should be called after username by TalkBack + val subtitleModifier = uiParticipant.avatarData.getAvailabilityStatusDescriptionId()?.let { + val contentDescription = stringResource(it) + Modifier.semantics { this.contentDescription = "$userName, $contentDescription" } + } ?: Modifier + HighlightSubtitle( - subTitle = processUsername(uiParticipant), + subTitle = userName, searchQuery = searchQuery, - prefix = processUsernamePrefix(uiParticipant) + prefix = processUsernamePrefix(uiParticipant), + modifier = subtitleModifier ) }, actions = { diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/HighLightSubtTitle.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/HighLightSubtTitle.kt index 1909cbda730..12e1a7c4d4a 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/HighLightSubtTitle.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/HighLightSubtTitle.kt @@ -21,6 +21,7 @@ package com.wire.android.ui.home.conversations.search import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.style.TextOverflow @@ -33,6 +34,7 @@ import com.wire.android.util.QueryMatchExtractor @Composable fun HighlightSubtitle( subTitle: String, + modifier: Modifier = Modifier, searchQuery: String = String.EMPTY, prefix: String = "@" ) { @@ -76,6 +78,7 @@ fun HighlightSubtitle( } } }, + modifier = modifier, maxLines = 1, overflow = TextOverflow.Ellipsis ) @@ -85,7 +88,8 @@ fun HighlightSubtitle( style = MaterialTheme.wireTypography.subline01, color = MaterialTheme.wireColorScheme.secondaryText, maxLines = 1, - overflow = TextOverflow.Ellipsis + overflow = TextOverflow.Ellipsis, + modifier = modifier ) } } 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 ff3801d6bf9..743832d4877 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 @@ -117,8 +117,11 @@ fun SearchUsersAndServicesScreen( SearchPeopleScreenType.CONVERSATION_DETAILS -> NavigationIconType.Close(R.string.content_description_add_participants_close) - SearchPeopleScreenType.NEW_CONVERSATION -> NavigationIconType.Close() - SearchPeopleScreenType.NEW_GROUP_CONVERSATION -> NavigationIconType.Back() + SearchPeopleScreenType.NEW_CONVERSATION -> + NavigationIconType.Close(R.string.content_description_new_conversation_close_btn) + + SearchPeopleScreenType.NEW_GROUP_CONVERSATION -> + NavigationIconType.Back(R.string.content_description_new_group_conversation_back_btn) }, onNavigationPressed = onClose ) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/common/ConversationItemFactory.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/common/ConversationItemFactory.kt index 44ac89e3f7a..571b408bab4 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/common/ConversationItemFactory.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/common/ConversationItemFactory.kt @@ -32,6 +32,7 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.semantics import com.wire.android.R import com.wire.android.model.Clickable import com.wire.android.model.UserAvatarData @@ -75,16 +76,24 @@ fun ConversationItemFactory( ) { val openConversationOptionDescription = stringResource(R.string.content_description_conversation_details_more_btn) val openUserProfileDescription = stringResource(R.string.content_description_open_user_profile_label) + val acceptOrIgnoreDescription = stringResource(R.string.content_description_accept_or_ignore_connection_label) val openConversationDescription = stringResource(R.string.content_description_open_conversation_label) val onConversationItemClick = remember(conversation) { when (val lastEvent = conversation.lastMessageContent) { - is UILastMessageContent.Connection -> Clickable( - enabled = true, - onClick = { openUserProfile(lastEvent.userId) }, - onLongClick = null, - onClickDescription = openUserProfileDescription, - onLongClickDescription = null - ) + is UILastMessageContent.Connection -> { + val onClickDescription = if (conversation.badgeEventType == BadgeEventType.ReceivedConnectionRequest) { + acceptOrIgnoreDescription + } else { + openUserProfileDescription + } + Clickable( + enabled = true, + onClick = { openUserProfile(lastEvent.userId) }, + onLongClick = null, + onClickDescription = onClickDescription, + onLongClickDescription = null + ) + } else -> Clickable( enabled = true, @@ -97,7 +106,7 @@ fun ConversationItemFactory( } GeneralConversationItem( - modifier = modifier, + modifier = modifier.semantics(mergeDescendants = true) { }, conversation = conversation, isSelectable = isSelectableItem, isChecked = isChecked, diff --git a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/MessageActions.kt b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/MessageActions.kt index 17f9ad78bbc..30f5621a889 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/MessageActions.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/MessageActions.kt @@ -108,7 +108,7 @@ fun MessageEditActions( WireTertiaryIconButton( onButtonClicked = onEditCancelButtonClicked, iconResource = R.drawable.ic_close, - contentDescription = R.string.content_description_close_button, + contentDescription = R.string.label_close, shape = CircleShape, minSize = MaterialTheme.wireDimensions.buttonCircleMinSize, minClickableSize = MaterialTheme.wireDimensions.buttonMinClickableSize, diff --git a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/recordaudio/RecordAudioButtons.kt b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/recordaudio/RecordAudioButtons.kt index 33e010dc9b1..487e0cc5171 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/recordaudio/RecordAudioButtons.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/recordaudio/RecordAudioButtons.kt @@ -74,7 +74,7 @@ fun RecordAudioButtonClose( WireTertiaryIconButton( onButtonClicked = onClick, iconResource = R.drawable.ic_close, - contentDescription = R.string.content_description_close_button, + contentDescription = R.string.label_close, shape = CircleShape, minSize = MaterialTheme.wireDimensions.buttonCircleMinSize, minClickableSize = MaterialTheme.wireDimensions.buttonMinClickableSize, diff --git a/app/src/main/kotlin/com/wire/android/ui/userprofile/self/dialog/LogoutOptionsDialog.kt b/app/src/main/kotlin/com/wire/android/ui/userprofile/self/dialog/LogoutOptionsDialog.kt index 221b012bcb6..b335e3713e8 100644 --- a/app/src/main/kotlin/com/wire/android/ui/userprofile/self/dialog/LogoutOptionsDialog.kt +++ b/app/src/main/kotlin/com/wire/android/ui/userprofile/self/dialog/LogoutOptionsDialog.kt @@ -52,7 +52,8 @@ fun LogoutOptionsDialog( dismissButtonProperties = WireDialogButtonProperties( onClick = dialogState::dismiss, text = stringResource(id = R.string.label_cancel), - state = WireButtonState.Default + state = WireButtonState.Default, + description = stringResource(R.string.dialog_logout_wipe_data_cancel_description) ), optionButton1Properties = WireDialogButtonProperties( onClick = remember(state) { { logout(state.shouldWipeData).also { dialogState.dismiss() } } }, diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0e080e0598c..ecf54741471 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -211,14 +211,18 @@ toggle setting Go back to conversation details Go back to conversation details + accept or ignore the request open profile open conversation open service change it open link Alert + Dropdown close dropdown open notification settings + Close new conversation view + Go back to new conversation view Go back to new conversation view Type group name Conversation options @@ -618,6 +622,7 @@ Clear Data? Delete all your personal information and conversations on this device + Cancel logout Set yourself to Available You will appear as Available to other people. You will receive notifications for incoming calls and for messages according to the Notifications setting in each conversation. diff --git a/core/ui-common/src/main/kotlin/com/wire/android/ui/common/WireDialog.kt b/core/ui-common/src/main/kotlin/com/wire/android/ui/common/WireDialog.kt index b4e0ab63aa6..c16829ab2df 100644 --- a/core/ui-common/src/main/kotlin/com/wire/android/ui/common/WireDialog.kt +++ b/core/ui-common/src/main/kotlin/com/wire/android/ui/common/WireDialog.kt @@ -41,6 +41,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.heading import androidx.compose.ui.semantics.paneTitle import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.AnnotatedString @@ -230,7 +231,7 @@ fun WireDialogContent( @Composable private fun TitleDialogSection(title: String, titleLoading: Boolean) { Row(verticalAlignment = Alignment.CenterVertically) { - Text(text = title, style = MaterialTheme.wireTypography.title02) + Text(text = title, style = MaterialTheme.wireTypography.title02, modifier = Modifier.semantics { heading() }) if (titleLoading) { WireCircularProgressIndicator(progressColor = MaterialTheme.wireColorScheme.onBackground) } @@ -281,13 +282,13 @@ private fun WireDialogButtonProperties?.getButton(modifier: Modifier = Modifier) Box(modifier = modifier) { when (type) { WireDialogButtonType.Primary -> - WirePrimaryButton(onClick = onClick, text = text, state = state, loading = loading) + WirePrimaryButton(onClick = onClick, text = text, state = state, loading = loading, description = description) WireDialogButtonType.Secondary -> - WireSecondaryButton(onClick = onClick, text = text, state = state, loading = loading) + WireSecondaryButton(onClick = onClick, text = text, state = state, loading = loading, description = description) WireDialogButtonType.Tertiary -> - WireTertiaryButton(onClick = onClick, text = text, state = state, loading = loading) + WireTertiaryButton(onClick = onClick, text = text, state = state, loading = loading, description = description) } } } @@ -300,7 +301,8 @@ data class WireDialogButtonProperties( val onClick: () -> Unit, val state: WireButtonState = WireButtonState.Default, val type: WireDialogButtonType = WireDialogButtonType.Secondary, - val loading: Boolean = false + val loading: Boolean = false, + val description: String? = null ) data class DialogTextSuffixLink(val linkText: String, val linkUrl: String) diff --git a/core/ui-common/src/main/kotlin/com/wire/android/ui/common/bottomsheet/ModalSheetHeaderItem.kt b/core/ui-common/src/main/kotlin/com/wire/android/ui/common/bottomsheet/ModalSheetHeaderItem.kt index 1f00db1864c..f43ac73b191 100644 --- a/core/ui-common/src/main/kotlin/com/wire/android/ui/common/bottomsheet/ModalSheetHeaderItem.kt +++ b/core/ui-common/src/main/kotlin/com/wire/android/ui/common/bottomsheet/ModalSheetHeaderItem.kt @@ -30,7 +30,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.heading import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.Dp import com.wire.android.ui.common.dimensions @@ -60,7 +60,7 @@ fun ModalSheetHeaderItem(header: MenuModalSheetHeader = MenuModalSheetHeader.Gon Text( text = header.title, style = MaterialTheme.wireTypography.title02, - modifier = Modifier.semantics { contentDescription = header.title } + modifier = Modifier.semantics { heading() } ) } WireDivider() 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 00b8e95b805..0e441d40c33 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 @@ -33,7 +33,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonElevation -import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.LocalMinimumInteractiveComponentSize import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text @@ -49,6 +48,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.clearAndSetSemantics +import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.onClick import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.TextStyle @@ -65,7 +66,6 @@ import com.wire.android.ui.theme.wireTypography import java.lang.Integer.max import kotlin.math.roundToInt -@OptIn(ExperimentalMaterial3Api::class) @Composable fun WireButton( onClick: () -> Unit, @@ -91,7 +91,8 @@ fun WireButton( vertical = MaterialTheme.wireDimensions.buttonVerticalContentPadding ), interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, - onClickDescription: String? = null + onClickDescription: String? = null, + description: String? = null ) { val border = when { borderWidth > 0.dp -> BorderStroke(width = borderWidth, color = colors.outlineColor(state).value) @@ -124,7 +125,10 @@ fun WireButton( placeable.place(centerX, centerY) } } - .semantics { onClickDescription?.let { onClick(it) { false } } }, + .semantics { + onClickDescription?.let { onClick(it) { false } } + description?.let { contentDescription = description } + }, enabled = state != WireButtonState.Disabled, interactionSource = interactionSource, elevation = elevation, @@ -144,6 +148,7 @@ fun WireButton( textStyle = textStyle, state = state, colors = colors, + semanticIgnoreText = !description.isNullOrEmpty() ) } } @@ -161,6 +166,7 @@ private fun InnerButtonBox( textStyle: TextStyle = MaterialTheme.wireTypography.button03, state: WireButtonState = WireButtonState.Default, colors: WireButtonColors = wirePrimaryButtonColors(), + semanticIgnoreText: Boolean = false ) { val contentColor = colors.contentColor(state).value val leadingItem: (@Composable () -> Unit) = { leadingIcon?.let { Tint(contentColor = contentColor, content = it) } } @@ -198,7 +204,9 @@ private fun InnerButtonBox( ) { if (leadingIconAlignment == IconAlignment.Center) leadingItem() if (!text.isNullOrEmpty()) { + val modifier = if (semanticIgnoreText) Modifier.clearAndSetSemantics { } else Modifier Text( + modifier = modifier, text = text, style = textStyle, color = contentColor diff --git a/core/ui-common/src/main/kotlin/com/wire/android/ui/common/button/WireItemLabel.kt b/core/ui-common/src/main/kotlin/com/wire/android/ui/common/button/WireItemLabel.kt index 8a5ae5ec23b..51bf9059129 100644 --- a/core/ui-common/src/main/kotlin/com/wire/android/ui/common/button/WireItemLabel.kt +++ b/core/ui-common/src/main/kotlin/com/wire/android/ui/common/button/WireItemLabel.kt @@ -33,6 +33,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.semantics.clearAndSetSemantics import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics import androidx.compose.ui.tooling.preview.Preview @@ -53,13 +54,14 @@ fun WireItemLabel( contentDescription: String = text ) = Box( modifier = modifier - .border(width = 1.dp, color = MaterialTheme.wireColorScheme.divider, shape = shape) + .border(width = 1.dp, color = MaterialTheme.wireColorScheme.secondaryButtonDisabledOutline, shape = shape) .padding(contentPadding) - .semantics(mergeDescendants = true) { this.contentDescription = contentDescription } + .semantics { this.contentDescription = contentDescription } .wrapContentWidth() .wrapContentHeight(), ) { Text( + modifier = Modifier.clearAndSetSemantics { }, text = text, style = MaterialTheme.wireTypography.label02, ) 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 48b08791764..7b1dc2af159 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 @@ -71,7 +71,8 @@ fun WirePrimaryButton( vertical = MaterialTheme.wireDimensions.buttonVerticalContentPadding ), interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, - onClickDescription: String? = null + onClickDescription: String? = null, + description: String? = null ) = WireButton( onClick = onClick, loading = loading, @@ -93,7 +94,8 @@ fun WirePrimaryButton( contentPadding = contentPadding, interactionSource = interactionSource, modifier = modifier, - onClickDescription = onClickDescription + onClickDescription = onClickDescription, + description = description ) @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 4045fa2d517..9fdf93fdb77 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 @@ -70,7 +70,8 @@ fun WireSecondaryButton( vertical = MaterialTheme.wireDimensions.buttonVerticalContentPadding ), interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, - onClickDescription: String? = null + onClickDescription: String? = null, + description: String? = null ) = WireButton( onClick = onClick, loading = loading, @@ -92,7 +93,8 @@ fun WireSecondaryButton( contentPadding = contentPadding, interactionSource = interactionSource, modifier = modifier, - onClickDescription = onClickDescription + onClickDescription = onClickDescription, + description = description ) @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 bcd77ea67bb..80696e05ecf 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 @@ -66,7 +66,8 @@ fun WireTertiaryButton( vertical = MaterialTheme.wireDimensions.buttonVerticalContentPadding ), interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, - onClickDescription: String? = null + onClickDescription: String? = null, + description: String? = null ) = WireButton( onClick = onClick, loading = loading, @@ -88,7 +89,8 @@ fun WireTertiaryButton( contentPadding = contentPadding, interactionSource = interactionSource, modifier = modifier, - onClickDescription = onClickDescription + onClickDescription = onClickDescription, + description = description ) @Preview(name = "Default WireSecondaryButton") diff --git a/core/ui-common/src/main/kotlin/com/wire/android/ui/common/topappbar/NavigationIconButton.kt b/core/ui-common/src/main/kotlin/com/wire/android/ui/common/topappbar/NavigationIconButton.kt index f0618adca53..dd0bb92a1b4 100644 --- a/core/ui-common/src/main/kotlin/com/wire/android/ui/common/topappbar/NavigationIconButton.kt +++ b/core/ui-common/src/main/kotlin/com/wire/android/ui/common/topappbar/NavigationIconButton.kt @@ -45,10 +45,10 @@ fun BackNavigationIconButton(onBackButtonClick: () -> Unit) { } sealed class NavigationIconType(val icon: ImageVector, @StringRes open val contentDescription: Int) { - data class Back(@StringRes override val contentDescription: Int = R.string.content_description_back_button) : + data class Back(@StringRes override val contentDescription: Int = R.string.content_description_left_arrow) : NavigationIconType(Icons.AutoMirrored.Filled.ArrowBack, contentDescription) - data class Close(@StringRes override val contentDescription: Int = R.string.content_description_close_button) : + data class Close(@StringRes override val contentDescription: Int = R.string.content_description_close) : NavigationIconType(Icons.Filled.Close, contentDescription) data object Menu : NavigationIconType(Icons.Filled.Menu, R.string.content_description_menu_button) diff --git a/core/ui-common/src/main/res/values/strings.xml b/core/ui-common/src/main/res/values/strings.xml index 88bc5693363..ae450d738d4 100644 --- a/core/ui-common/src/main/res/values/strings.xml +++ b/core/ui-common/src/main/res/values/strings.xml @@ -20,7 +20,9 @@ Please wait until the app is synchronized Please wait until the Internet connection is restored Back button + Go Back Close button + Close Main navigation Drop down arrow pending approval of connection request