diff --git a/app/src/main/kotlin/com/wire/android/mapper/OtherAccountMapper.kt b/app/src/main/kotlin/com/wire/android/mapper/OtherAccountMapper.kt
index 3a9b0d2889e..1c4fdf946b9 100644
--- a/app/src/main/kotlin/com/wire/android/mapper/OtherAccountMapper.kt
+++ b/app/src/main/kotlin/com/wire/android/mapper/OtherAccountMapper.kt
@@ -20,15 +20,14 @@ package com.wire.android.mapper
import com.wire.android.ui.home.conversations.avatar
import com.wire.android.ui.userprofile.self.model.OtherAccount
-import com.wire.kalium.logic.data.team.Team
import com.wire.kalium.logic.data.user.SelfUser
import javax.inject.Inject
class OtherAccountMapper @Inject constructor() {
- fun toOtherAccount(selfUser: SelfUser, team: Team?): OtherAccount = OtherAccount(
+ fun toOtherAccount(selfUser: SelfUser): OtherAccount = OtherAccount(
id = selfUser.id,
fullName = selfUser.name ?: "",
avatarData = selfUser.avatar(selfUser.connectionStatus),
- teamName = team?.name
+ handle = selfUser.handle
)
}
diff --git a/app/src/main/kotlin/com/wire/android/ui/userprofile/self/OtherAccounts.kt b/app/src/main/kotlin/com/wire/android/ui/userprofile/self/OtherAccounts.kt
new file mode 100644
index 00000000000..a566c94cd60
--- /dev/null
+++ b/app/src/main/kotlin/com/wire/android/ui/userprofile/self/OtherAccounts.kt
@@ -0,0 +1,67 @@
+package com.wire.android.ui.userprofile.self
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentWidth
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import com.wire.android.R
+import com.wire.android.model.Clickable
+import com.wire.android.ui.common.ArrowRightIcon
+import com.wire.android.ui.common.RowItemTemplate
+import com.wire.android.ui.common.avatar.UserProfileAvatar
+import com.wire.android.ui.common.dimensions
+import com.wire.android.ui.home.conversations.search.HighlightName
+import com.wire.android.ui.home.conversations.search.HighlightSubtitle
+import com.wire.android.ui.home.conversationslist.common.FolderHeader
+import com.wire.android.ui.theme.wireDimensions
+import com.wire.android.ui.userprofile.self.model.OtherAccount
+
+@Composable
+internal fun OtherAccountsHeader() {
+ FolderHeader(stringResource(id = R.string.user_profile_other_accs))
+}
+
+@Composable
+internal fun OtherAccountItem(
+ account: OtherAccount,
+ clickable: Clickable = Clickable(enabled = true) {},
+) {
+ RowItemTemplate(
+ leadingIcon = { UserProfileAvatar(account.avatarData) },
+ titleStartPadding = dimensions().spacing0x,
+ title = {
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ HighlightName(
+ name = account.fullName,
+ modifier = Modifier.weight(weight = 1f, fill = false),
+ searchQuery = ""
+ )
+ }
+ },
+ subtitle = {
+ val subTitle = buildString {
+ append(account.handle ?: "")
+ if (account.id.domain.isNotBlank()) {
+ append("@${account.id.domain}")
+ }
+ }
+ HighlightSubtitle(subTitle = subTitle)
+ },
+ actions = {
+ Box(
+ modifier = Modifier
+ .wrapContentWidth()
+ .padding(end = MaterialTheme.wireDimensions.spacing8x)
+ ) {
+ ArrowRightIcon(Modifier.align(Alignment.TopEnd), R.string.content_description_empty)
+ }
+ },
+ clickable = clickable,
+ modifier = Modifier.padding(start = dimensions().spacing8x)
+ )
+}
diff --git a/app/src/main/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileScreen.kt b/app/src/main/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileScreen.kt
index 70a44582e10..e09da4b1532 100644
--- a/app/src/main/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileScreen.kt
+++ b/app/src/main/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileScreen.kt
@@ -27,16 +27,16 @@ import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.scrollable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
+import androidx.compose.material.Divider
+import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
@@ -44,6 +44,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.DialogProperties
@@ -60,12 +61,9 @@ import com.wire.android.navigation.NavigationCommand
import com.wire.android.navigation.Navigator
import com.wire.android.navigation.WireDestination
import com.wire.android.navigation.style.PopUpNavigationAnimation
-import com.wire.android.ui.common.ArrowRightIcon
-import com.wire.android.ui.common.RowItemTemplate
-import com.wire.android.ui.common.avatar.UserProfileAvatar
-import com.wire.android.ui.common.avatar.UserStatusIndicator
import com.wire.android.ui.common.VisibilityState
import com.wire.android.ui.common.WireDropDown
+import com.wire.android.ui.common.avatar.UserStatusIndicator
import com.wire.android.ui.common.button.WireButtonState
import com.wire.android.ui.common.button.WirePrimaryButton
import com.wire.android.ui.common.button.WireSecondaryButton
@@ -73,16 +71,16 @@ import com.wire.android.ui.common.dialogs.ProgressDialog
import com.wire.android.ui.common.dimensions
import com.wire.android.ui.common.scaffold.WireScaffold
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.visbility.rememberVisibilityState
import com.wire.android.ui.destinations.AppSettingsScreenDestination
import com.wire.android.ui.destinations.AvatarPickerScreenDestination
+import com.wire.android.ui.destinations.MyAccountScreenDestination
import com.wire.android.ui.destinations.SelfQRCodeScreenDestination
import com.wire.android.ui.destinations.TeamMigrationScreenDestination
import com.wire.android.ui.destinations.WelcomeScreenDestination
-import com.wire.android.ui.home.conversations.search.HighlightName
-import com.wire.android.ui.home.conversations.search.HighlightSubtitle
import com.wire.android.ui.home.conversationslist.common.FolderHeader
import com.wire.android.ui.legalhold.banner.LegalHoldPendingBanner
import com.wire.android.ui.legalhold.banner.LegalHoldSubjectBanner
@@ -92,6 +90,7 @@ import com.wire.android.ui.legalhold.dialog.requested.LegalHoldRequestedState
import com.wire.android.ui.legalhold.dialog.requested.LegalHoldRequestedViewModel
import com.wire.android.ui.legalhold.dialog.subject.LegalHoldSubjectProfileSelfDialog
import com.wire.android.ui.theme.WireTheme
+import com.wire.android.ui.theme.wireColorScheme
import com.wire.android.ui.theme.wireDimensions
import com.wire.android.ui.userprofile.common.EditableState
import com.wire.android.ui.userprofile.common.UserProfileInfo
@@ -123,7 +122,13 @@ fun SelfUserProfileScreen(
state = viewModelSelf.userProfileState,
onCloseClick = navigator::navigateBack,
logout = { viewModelSelf.logout(it, NavigationSwitchAccountActions(navigator::navigate)) },
- onChangeUserProfilePicture = { navigator.navigate(NavigationCommand(AvatarPickerScreenDestination)) },
+ onChangeUserProfilePicture = {
+ navigator.navigate(
+ NavigationCommand(
+ AvatarPickerScreenDestination
+ )
+ )
+ },
onEditClick = { navigator.navigate(NavigationCommand(AppSettingsScreenDestination)) },
onStatusClicked = viewModelSelf::changeStatusClick,
onAddAccountClick = { navigator.navigate(NavigationCommand(WelcomeScreenDestination)) },
@@ -133,7 +138,12 @@ fun SelfUserProfileScreen(
onMessageShown = viewModelSelf::clearErrorMessage,
onLegalHoldAcceptClick = legalHoldRequestedViewModel::show,
onLegalHoldLearnMoreClick = remember { { legalHoldSubjectDialogState.show(Unit) } },
- onOtherAccountClick = { viewModelSelf.switchAccount(it, NavigationSwitchAccountActions(navigator::navigate)) },
+ onOtherAccountClick = {
+ viewModelSelf.switchAccount(
+ it,
+ NavigationSwitchAccountActions(navigator::navigate)
+ )
+ },
onQrCodeClick = {
viewModelSelf.trackQrCodeClick()
navigator.navigate(NavigationCommand(SelfQRCodeScreenDestination(viewModelSelf.userProfileState.userName)))
@@ -142,6 +152,7 @@ fun SelfUserProfileScreen(
viewModelSelf.sendPersonalToTeamMigrationEvent()
navigator.navigate(NavigationCommand(TeamMigrationScreenDestination))
},
+ onAccountDetailsClick = { navigator.navigate(NavigationCommand(MyAccountScreenDestination)) },
isUserInCall = viewModelSelf::isUserInCall,
)
@@ -193,6 +204,7 @@ private fun SelfUserProfileContent(
onOtherAccountClick: (UserId) -> Unit = {},
onQrCodeClick: () -> Unit = {},
onCreateAccount: () -> Unit = {},
+ onAccountDetailsClick: () -> Unit = {},
isUserInCall: () -> Boolean
) {
val snackbarHostState = LocalSnackbarHostState.current
@@ -212,7 +224,11 @@ private fun SelfUserProfileContent(
SelfUserProfileTopBar(
onCloseClick = onCloseClick,
onLogoutClick = remember {
- { logoutOptionsDialogState.show(logoutOptionsDialogState.savedState ?: LogoutOptionsDialogState()) }
+ {
+ logoutOptionsDialogState.show(
+ logoutOptionsDialogState.savedState ?: LogoutOptionsDialogState()
+ )
+ }
}
)
}
@@ -284,12 +300,19 @@ private fun SelfUserProfileContent(
stickyHeader {
CurrentSelfUserStatus(
userStatus = status,
- onStatusClicked = onStatusClicked
+ onStatusClicked = onStatusClicked,
)
}
}
+ stickyHeader {
+ VerticalSpace.x8()
+ Box(modifier = Modifier.padding(horizontal = MaterialTheme.wireDimensions.spacing16x)) {
+ AccountDetailButton(onAccountDetailsClick = onAccountDetailsClick)
+ }
+ }
if (state.otherAccounts.isNotEmpty()) {
stickyHeader {
+ VerticalSpace.x16()
OtherAccountsHeader()
}
items(
@@ -298,24 +321,33 @@ private fun SelfUserProfileContent(
OtherAccountItem(
account = account,
clickable = remember {
- Clickable(enabled = true, onClickDescription = selectLabel, onClick = {
- if (isUserInCall()) {
- Toast.makeText(
- context,
- context.getString(R.string.cant_switch_account_in_call),
- Toast.LENGTH_SHORT
- ).show()
- } else {
- onOtherAccountClick(account.id)
+ Clickable(
+ enabled = true,
+ onClickDescription = selectLabel,
+ onClick = {
+ if (isUserInCall()) {
+ Toast.makeText(
+ context,
+ context.getString(R.string.cant_switch_account_in_call),
+ Toast.LENGTH_SHORT
+ ).show()
+ } else {
+ onOtherAccountClick(account.id)
+ }
}
- })
+ )
}
)
}
)
}
}
- NewTeamButton(onAddAccountClick, isUserInCall, context)
+
+ Divider(color = MaterialTheme.wireColorScheme.outline)
+
+ Box(modifier = Modifier.padding(dimensions().spacing16x)) {
+ NewTeamButton(onAddAccountClick, isUserInCall, context)
+ }
}
ChangeStatusDialogContent(
data = statusDialogData,
@@ -362,7 +394,10 @@ private fun SelfUserProfileTopBar(
minSize = MaterialTheme.wireDimensions.buttonSmallMinSize,
minClickableSize = MaterialTheme.wireDimensions.buttonMinClickableSize,
state = WireButtonState.Error,
- clickBlockParams = ClickBlockParams(blockWhenSyncing = false, blockWhenConnecting = false),
+ clickBlockParams = ClickBlockParams(
+ blockWhenSyncing = false,
+ blockWhenConnecting = false
+ ),
)
}
)
@@ -371,7 +406,7 @@ private fun SelfUserProfileTopBar(
@Composable
private fun CurrentSelfUserStatus(
userStatus: UserAvailabilityStatus,
- onStatusClicked: (UserAvailabilityStatus) -> Unit
+ onStatusClicked: (UserAvailabilityStatus) -> Unit,
) {
val items = listOf(
UserAvailabilityStatus.AVAILABLE,
@@ -394,11 +429,7 @@ private fun CurrentSelfUserStatus(
},
defaultItemIndex = items.indexOf(userStatus),
label = null,
- modifier = Modifier.padding(
- bottom = MaterialTheme.wireDimensions.spacing16x,
- start = MaterialTheme.wireDimensions.spacing16x,
- end = MaterialTheme.wireDimensions.spacing16x
- ),
+ modifier = Modifier.padding(horizontal = MaterialTheme.wireDimensions.spacing16x),
autoUpdateSelection = false,
showDefaultTextIndicator = false,
leadingCompose = { index -> UserStatusIndicator(items[index]) },
@@ -409,75 +440,52 @@ private fun CurrentSelfUserStatus(
}
}
-@Composable
-private fun OtherAccountsHeader() {
- FolderHeader(stringResource(id = R.string.user_profile_other_accs))
-}
-
@Composable
private fun NewTeamButton(
onAddAccountClick: () -> Unit,
isUserIdCall: () -> Boolean,
context: Context
) {
- Surface(shadowElevation = dimensions().spacing8x) {
- WirePrimaryButton(
- modifier = Modifier
- .background(MaterialTheme.colorScheme.background)
- .padding(dimensions().spacing16x)
- .testTag("New Team or Account"),
- text = stringResource(R.string.user_profile_new_account_text),
- onClickDescription = stringResource(R.string.content_description_self_profile_new_account_btn),
- onClick = remember {
- {
- if (isUserIdCall()) {
- Toast.makeText(
- context,
- context.getString(R.string.cant_switch_account_in_call),
- Toast.LENGTH_SHORT
- ).show()
- } else {
- onAddAccountClick()
- }
+ WirePrimaryButton(
+ modifier = Modifier
+ .testTag("New Team or Account"),
+ text = stringResource(R.string.user_profile_new_account_text),
+ onClickDescription = stringResource(R.string.content_description_self_profile_new_account_btn),
+ onClick = remember {
+ {
+ if (isUserIdCall()) {
+ Toast.makeText(
+ context,
+ context.getString(R.string.cant_switch_account_in_call),
+ Toast.LENGTH_SHORT
+ ).show()
+ } else {
+ onAddAccountClick()
}
}
- )
- }
+ }
+ )
}
@Composable
-private fun OtherAccountItem(
- account: OtherAccount,
- clickable: Clickable = Clickable(enabled = true) {}
+private fun AccountDetailButton(
+ onAccountDetailsClick: () -> Unit,
) {
- RowItemTemplate(
- leadingIcon = { UserProfileAvatar(account.avatarData) },
- titleStartPadding = dimensions().spacing0x,
- title = {
- Row(verticalAlignment = Alignment.CenterVertically) {
- HighlightName(
- name = account.fullName,
- modifier = Modifier.weight(weight = 1f, fill = false),
- searchQuery = ""
- )
- }
- },
- subtitle = {
- if (account.teamName != null) {
- HighlightSubtitle(subTitle = account.teamName, prefix = "")
- }
- },
- actions = {
- Box(
+ WireSecondaryButton(
+ modifier = Modifier
+ .testTag("Account details"),
+ text = stringResource(R.string.settings_your_account_label),
+ trailingIcon = {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_arrow_right),
+ contentDescription = "",
+ tint = MaterialTheme.wireColorScheme.onSecondaryButtonEnabled,
modifier = Modifier
- .wrapContentWidth()
- .padding(end = MaterialTheme.wireDimensions.spacing8x)
- ) {
- ArrowRightIcon(Modifier.align(Alignment.TopEnd), R.string.content_description_empty)
- }
+ .defaultMinSize(dimensions().wireIconButtonSize)
+ .padding(end = dimensions().spacing8x)
+ )
},
- clickable = clickable,
- modifier = Modifier.padding(start = dimensions().spacing8x)
+ onClick = onAccountDetailsClick,
)
}
@@ -486,7 +494,11 @@ private fun LoggingOutDialog(isLoggingOut: Boolean) {
if (isLoggingOut) {
ProgressDialog(
title = stringResource(R.string.user_profile_logging_out_progress),
- properties = DialogProperties(dismissOnBackPress = false, dismissOnClickOutside = false, usePlatformDefaultWidth = true)
+ properties = DialogProperties(
+ dismissOnBackPress = false,
+ dismissOnClickOutside = false,
+ usePlatformDefaultWidth = true
+ )
)
}
}
@@ -503,13 +515,21 @@ fun PreviewSelfUserProfileScreen() {
userName = "userName_long_long_long_long_long_long_long_long_long_long",
teamName = "Best team ever long long long long long long long long long ",
otherAccounts = listOf(
- OtherAccount(id = UserId("id1", "domain"), fullName = "Other Name", teamName = "team A"),
- OtherAccount(id = UserId("id2", "domain"), fullName = "New Name")
+ OtherAccount(
+ id = UserId("id1", "domain"),
+ fullName = "Other Name",
+ handle = "userName",
+ ),
+ OtherAccount(
+ id = UserId("id2", "domain"),
+ fullName = "New Name",
+ handle = "userName",
+ )
),
statusDialogData = null,
legalHoldStatus = LegalHoldUIState.Active,
),
- isUserInCall = { false }
+ isUserInCall = { false },
)
}
}
@@ -526,8 +546,16 @@ fun PersonalSelfUserProfileScreenPreview() {
userName = "some-user",
teamName = null,
otherAccounts = listOf(
- OtherAccount(id = UserId("id1", "domain"), fullName = "Other Name", teamName = "team A"),
- OtherAccount(id = UserId("id2", "domain"), fullName = "New Name")
+ OtherAccount(
+ id = UserId("id1", "domain"),
+ fullName = "Other Name",
+ handle = "userName",
+ ),
+ OtherAccount(
+ id = UserId("id2", "domain"),
+ fullName = "New Name",
+ handle = "userName",
+ )
),
statusDialogData = null,
legalHoldStatus = LegalHoldUIState.Active,
@@ -541,7 +569,10 @@ fun PersonalSelfUserProfileScreenPreview() {
@Composable
fun PreviewCurrentSelfUserStatus() {
WireTheme {
- CurrentSelfUserStatus(UserAvailabilityStatus.AVAILABLE, onStatusClicked = {})
+ CurrentSelfUserStatus(
+ UserAvailabilityStatus.AVAILABLE,
+ onStatusClicked = {},
+ )
}
}
diff --git a/app/src/main/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileViewModel.kt
index 36439af8e3c..7ea1e925b6b 100644
--- a/app/src/main/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileViewModel.kt
+++ b/app/src/main/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileViewModel.kt
@@ -161,7 +161,7 @@ class SelfUserProfileViewModel @Inject constructor(
Pair(
selfUser,
list.filter { it.first.id != selfUser.id }
- .map { (selfUser, team) -> otherAccountMapper.toOtherAccount(selfUser, team) }
+ .map { (selfUser, _) -> otherAccountMapper.toOtherAccount(selfUser) }
)
}
.distinctUntilChanged()
diff --git a/app/src/main/kotlin/com/wire/android/ui/userprofile/self/model/OtherAccount.kt b/app/src/main/kotlin/com/wire/android/ui/userprofile/self/model/OtherAccount.kt
index d9d273f62c9..26d22260595 100644
--- a/app/src/main/kotlin/com/wire/android/ui/userprofile/self/model/OtherAccount.kt
+++ b/app/src/main/kotlin/com/wire/android/ui/userprofile/self/model/OtherAccount.kt
@@ -26,5 +26,5 @@ data class OtherAccount(
val id: UserId,
val fullName: String,
val avatarData: UserAvatarData = UserAvatarData(),
- val teamName: String? = null
+ val handle: String?,
)
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 95fafc74212..9309f671a99 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -594,14 +594,14 @@
User Profile
Close User profile
- Log out
+ Logout
Your Other Accounts
Availability
Available
Busy
Away
None
- New Team or Account
+ New Team or Add Account
Details
Devices
Group
diff --git a/app/src/test/kotlin/com/wire/android/mapper/OtherAccountMapperTest.kt b/app/src/test/kotlin/com/wire/android/mapper/OtherAccountMapperTest.kt
index 2f619df0c9b..3f02c87dacb 100644
--- a/app/src/test/kotlin/com/wire/android/mapper/OtherAccountMapperTest.kt
+++ b/app/src/test/kotlin/com/wire/android/mapper/OtherAccountMapperTest.kt
@@ -23,11 +23,9 @@ import com.wire.android.ui.userprofile.self.model.OtherAccount
import com.wire.kalium.logic.data.team.Team
import com.wire.kalium.logic.data.user.SelfUser
import io.mockk.MockKAnnotations
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
-@OptIn(ExperimentalCoroutinesApi::class)
class OtherAccountMapperTest {
@Test
@@ -39,23 +37,22 @@ class OtherAccountMapperTest {
testSelfUser(1) to null
)
// When
- val results = data.map { (selfUser, team) -> mapper.toOtherAccount(selfUser, team) }
+ val results = data.map { (selfUser, _) -> mapper.toOtherAccount(selfUser) }
// Then
results.forEachIndexed { index, result ->
- val (selfUser, team) = data[index]
- assert(compareResult(selfUser, team, result))
+ val (selfUser, _) = data[index]
+ assert(compareResult(selfUser, result))
}
}
private fun compareResult(
selfUser: SelfUser,
- team: Team?,
otherAccount: OtherAccount
): Boolean =
selfUser.id == otherAccount.id
&& selfUser.name == otherAccount.fullName
&& selfUser.avatar(selfUser.connectionStatus) == otherAccount.avatarData
- && team?.name == otherAccount.teamName
+ && selfUser.handle == otherAccount.handle
private class Arrangement {