Move and refactor MatrixUser (#381)

Move and refactor MatrixUser

Instead of living in matrixui and having an AvatarData, this can
reside in the matrix module and just have the URL. An extension
method in matrixui can then provide the AvatarData when required.

This removes some small duplication, and pushes the UI-specific
information (i.e., what size of avatar is going to be rendered)
further down the stack. It also aligns the field names with those
used by the rust SDK (e.g. "displayName" instead of "userName").
This commit is contained in:
Chris Smith 2023-05-03 17:30:19 +01:00 committed by GitHub
parent 271c66588a
commit 581c5ab2d2
51 changed files with 125 additions and 215 deletions

View file

@ -17,11 +17,9 @@
package io.element.android.features.createroom.impl
import io.element.android.features.userlist.api.UserListDataSource
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.usersearch.MatrixUserProfile
import io.element.android.libraries.matrix.ui.model.MatrixUser
import io.element.android.libraries.matrix.api.user.MatrixUser
import javax.inject.Inject
class AllMatrixUsersDataSource @Inject constructor(
@ -29,7 +27,7 @@ class AllMatrixUsersDataSource @Inject constructor(
) : UserListDataSource {
override suspend fun search(query: String): List<MatrixUser> {
val res = client.searchUsers(query, MAX_SEARCH_RESULTS)
return res.getOrNull()?.results?.map(::toMatrixUser).orEmpty()
return res.getOrNull()?.results.orEmpty()
}
override suspend fun getProfile(userId: UserId): MatrixUser? {
@ -37,16 +35,6 @@ class AllMatrixUsersDataSource @Inject constructor(
return null
}
private fun toMatrixUser(matrixUserProfile: MatrixUserProfile) = MatrixUser(
id = matrixUserProfile.userId,
username = matrixUserProfile.displayName,
avatarData = AvatarData(
id = matrixUserProfile.userId.value,
name = matrixUserProfile.displayName,
url = matrixUserProfile.avatarUrl,
)
)
companion object {
private const val MAX_SEARCH_RESULTS = 5L
}

View file

@ -17,7 +17,7 @@
package io.element.android.features.createroom.impl
import io.element.android.features.createroom.impl.configureroom.RoomPrivacy
import io.element.android.libraries.matrix.ui.model.MatrixUser
import io.element.android.libraries.matrix.api.user.MatrixUser
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf

View file

@ -18,7 +18,7 @@ package io.element.android.features.createroom.impl.configureroom
import android.net.Uri
import io.element.android.features.createroom.impl.CreateRoomConfig
import io.element.android.libraries.matrix.ui.model.MatrixUser
import io.element.android.libraries.matrix.api.user.MatrixUser
sealed interface ConfigureRoomEvents {
data class RoomNameChanged(val name: String) : ConfigureRoomEvents

View file

@ -89,7 +89,7 @@ class ConfigureRoomPresenter @Inject constructor(
isDirect = false,
visibility = if (config.privacy == RoomPrivacy.Public) RoomVisibility.PUBLIC else RoomVisibility.PRIVATE,
preset = if (config.privacy == RoomPrivacy.Public) RoomPreset.PUBLIC_CHAT else RoomPreset.PRIVATE_CHAT,
invite = config.invites.map { it.id },
invite = config.invites.map { it.userId },
avatar = config.avatarUrl,
)
matrixClient.createRoom(params).getOrThrow()

View file

@ -16,7 +16,7 @@
package io.element.android.features.createroom.impl.configureroom
import io.element.android.libraries.matrix.ui.model.MatrixUser
import io.element.android.libraries.matrix.api.user.MatrixUser
data class ConfigureRoomPresenterArgs(
val selectedUsers: List<MatrixUser>,

View file

@ -16,7 +16,7 @@
package io.element.android.features.createroom.impl.root
import io.element.android.libraries.matrix.ui.model.MatrixUser
import io.element.android.libraries.matrix.api.user.MatrixUser
sealed interface CreateRoomRootEvents {
object InvitePeople : CreateRoomRootEvents

View file

@ -31,7 +31,7 @@ import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.architecture.execute
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.ui.model.MatrixUser
import io.element.android.libraries.matrix.api.user.MatrixUser
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import javax.inject.Inject
@ -65,7 +65,7 @@ class CreateRoomRootPresenter @Inject constructor(
fun startDm(matrixUser: MatrixUser) {
startDmAction.value = Async.Uninitialized
val existingDM = matrixClient.findDM(matrixUser.id)
val existingDM = matrixClient.findDM(matrixUser.userId)
if (existingDM == null) {
localCoroutineScope.createDM(matrixUser, startDmAction)
} else {
@ -90,7 +90,7 @@ class CreateRoomRootPresenter @Inject constructor(
private fun CoroutineScope.createDM(user: MatrixUser, startDmAction: MutableState<Async<RoomId>>) = launch {
suspend {
matrixClient.createDM(user.id).getOrThrow()
matrixClient.createDM(user.userId).getOrThrow()
}.execute(startDmAction)
}
}

View file

@ -31,7 +31,7 @@ open class CreateRoomRootStateProvider : PreviewParameterProvider<CreateRoomRoot
startDmAction = Async.Loading(),
userListState = aMatrixUser().let {
aUserListState().copy(
searchQuery = it.id.value,
searchQuery = it.userId.value,
searchResults = UserSearchResultState.Results(persistentListOf(it)),
selectedUsers = persistentListOf(it),
isSearchActive = true,
@ -42,7 +42,7 @@ open class CreateRoomRootStateProvider : PreviewParameterProvider<CreateRoomRoot
startDmAction = Async.Failure(Throwable()),
userListState = aMatrixUser().let {
aUserListState().copy(
searchQuery = it.id.value,
searchQuery = it.userId.value,
searchResults = UserSearchResultState.Results(persistentListOf(it)),
selectedUsers = persistentListOf(it),
isSearchActive = true,

View file

@ -17,16 +17,14 @@
package io.element.android.features.createroom.impl
import com.google.common.truth.Truth
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.usersearch.MatrixSearchUserResults
import io.element.android.libraries.matrix.api.usersearch.MatrixUserProfile
import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
import io.element.android.libraries.matrix.test.A_USER_ID
import io.element.android.libraries.matrix.test.A_USER_ID_2
import io.element.android.libraries.matrix.test.A_USER_NAME
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.ui.model.MatrixUser
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.Test
@ -51,15 +49,15 @@ internal class AllMatrixUsersDataSourceTest {
val results = dataSource.search("test")
Truth.assertThat(results).containsExactly(
MatrixUser(
id = A_USER_ID,
username = A_USER_NAME,
avatarData = AvatarData(id = A_USER_ID.value, name = A_USER_NAME, url = AN_AVATAR_URL)
userId = A_USER_ID,
displayName = A_USER_NAME,
avatarUrl = AN_AVATAR_URL
),
MatrixUser(
id = A_USER_ID_2,
username = A_USER_NAME,
avatarData = AvatarData(id = A_USER_ID_2.value, name = A_USER_NAME, url = AN_AVATAR_URL)
)
userId = A_USER_ID_2,
displayName = A_USER_NAME,
avatarUrl = AN_AVATAR_URL
),
)
}
@ -80,5 +78,5 @@ internal class AllMatrixUsersDataSourceTest {
userId: UserId = A_USER_ID,
displayName: String = A_USER_NAME,
avatarUrl: String = AN_AVATAR_URL
) = MatrixUserProfile(userId, displayName, avatarUrl)
) = MatrixUser(userId, displayName, avatarUrl)
}

View file

@ -30,10 +30,10 @@ import io.element.android.features.userlist.test.FakeUserListPresenterFactory
import io.element.android.libraries.architecture.Async
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.test.A_THROWABLE
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import io.element.android.libraries.matrix.ui.model.MatrixUser
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest

View file

@ -19,7 +19,7 @@ package io.element.android.features.preferences.impl.root
import io.element.android.features.logout.api.LogoutPreferenceState
import io.element.android.features.rageshake.api.preferences.RageshakePreferencesState
import io.element.android.libraries.architecture.Async
import io.element.android.libraries.matrix.ui.model.MatrixUser
import io.element.android.libraries.matrix.api.user.MatrixUser
data class PreferencesRootState(
val logoutState: LogoutPreferenceState,

View file

@ -32,8 +32,8 @@ import io.element.android.libraries.designsystem.components.preferences.Preferen
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.preview.LargeHeightPreview
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.ui.components.MatrixUserProvider
import io.element.android.libraries.matrix.ui.model.MatrixUser
import io.element.android.libraries.ui.strings.R as StringR
@Composable

View file

@ -26,9 +26,9 @@ import androidx.compose.ui.unit.dp
import io.element.android.libraries.architecture.Async
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.ui.components.MatrixUserHeader
import io.element.android.libraries.matrix.ui.components.MatrixUserWithNullProvider
import io.element.android.libraries.matrix.ui.model.MatrixUser
@Composable
fun UserPreferences(

View file

@ -16,7 +16,7 @@
package io.element.android.features.roomdetails.impl.members
import io.element.android.libraries.matrix.ui.model.MatrixUser
import io.element.android.libraries.matrix.api.user.MatrixUser
sealed interface RoomMemberListEvents {
data class SelectUser(val user: MatrixUser) : RoomMemberListEvents

View file

@ -29,7 +29,7 @@ import io.element.android.libraries.architecture.Async
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.ui.model.MatrixUser
import io.element.android.libraries.matrix.api.user.MatrixUser
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.withContext

View file

@ -18,8 +18,7 @@ package io.element.android.features.roomdetails.impl.members
import io.element.android.features.userlist.api.UserListState
import io.element.android.libraries.architecture.Async
import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.ui.model.MatrixUser
import io.element.android.libraries.matrix.api.user.MatrixUser
import kotlinx.collections.immutable.ImmutableList
data class RoomMemberListState(

View file

@ -20,8 +20,8 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.userlist.api.UserSearchResultState
import io.element.android.features.userlist.api.aUserListState
import io.element.android.libraries.architecture.Async
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.ui.components.aMatrixUser
import io.element.android.libraries.matrix.ui.model.MatrixUser
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf

View file

@ -52,7 +52,7 @@ import io.element.android.libraries.designsystem.theme.components.CircularProgre
import io.element.android.libraries.designsystem.theme.components.Scaffold
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.ui.model.MatrixUser
import io.element.android.libraries.matrix.api.user.MatrixUser
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@ -64,7 +64,7 @@ fun RoomMemberListView(
) {
fun onUserSelected(user: MatrixUser) {
onMemberSelected(user.id)
onMemberSelected(user.userId)
}
Scaffold(

View file

@ -19,13 +19,13 @@ package io.element.android.features.roomdetails.impl.members
import io.element.android.features.userlist.api.UserListDataSource
import io.element.android.libraries.core.bool.orFalse
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.api.room.roomMembers
import io.element.android.libraries.matrix.ui.model.MatrixUser
import io.element.android.libraries.matrix.api.room.toMatrixUser
import io.element.android.libraries.matrix.api.user.MatrixUser
import kotlinx.coroutines.flow.dropWhile
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withContext
@ -50,22 +50,11 @@ class RoomUserListDataSource @Inject constructor(
|| member.displayName?.contains(query, ignoreCase = true).orFalse()
}
}
filteredMembers.map(::mapMemberToMatrixUser)
filteredMembers.map(RoomMember::toMatrixUser)
}
override suspend fun getProfile(userId: UserId): MatrixUser? {
return null
}
private fun mapMemberToMatrixUser(member: RoomMember): MatrixUser {
return MatrixUser(
id = member.userId,
username = member.displayName,
avatarData = AvatarData(
id = member.userId.value,
name = member.displayName,
url = member.avatarUrl
)
)
}
}

View file

@ -27,6 +27,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import io.element.android.features.networkmonitor.api.NetworkMonitor
import io.element.android.features.networkmonitor.api.NetworkStatus
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
import io.element.android.features.roomlist.impl.model.RoomListRoomSummaryPlaceholders
import io.element.android.libraries.architecture.Presenter
@ -34,17 +35,15 @@ import io.element.android.libraries.core.coroutine.parallelMap
import io.element.android.libraries.core.extensions.orEmpty
import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormatter
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.utils.SnackbarDispatcher
import io.element.android.libraries.designsystem.utils.handleSnackbarMessage
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.RoomSummary
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus
import io.element.android.libraries.matrix.ui.model.MatrixUser
import io.element.android.features.networkmonitor.api.NetworkStatus
import io.element.android.libraries.matrix.api.core.RoomId
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
@ -153,17 +152,10 @@ class RoomListPresenter @Inject constructor(
private fun CoroutineScope.initialLoad(matrixUser: MutableState<MatrixUser?>) = launch {
val userAvatarUrl = client.loadUserAvatarURLString().getOrNull()
val userDisplayName = client.loadUserDisplayName().getOrNull()
val avatarData =
AvatarData(
id = client.sessionId.value,
name = userDisplayName,
url = userAvatarUrl,
size = AvatarSize.SMALL
)
matrixUser.value = MatrixUser(
id = UserId(client.sessionId.value),
username = userDisplayName ?: client.sessionId.value,
avatarData = avatarData,
userId = UserId(client.sessionId.value),
displayName = userDisplayName,
avatarUrl = userAvatarUrl,
)
}

View file

@ -19,7 +19,7 @@ package io.element.android.features.roomlist.impl
import androidx.compose.runtime.Immutable
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
import io.element.android.libraries.designsystem.utils.SnackbarMessage
import io.element.android.libraries.matrix.ui.model.MatrixUser
import io.element.android.libraries.matrix.api.user.MatrixUser
import kotlinx.collections.immutable.ImmutableList
@Immutable

View file

@ -23,7 +23,7 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.utils.SnackbarMessage
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.ui.model.MatrixUser
import io.element.android.libraries.matrix.api.user.MatrixUser
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import io.element.android.libraries.ui.strings.R as StringR
@ -42,7 +42,7 @@ open class RoomListStateProvider : PreviewParameterProvider<RoomListState> {
}
internal fun aRoomListState() = RoomListState(
matrixUser = MatrixUser(id = UserId("@id:domain"), username = "User#1", avatarData = AvatarData("@id:domain", "U")),
matrixUser = MatrixUser(userId = UserId("@id:domain"), displayName = "User#1"),
roomList = aRoomListRoomSummaryList(),
filter = "filter",
filteredRoomList = aRoomListRoomSummaryList(),

View file

@ -27,6 +27,9 @@ import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.stringResource
@ -35,7 +38,6 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import io.element.android.features.roomlist.impl.R
import io.element.android.libraries.designsystem.components.avatar.Avatar
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.theme.components.Icon
@ -44,7 +46,8 @@ import io.element.android.libraries.designsystem.theme.components.MediumTopAppBa
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.utils.LogCompositions
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.ui.model.MatrixUser
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.ui.model.getAvatarData
import io.element.android.libraries.testtags.TestTags
import io.element.android.libraries.testtags.testTag
import io.element.android.libraries.ui.strings.R as StringR
@ -107,7 +110,8 @@ private fun DefaultRoomListTopBar(
modifier = Modifier.testTag(TestTags.homeScreenSettings),
onClick = onOpenSettings
) {
Avatar(matrixUser.avatarData, contentDescription = stringResource(StringR.string.common_settings))
val avatarData by remember { derivedStateOf { matrixUser.getAvatarData() } }
Avatar(avatarData, contentDescription = stringResource(StringR.string.common_settings))
}
}
},
@ -135,7 +139,7 @@ internal fun DefaultRoomListTopBarDarkPreview() = ElementPreviewDark { DefaultRo
@Composable
private fun DefaultRoomListTopBarPreview() {
DefaultRoomListTopBar(
matrixUser = MatrixUser(UserId("@id:domain"), "Alice", AvatarData("@id:domain", "Alice")),
matrixUser = MatrixUser(UserId("@id:domain"), "Alice"),
scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(rememberTopAppBarState()),
)
}

View file

@ -60,10 +60,9 @@ class RoomListPresenterTests {
Truth.assertThat(initialState.matrixUser).isNull()
val withUserState = awaitItem()
Truth.assertThat(withUserState.matrixUser).isNotNull()
Truth.assertThat(withUserState.matrixUser!!.id).isEqualTo(A_USER_ID)
Truth.assertThat(withUserState.matrixUser!!.username).isEqualTo(A_USER_NAME)
Truth.assertThat(withUserState.matrixUser!!.avatarData.name).isEqualTo(A_USER_NAME)
Truth.assertThat(withUserState.matrixUser!!.avatarData.url).isEqualTo(AN_AVATAR_URL)
Truth.assertThat(withUserState.matrixUser!!.userId).isEqualTo(A_USER_ID)
Truth.assertThat(withUserState.matrixUser!!.displayName).isEqualTo(A_USER_NAME)
Truth.assertThat(withUserState.matrixUser!!.avatarUrl).isEqualTo(AN_AVATAR_URL)
}
}
@ -88,8 +87,6 @@ class RoomListPresenterTests {
Truth.assertThat(initialState.matrixUser).isNull()
val withUserState = awaitItem()
Truth.assertThat(withUserState.matrixUser).isNotNull()
// username fallback to user id value
Truth.assertThat(withUserState.matrixUser!!.username).isEqualTo(A_USER_ID.value)
}
}

View file

@ -17,7 +17,7 @@
package io.element.android.features.userlist.api
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.ui.model.MatrixUser
import io.element.android.libraries.matrix.api.user.MatrixUser
interface UserListDataSource {
//TODO should probably have a flow

View file

@ -16,7 +16,7 @@
package io.element.android.features.userlist.api
import io.element.android.libraries.matrix.ui.model.MatrixUser
import io.element.android.libraries.matrix.api.user.MatrixUser
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import javax.inject.Inject

View file

@ -16,7 +16,7 @@
package io.element.android.features.userlist.api
import io.element.android.libraries.matrix.ui.model.MatrixUser
import io.element.android.libraries.matrix.api.user.MatrixUser
sealed interface UserListEvents {
data class UpdateSearchQuery(val query: String) : UserListEvents

View file

@ -16,7 +16,7 @@
package io.element.android.features.userlist.api
import io.element.android.libraries.matrix.ui.model.MatrixUser
import io.element.android.libraries.matrix.api.user.MatrixUser
import kotlinx.collections.immutable.ImmutableList
data class UserListState(

View file

@ -24,9 +24,9 @@ import androidx.compose.ui.unit.dp
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.ui.components.CheckableMatrixUserRow
import io.element.android.libraries.matrix.ui.components.aMatrixUser
import io.element.android.libraries.matrix.ui.model.MatrixUser
@Composable
fun SearchMultipleUsersResultItem(

View file

@ -24,9 +24,9 @@ import androidx.compose.ui.unit.dp
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.ui.components.MatrixUserRow
import io.element.android.libraries.matrix.ui.components.aMatrixUser
import io.element.android.libraries.matrix.ui.model.MatrixUser
@Composable
fun SearchSingleUserResultItem(

View file

@ -43,7 +43,7 @@ import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.IconButton
import io.element.android.libraries.designsystem.theme.components.SearchBar
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.matrix.ui.model.MatrixUser
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.ui.strings.R
import kotlinx.collections.immutable.ImmutableList
@ -128,7 +128,7 @@ fun SearchUserBar(
SearchMultipleUsersResultItem(
modifier = Modifier.fillMaxWidth(),
matrixUser = matrixUser,
isUserSelected = selectedUsers.find { it.id == matrixUser.id } != null,
isUserSelected = selectedUsers.find { it.userId == matrixUser.userId } != null,
onCheckedChange = { checked ->
if (checked) {
onUserSelected(matrixUser)

View file

@ -40,8 +40,9 @@ import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.IconButton
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.ui.components.aMatrixUser
import io.element.android.libraries.matrix.ui.model.MatrixUser
import io.element.android.libraries.matrix.ui.model.getAvatarData
import io.element.android.libraries.matrix.ui.model.getBestName
import io.element.android.libraries.ui.strings.R
@ -55,7 +56,7 @@ fun SelectedUser(
Column(
horizontalAlignment = Alignment.CenterHorizontally,
) {
Avatar(matrixUser.avatarData.copy(size = AvatarSize.Custom(56.dp)))
Avatar(matrixUser.getAvatarData(size = AvatarSize.Custom(56.dp)))
Text(
text = matrixUser.getBestName(),
overflow = TextOverflow.Ellipsis,

View file

@ -33,7 +33,7 @@ import androidx.compose.ui.unit.dp
import io.element.android.features.userlist.api.aListOfSelectedUsers
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.matrix.ui.model.MatrixUser
import io.element.android.libraries.matrix.api.user.MatrixUser
import kotlinx.collections.immutable.ImmutableList
@Composable

View file

@ -29,7 +29,7 @@ import io.element.android.features.userlist.api.UserListState
import io.element.android.features.userlist.api.UserListStateProvider
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.matrix.ui.model.MatrixUser
import io.element.android.libraries.matrix.api.user.MatrixUser
@Composable
fun UserListView(

View file

@ -38,7 +38,7 @@ import io.element.android.features.userlist.api.UserSearchResultState
import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.matrix.api.core.MatrixPatterns
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.ui.model.MatrixUser
import io.element.android.libraries.matrix.api.user.MatrixUser
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.delay
@ -105,7 +105,7 @@ class DefaultUserListPresenter @AssistedInject constructor(
private suspend fun performSearch(query: String): UserSearchResultState {
val isMatrixId = MatrixPatterns.isUserId(query)
val results = userListDataSource.search(query).toMutableList()
if (isMatrixId && results.none { it.id.value == query }) {
if (isMatrixId && results.none { it.userId.value == query }) {
val getProfileResult: MatrixUser? = userListDataSource.getProfile(UserId(query))
val profile = getProfileResult ?: MatrixUser(UserId(query))
results.add(0, profile)

View file

@ -28,8 +28,8 @@ import io.element.android.features.userlist.api.UserListPresenterArgs
import io.element.android.features.userlist.api.UserSearchResultState
import io.element.android.features.userlist.test.FakeUserListDataSource
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.ui.components.aMatrixUser
import io.element.android.libraries.matrix.ui.model.MatrixUser
import io.mockk.coJustRun
import io.mockk.mockkConstructor
import kotlinx.collections.immutable.persistentListOf

View file

@ -18,7 +18,7 @@ package io.element.android.features.userlist.test
import io.element.android.features.userlist.api.UserListDataSource
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.ui.model.MatrixUser
import io.element.android.libraries.matrix.api.user.MatrixUser
class FakeUserListDataSource : UserListDataSource {

View file

@ -16,22 +16,16 @@
package io.element.android.libraries.designsystem.components.avatar
import android.os.Parcelable
import androidx.compose.runtime.Immutable
import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize
@Immutable
@Parcelize
data class AvatarData(
val id: String,
val name: String?,
val url: String? = null,
@IgnoredOnParcel
val size: AvatarSize = AvatarSize.MEDIUM
) : Parcelable {
) {
@IgnoredOnParcel
val initial by lazy {
(name?.takeIf { it.isNotBlank() } ?: id)
.let { dn ->

View file

@ -26,7 +26,7 @@ import io.element.android.libraries.matrix.api.pusher.PushersService
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource
import io.element.android.libraries.matrix.api.usersearch.MatrixSearchUserResults
import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
import java.io.Closeable

View file

@ -17,6 +17,7 @@
package io.element.android.libraries.matrix.api.room
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.user.MatrixUser
data class RoomMember(
val userId: UserId,
@ -29,6 +30,12 @@ data class RoomMember(
val isIgnored: Boolean,
)
fun RoomMember.toMatrixUser() = MatrixUser(
userId = userId,
displayName = displayName,
avatarUrl = avatarUrl,
)
enum class RoomMembershipState {
BAN, INVITE, JOIN, KNOCK, LEAVE
}

View file

@ -14,9 +14,9 @@
* limitations under the License.
*/
package io.element.android.libraries.matrix.api.usersearch
package io.element.android.libraries.matrix.api.user
data class MatrixSearchUserResults(
val results: List<MatrixUserProfile>,
val results: List<MatrixUser>,
val limited: Boolean,
)

View file

@ -14,12 +14,15 @@
* limitations under the License.
*/
package io.element.android.libraries.matrix.api.usersearch
package io.element.android.libraries.matrix.api.user
import android.os.Parcelable
import io.element.android.libraries.matrix.api.core.UserId
import kotlinx.parcelize.Parcelize
data class MatrixUserProfile(
@Parcelize
data class MatrixUser(
val userId: UserId,
val displayName: String?,
val avatarUrl: String?
)
val displayName: String? = null,
val avatarUrl: String? = null
) : Parcelable

View file

@ -29,7 +29,7 @@ import io.element.android.libraries.matrix.api.pusher.PushersService
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource
import io.element.android.libraries.matrix.api.usersearch.MatrixSearchUserResults
import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
import io.element.android.libraries.matrix.impl.media.RustMediaResolver
import io.element.android.libraries.matrix.impl.notification.RustNotificationService

View file

@ -17,8 +17,8 @@
package io.element.android.libraries.matrix.impl.usersearch
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.usersearch.MatrixSearchUserResults
import io.element.android.libraries.matrix.api.usersearch.MatrixUserProfile
import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults
import io.element.android.libraries.matrix.api.user.MatrixUser
import org.matrix.rustcomponents.sdk.SearchUsersResults
import org.matrix.rustcomponents.sdk.UserProfile
@ -31,8 +31,8 @@ object UserSearchResultMapper {
)
}
private fun mapUserProfile(userProfile: UserProfile): MatrixUserProfile {
return MatrixUserProfile(
private fun mapUserProfile(userProfile: UserProfile): MatrixUser {
return MatrixUser(
userId = UserId(userProfile.userId),
displayName = userProfile.displayName,
avatarUrl = userProfile.avatarUrl,

View file

@ -27,7 +27,7 @@ import io.element.android.libraries.matrix.api.pusher.PushersService
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource
import io.element.android.libraries.matrix.api.usersearch.MatrixSearchUserResults
import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
import io.element.android.libraries.matrix.test.media.FakeMediaResolver
import io.element.android.libraries.matrix.test.notification.FakeNotificationService

View file

@ -1,53 +0,0 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.matrix.ui
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.ui.model.MatrixUser
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.asFlow
import javax.inject.Inject
class MatrixItemHelper @Inject constructor(
private val client: MatrixClient
) {
/**
* TODO Make username and avatar live...
*/
@OptIn(FlowPreview::class)
fun getCurrentUserData(avatarSize: AvatarSize): Flow<MatrixUser> {
return suspend {
val userAvatarUrl = client.loadUserAvatarURLString().getOrNull()
val userDisplayName = client.loadUserDisplayName().getOrNull()
val avatarData =
AvatarData(
client.sessionId.value,
userDisplayName,
userAvatarUrl,
avatarSize
)
MatrixUser(
id = client.sessionId,
username = userDisplayName,
avatarData = avatarData,
)
}.asFlow()
}
}

View file

@ -30,14 +30,14 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.theme.components.Checkbox
import io.element.android.libraries.matrix.ui.model.MatrixUser
import io.element.android.libraries.matrix.api.user.MatrixUser
@Composable
fun CheckableMatrixUserRow(
checked: Boolean,
matrixUser: MatrixUser,
modifier: Modifier = Modifier,
avatarSize: AvatarSize = matrixUser.avatarData.size,
avatarSize: AvatarSize = AvatarSize.MEDIUM,
onCheckedChange: (Boolean) -> Unit = {},
enabled: Boolean = true,
) {

View file

@ -38,7 +38,8 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.matrix.ui.model.MatrixUser
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.ui.model.getAvatarData
import io.element.android.libraries.matrix.ui.model.getBestName
@Composable
@ -56,7 +57,7 @@ fun MatrixUserHeader(
horizontalAlignment = Alignment.CenterHorizontally
) {
Avatar(
matrixUser.avatarData.copy(size = AvatarSize.HUGE),
matrixUser.getAvatarData(size = AvatarSize.HUGE),
)
Spacer(modifier = Modifier.height(16.dp))
// Name
@ -69,10 +70,10 @@ fun MatrixUserHeader(
color = MaterialTheme.colorScheme.primary,
)
// Id
if (matrixUser.username.isNullOrEmpty().not()) {
if (matrixUser.displayName.isNullOrEmpty().not()) {
Spacer(modifier = Modifier.height(4.dp))
Text(
text = matrixUser.id.value,
text = matrixUser.userId.value,
color = MaterialTheme.colorScheme.secondary,
fontSize = 14.sp,
maxLines = 1,

View file

@ -17,25 +17,20 @@
package io.element.android.libraries.matrix.ui.components
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.libraries.designsystem.components.avatar.anAvatarData
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.ui.model.MatrixUser
import io.element.android.libraries.matrix.api.user.MatrixUser
open class MatrixUserProvider : PreviewParameterProvider<MatrixUser> {
override val values: Sequence<MatrixUser>
get() = sequenceOf(
aMatrixUser(),
aMatrixUser().copy(
username = null,
avatarData = anAvatarData().copy(name = null)
),
aMatrixUser().copy(displayName = null),
)
}
fun aMatrixUser(id: String = "@id_of_alice:server.org", userName: String = "Alice") = MatrixUser(
id = UserId(id),
username = userName,
avatarData = anAvatarData(id, userName)
fun aMatrixUser(id: String = "@id_of_alice:server.org", displayName: String = "Alice") = MatrixUser(
userId = UserId(id),
displayName = displayName,
)
fun aMatrixUserList() = listOf(
@ -55,10 +50,7 @@ open class MatrixUserWithNullProvider : PreviewParameterProvider<MatrixUser?> {
override val values: Sequence<MatrixUser?>
get() = sequenceOf(
aMatrixUser(),
aMatrixUser().copy(
username = null,
avatarData = anAvatarData().copy(name = null)
),
aMatrixUser().copy(displayName = null),
null,
)
}

View file

@ -37,14 +37,15 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.matrix.ui.model.MatrixUser
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.ui.model.getAvatarData
import io.element.android.libraries.matrix.ui.model.getBestName
@Composable
fun MatrixUserRow(
matrixUser: MatrixUser,
modifier: Modifier = Modifier,
avatarSize: AvatarSize = matrixUser.avatarData.size,
avatarSize: AvatarSize = AvatarSize.MEDIUM,
) {
Row(
modifier = modifier
@ -54,7 +55,7 @@ fun MatrixUserRow(
verticalAlignment = Alignment.CenterVertically
) {
Avatar(
matrixUser.avatarData.copy(size = avatarSize),
matrixUser.getAvatarData(size = avatarSize),
)
Column(
modifier = Modifier
@ -70,9 +71,9 @@ fun MatrixUserRow(
color = MaterialTheme.colorScheme.primary,
)
// Id
if (matrixUser.username.isNullOrEmpty().not()) {
if (matrixUser.displayName.isNullOrEmpty().not()) {
Text(
text = matrixUser.id.value,
text = matrixUser.userId.value,
color = MaterialTheme.colorScheme.secondary,
fontSize = 14.sp,
maxLines = 1,

View file

@ -16,20 +16,17 @@
package io.element.android.libraries.matrix.ui.model
import android.os.Parcelable
import androidx.compose.runtime.Immutable
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.matrix.api.core.UserId
import kotlinx.parcelize.Parcelize
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.matrix.api.user.MatrixUser
@Parcelize
@Immutable
data class MatrixUser(
val id: UserId,
val username: String? = null,
val avatarData: AvatarData = AvatarData(id.value, username),
) : Parcelable
fun MatrixUser.getAvatarData(size: AvatarSize = AvatarSize.MEDIUM) = AvatarData(
id = userId.value,
name = displayName,
url = avatarUrl,
size = size,
)
fun MatrixUser.getBestName(): String {
return username?.takeIf { it.isNotEmpty() } ?: id.value
return displayName?.takeIf { it.isNotEmpty() } ?: userId.value
}