diff --git a/features/messages/build.gradle.kts b/features/messages/build.gradle.kts index d0c44cfea5..bf488500a3 100644 --- a/features/messages/build.gradle.kts +++ b/features/messages/build.gradle.kts @@ -34,6 +34,7 @@ dependencies { implementation(project(":libraries:di")) implementation(project(":libraries:core")) implementation(project(":libraries:matrix")) + implementation(project(":libraries:matrixui")) implementation(project(":libraries:designsystem")) implementation(project(":libraries:textcomposer")) implementation(libs.mavericks.compose) diff --git a/features/messages/src/main/java/io/element/android/x/features/messages/MessageTimelineItemStateFactory.kt b/features/messages/src/main/java/io/element/android/x/features/messages/MessageTimelineItemStateFactory.kt index f2779dd8aa..68bf073de7 100644 --- a/features/messages/src/main/java/io/element/android/x/features/messages/MessageTimelineItemStateFactory.kt +++ b/features/messages/src/main/java/io/element/android/x/features/messages/MessageTimelineItemStateFactory.kt @@ -17,7 +17,6 @@ package io.element.android.x.features.messages import androidx.recyclerview.widget.DiffUtil -import io.element.android.x.designsystem.components.avatar.AvatarData import io.element.android.x.designsystem.components.avatar.AvatarSize import io.element.android.x.features.messages.diff.CacheInvalidator import io.element.android.x.features.messages.diff.MatrixTimelineItemsDiffCallback @@ -34,11 +33,10 @@ import io.element.android.x.features.messages.model.content.MessagesTimelineItem import io.element.android.x.features.messages.model.content.MessagesTimelineItemTextContent import io.element.android.x.features.messages.model.content.MessagesTimelineItemUnknownContent import io.element.android.x.features.messages.util.invalidateLast -import io.element.android.x.matrix.MatrixClient import io.element.android.x.matrix.media.MediaResolver import io.element.android.x.matrix.room.MatrixRoom import io.element.android.x.matrix.timeline.MatrixTimelineItem -import kotlin.system.measureTimeMillis +import io.element.android.x.matrix.ui.MatrixItemHelper import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -52,9 +50,10 @@ import org.matrix.rustcomponents.sdk.FormattedBody import org.matrix.rustcomponents.sdk.MessageFormat import org.matrix.rustcomponents.sdk.MessageType import timber.log.Timber +import kotlin.system.measureTimeMillis class MessageTimelineItemStateFactory( - private val client: MatrixClient, + private val matrixItemHelper: MatrixItemHelper, private val room: MatrixRoom, private val dispatcher: CoroutineDispatcher, ) { @@ -153,7 +152,11 @@ class MessageTimelineItemStateFactory( val senderDisplayName = room.userDisplayName(currentSender).getOrNull() val senderAvatarUrl = room.userAvatarUrl(currentSender).getOrNull() val senderAvatarData = - loadAvatarData(senderDisplayName ?: currentSender, senderAvatarUrl) + matrixItemHelper.loadAvatarData( + name = senderDisplayName ?: currentSender, + url = senderAvatarUrl, + size = AvatarSize.SMALL + ) return MessagesTimelineItemState.MessageEvent( id = currentTimelineItem.uniqueId, senderId = currentSender, @@ -243,14 +246,4 @@ class MessageTimelineItemStateFactory( else -> MessagesItemGroupPosition.None } } - - private suspend fun loadAvatarData( - name: String, - url: String?, - size: AvatarSize = AvatarSize.SMALL - ): AvatarData { - val model = client.mediaResolver() - .resolve(url, kind = MediaResolver.Kind.Thumbnail(size.value)) - return AvatarData(name, model, size) - } } diff --git a/features/messages/src/main/java/io/element/android/x/features/messages/MessagesViewModel.kt b/features/messages/src/main/java/io/element/android/x/features/messages/MessagesViewModel.kt index 507af9d706..5083a9fd7b 100644 --- a/features/messages/src/main/java/io/element/android/x/features/messages/MessagesViewModel.kt +++ b/features/messages/src/main/java/io/element/android/x/features/messages/MessagesViewModel.kt @@ -23,7 +23,6 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.x.anvilannotations.ContributesViewModel import io.element.android.x.core.di.daggerMavericksViewModelFactory -import io.element.android.x.designsystem.components.avatar.AvatarData import io.element.android.x.designsystem.components.avatar.AvatarSize import io.element.android.x.di.SessionScope import io.element.android.x.features.messages.model.MessagesItemAction @@ -33,9 +32,9 @@ import io.element.android.x.features.messages.model.MessagesViewState import io.element.android.x.features.messages.model.content.MessagesTimelineItemRedactedContent import io.element.android.x.features.messages.model.content.MessagesTimelineItemTextBasedContent import io.element.android.x.matrix.MatrixClient -import io.element.android.x.matrix.media.MediaResolver import io.element.android.x.matrix.timeline.MatrixTimeline import io.element.android.x.matrix.timeline.MatrixTimelineItem +import io.element.android.x.matrix.ui.MatrixItemHelper import io.element.android.x.textcomposer.MessageComposerMode import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.launchIn @@ -53,9 +52,10 @@ class MessagesViewModel @AssistedInject constructor( companion object : MavericksViewModelFactory by daggerMavericksViewModelFactory() + private val matrixItemHelper = MatrixItemHelper(client) private val room = client.getRoom(initialState.roomId)!! private val messageTimelineItemStateFactory = - MessageTimelineItemStateFactory(client, room, Dispatchers.Default) + MessageTimelineItemStateFactory(matrixItemHelper, room, Dispatchers.Default) private val timeline = room.timeline() private val timelineCallback = object : MatrixTimeline.Callback { @@ -161,7 +161,10 @@ class MessagesViewModel @AssistedInject constructor( room.syncUpdateFlow() .onEach { val avatarData = - loadAvatarData(room.name ?: room.roomId.value, room.avatarUrl, AvatarSize.SMALL) + matrixItemHelper.loadAvatarData( + room = room, + size = AvatarSize.SMALL + ) setState { copy( roomName = room.name, roomAvatar = avatarData, @@ -217,16 +220,6 @@ class MessagesViewModel @AssistedInject constructor( setSnackbarContent("Not implemented yet!") } - private suspend fun loadAvatarData( - name: String, - url: String?, - size: AvatarSize = AvatarSize.MEDIUM - ): AvatarData { - val model = client.mediaResolver() - .resolve(url, kind = MediaResolver.Kind.Thumbnail(size.value)) - return AvatarData(name, model, size) - } - override fun onCleared() { super.onCleared() timeline.callback = null diff --git a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListViewModel.kt b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListViewModel.kt index 72a3e25670..9ecac4f705 100644 --- a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListViewModel.kt +++ b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListViewModel.kt @@ -25,16 +25,14 @@ import dagger.assisted.AssistedInject import io.element.android.x.anvilannotations.ContributesViewModel import io.element.android.x.core.coroutine.parallelMap import io.element.android.x.core.di.daggerMavericksViewModelFactory -import io.element.android.x.designsystem.components.avatar.AvatarData import io.element.android.x.designsystem.components.avatar.AvatarSize import io.element.android.x.di.SessionScope import io.element.android.x.features.roomlist.model.RoomListRoomSummary import io.element.android.x.features.roomlist.model.RoomListRoomSummaryPlaceholders import io.element.android.x.features.roomlist.model.RoomListViewState import io.element.android.x.matrix.MatrixClient -import io.element.android.x.matrix.media.MediaResolver import io.element.android.x.matrix.room.RoomSummary -import io.element.android.x.matrix.ui.model.MatrixUser +import io.element.android.x.matrix.ui.MatrixItemHelper import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged @@ -53,6 +51,7 @@ class RoomListViewModel @AssistedInject constructor( companion object : MavericksViewModelFactory by daggerMavericksViewModelFactory() private val lastMessageFormatter = LastMessageFormatter() + private val matrixUserHelper = MatrixItemHelper(client) init { handleInit() @@ -79,22 +78,7 @@ class RoomListViewModel @AssistedInject constructor( } private fun handleInit() { - suspend { - val userAvatarUrl = client.loadUserAvatarURLString().getOrNull() - val userDisplayName = client.loadUserDisplayName().getOrNull() - val avatarData = - loadAvatarData( - userDisplayName ?: client.userId().value, - userAvatarUrl, - AvatarSize.SMALL - ) - MatrixUser( - id = client.userId(), - username = userDisplayName, - avatarUrl = userAvatarUrl, - avatarData = avatarData, - ) - }.execute { + matrixUserHelper.getCurrentUserData(avatarSize = AvatarSize.SMALL).execute { copy(user = it) } @@ -137,9 +121,9 @@ class RoomListViewModel @AssistedInject constructor( when (roomSummary) { is RoomSummary.Empty -> RoomListRoomSummaryPlaceholders.create(roomSummary.identifier) is RoomSummary.Filled -> { - val avatarData = loadAvatarData( - roomSummary.details.name, - roomSummary.details.avatarURLString + val avatarData = matrixUserHelper.loadAvatarData( + roomSummary = roomSummary, + size = AvatarSize.MEDIUM ) RoomListRoomSummary( id = roomSummary.identifier(), @@ -153,14 +137,4 @@ class RoomListViewModel @AssistedInject constructor( } } } - - private suspend fun loadAvatarData( - name: String, - url: String?, - size: AvatarSize = AvatarSize.MEDIUM - ): AvatarData { - val model = client.mediaResolver() - .resolve(url, kind = MediaResolver.Kind.Thumbnail(size.value)) - return AvatarData(name, model, size) - } } diff --git a/libraries/matrixui/src/main/java/io/element/android/x/matrix/ui/MatrixItemHelper.kt b/libraries/matrixui/src/main/java/io/element/android/x/matrix/ui/MatrixItemHelper.kt new file mode 100644 index 0000000000..fb8a25f7f7 --- /dev/null +++ b/libraries/matrixui/src/main/java/io/element/android/x/matrix/ui/MatrixItemHelper.kt @@ -0,0 +1,81 @@ +/* + * 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.x.matrix.ui + +import io.element.android.x.designsystem.components.avatar.AvatarData +import io.element.android.x.designsystem.components.avatar.AvatarSize +import io.element.android.x.matrix.MatrixClient +import io.element.android.x.matrix.media.MediaResolver +import io.element.android.x.matrix.room.MatrixRoom +import io.element.android.x.matrix.room.RoomSummary +import io.element.android.x.matrix.ui.model.MatrixUser +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.asFlow + +class MatrixItemHelper( + private val client: MatrixClient +) { + /** + * TODO Make username and avatar live... + */ + @OptIn(FlowPreview::class) + fun getCurrentUserData(avatarSize: AvatarSize): Flow { + return suspend { + val userAvatarUrl = client.loadUserAvatarURLString().getOrNull() + val userDisplayName = client.loadUserDisplayName().getOrNull() + val avatarData = + loadAvatarData( + userDisplayName ?: client.userId().value, + userAvatarUrl, + avatarSize + ) + MatrixUser( + id = client.userId(), + username = userDisplayName, + avatarUrl = userAvatarUrl, + avatarData = avatarData, + ) + }.asFlow() + } + + suspend fun loadAvatarData(room: MatrixRoom, size: AvatarSize): AvatarData { + return loadAvatarData( + name = room.name ?: room.roomId.value, + url = room.avatarUrl, + size = size + ) + } + + suspend fun loadAvatarData(roomSummary: RoomSummary.Filled, size: AvatarSize): AvatarData { + return loadAvatarData( + name = roomSummary.details.name, + url = roomSummary.details.avatarURLString, + size = size + ) + } + + suspend fun loadAvatarData( + name: String, + url: String?, + size: AvatarSize + ): AvatarData { + val model = client.mediaResolver() + .resolve(url, kind = MediaResolver.Kind.Thumbnail(size.value)) + return AvatarData(name, model, size) + } +}