Use client.cachedAvatarUrl(), to be able to render the user avatar faster, and even if there is no network (#2488).
This commit is contained in:
parent
5c540c4af9
commit
1144d467e5
15 changed files with 105 additions and 124 deletions
|
|
@ -24,7 +24,6 @@ import androidx.compose.runtime.getValue
|
|||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.produceState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import io.element.android.features.logout.api.direct.DirectLogoutPresenter
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.core.meta.BuildType
|
||||
|
|
@ -35,8 +34,6 @@ import io.element.android.libraries.featureflag.api.FeatureFlags
|
|||
import io.element.android.libraries.indicator.api.IndicatorService
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.oidc.AccountManagementAction
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.matrix.api.user.getCurrentUser
|
||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
|
@ -58,11 +55,10 @@ class PreferencesRootPresenter @Inject constructor(
|
|||
) : Presenter<PreferencesRootState> {
|
||||
@Composable
|
||||
override fun present(): PreferencesRootState {
|
||||
val matrixUser: MutableState<MatrixUser?> = rememberSaveable {
|
||||
mutableStateOf(null)
|
||||
}
|
||||
val matrixUser = matrixClient.userProfile.collectAsState()
|
||||
LaunchedEffect(Unit) {
|
||||
initialLoad(matrixUser)
|
||||
// Force a refresh of the profile
|
||||
matrixClient.getUserProfile()
|
||||
}
|
||||
|
||||
val snackbarMessage by snackbarDispatcher.collectSnackbarMessageAsState()
|
||||
|
|
@ -121,10 +117,6 @@ class PreferencesRootPresenter @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun CoroutineScope.initialLoad(matrixUser: MutableState<MatrixUser?>) = launch {
|
||||
matrixUser.value = matrixClient.getCurrentUser()
|
||||
}
|
||||
|
||||
private fun CoroutineScope.initAccountManagementUrl(
|
||||
accountManagementUrl: MutableState<String?>,
|
||||
devicesManagementUrl: MutableState<String?>,
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
|
|||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
|
||||
data class PreferencesRootState(
|
||||
val myUser: MatrixUser?,
|
||||
val myUser: MatrixUser,
|
||||
val version: String,
|
||||
val deviceId: String?,
|
||||
val showCompleteVerification: Boolean,
|
||||
|
|
|
|||
|
|
@ -18,10 +18,13 @@ package io.element.android.features.preferences.impl.root
|
|||
|
||||
import io.element.android.features.logout.api.direct.aDirectLogoutState
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
fun aPreferencesRootState() = PreferencesRootState(
|
||||
myUser = null,
|
||||
fun aPreferencesRootState(
|
||||
myUser: MatrixUser,
|
||||
) = PreferencesRootState(
|
||||
myUser = myUser,
|
||||
version = "Version 1.1 (1)",
|
||||
deviceId = "ILAKNDNASDLK",
|
||||
showCompleteVerification = true,
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ fun PreferencesRootView(
|
|||
) {
|
||||
UserPreferences(
|
||||
modifier = Modifier.clickable {
|
||||
state.myUser?.let(onOpenUserProfile)
|
||||
onOpenUserProfile(state.myUser)
|
||||
},
|
||||
user = state.myUser,
|
||||
)
|
||||
|
|
@ -225,7 +225,7 @@ internal fun PreferencesRootViewDarkPreview(@PreviewParameter(MatrixUserProvider
|
|||
@Composable
|
||||
private fun ContentToPreview(matrixUser: MatrixUser) {
|
||||
PreferencesRootView(
|
||||
state = aPreferencesRootState().copy(myUser = matrixUser),
|
||||
state = aPreferencesRootState(myUser = matrixUser),
|
||||
onBackPressed = {},
|
||||
onOpenAnalytics = {},
|
||||
onOpenRageShake = {},
|
||||
|
|
|
|||
|
|
@ -75,7 +75,13 @@ class PreferencesRootPresenterTest {
|
|||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.myUser).isNull()
|
||||
assertThat(initialState.myUser).isEqualTo(
|
||||
MatrixUser(
|
||||
userId = matrixClient.sessionId,
|
||||
displayName = A_USER_NAME,
|
||||
avatarUrl = AN_AVATAR_URL
|
||||
)
|
||||
)
|
||||
assertThat(initialState.version).isEqualTo("A Version")
|
||||
val loadedState = awaitItem()
|
||||
assertThat(loadedState.myUser).isEqualTo(
|
||||
|
|
|
|||
|
|
@ -58,8 +58,6 @@ import io.element.android.libraries.matrix.api.roomlist.RoomList
|
|||
import io.element.android.libraries.matrix.api.sync.SyncService
|
||||
import io.element.android.libraries.matrix.api.sync.SyncState
|
||||
import io.element.android.libraries.matrix.api.timeline.ReceiptType
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.matrix.api.user.getCurrentUser
|
||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import io.element.android.services.analyticsproviders.api.trackers.captureInteraction
|
||||
|
|
@ -101,16 +99,15 @@ class RoomListPresenter @Inject constructor(
|
|||
override fun present(): RoomListState {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val leaveRoomState = leaveRoomPresenter.present()
|
||||
val matrixUser: MutableState<MatrixUser?> = rememberSaveable {
|
||||
mutableStateOf(null)
|
||||
}
|
||||
val matrixUser = client.userProfile.collectAsState()
|
||||
val networkConnectionStatus by networkMonitor.connectivity.collectAsState()
|
||||
val filtersState = filtersPresenter.present()
|
||||
val searchState = searchPresenter.present()
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
roomListDataSource.launchIn(this)
|
||||
initialLoad(matrixUser)
|
||||
// Force a refresh of the profile
|
||||
client.getUserProfile()
|
||||
}
|
||||
|
||||
var securityBannerDismissed by rememberSaveable { mutableStateOf(false) }
|
||||
|
|
@ -157,10 +154,6 @@ class RoomListPresenter @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun CoroutineScope.initialLoad(matrixUser: MutableState<MatrixUser?>) = launch {
|
||||
matrixUser.value = client.getCurrentUser()
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun securityBannerState(
|
||||
securityBannerDismissed: Boolean,
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ import kotlinx.collections.immutable.ImmutableList
|
|||
|
||||
@Immutable
|
||||
data class RoomListState(
|
||||
val matrixUser: MatrixUser?,
|
||||
val matrixUser: MatrixUser,
|
||||
val showAvatarIndicator: Boolean,
|
||||
val hasNetworkConnection: Boolean,
|
||||
val snackbarMessage: SnackbarMessage?,
|
||||
|
|
|
|||
|
|
@ -49,14 +49,14 @@ open class RoomListStateProvider : PreviewParameterProvider<RoomListState> {
|
|||
aRoomListState(contentState = aRoomsContentState(securityBannerState = SecurityBannerState.RecoveryKeyConfirmation)),
|
||||
aRoomListState(contentState = anEmptyContentState()),
|
||||
aRoomListState(contentState = aSkeletonContentState()),
|
||||
aRoomListState(matrixUser = null, contentState = aMigrationContentState()),
|
||||
aRoomListState(matrixUser = MatrixUser(userId = UserId("@id:domain")), contentState = aMigrationContentState()),
|
||||
aRoomListState(searchState = aRoomListSearchState(isSearchActive = true, query = "Test")),
|
||||
aRoomListState(filtersState = aRoomListFiltersState(isFeatureEnabled = true)),
|
||||
)
|
||||
}
|
||||
|
||||
internal fun aRoomListState(
|
||||
matrixUser: MatrixUser? = MatrixUser(userId = UserId("@id:domain"), displayName = "User#1"),
|
||||
matrixUser: MatrixUser = MatrixUser(userId = UserId("@id:domain"), displayName = "User#1"),
|
||||
showAvatarIndicator: Boolean = false,
|
||||
hasNetworkConnection: Boolean = true,
|
||||
snackbarMessage: SnackbarMessage? = null,
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ private val avatarBloomSize = 430.dp
|
|||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun RoomListTopBar(
|
||||
matrixUser: MatrixUser?,
|
||||
matrixUser: MatrixUser,
|
||||
showAvatarIndicator: Boolean,
|
||||
areSearchResultsDisplayed: Boolean,
|
||||
onToggleSearch: () -> Unit,
|
||||
|
|
@ -117,7 +117,7 @@ fun RoomListTopBar(
|
|||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun DefaultRoomListTopBar(
|
||||
matrixUser: MatrixUser?,
|
||||
matrixUser: MatrixUser,
|
||||
showAvatarIndicator: Boolean,
|
||||
areSearchResultsDisplayed: Boolean,
|
||||
scrollBehavior: TopAppBarScrollBehavior,
|
||||
|
|
@ -142,7 +142,7 @@ private fun DefaultRoomListTopBar(
|
|||
|
||||
val avatarData by remember(matrixUser) {
|
||||
derivedStateOf {
|
||||
matrixUser?.getAvatarData(size = AvatarSize.CurrentUserTopBar)
|
||||
matrixUser.getAvatarData(size = AvatarSize.CurrentUserTopBar)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
|||
import io.element.android.libraries.matrix.api.roomlist.RoomList
|
||||
import io.element.android.libraries.matrix.api.sync.SyncState
|
||||
import io.element.android.libraries.matrix.api.timeline.ReceiptType
|
||||
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.AN_EXCEPTION
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
|
|
@ -93,17 +94,24 @@ class RoomListPresenterTests {
|
|||
@Test
|
||||
fun `present - should start with no user and then load user with success`() = runTest {
|
||||
val scope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
val presenter = createRoomListPresenter(coroutineScope = scope)
|
||||
val matrixClient = FakeMatrixClient(
|
||||
userDisplayName = null,
|
||||
userAvatarUrl = null,
|
||||
)
|
||||
matrixClient.givenGetProfileResult(matrixClient.sessionId, Result.success(MatrixUser(matrixClient.sessionId, A_USER_NAME, AN_AVATAR_URL)))
|
||||
val presenter = createRoomListPresenter(
|
||||
client = matrixClient,
|
||||
coroutineScope = scope,
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.matrixUser).isNull()
|
||||
assertThat(initialState.matrixUser).isEqualTo(MatrixUser(A_USER_ID))
|
||||
val withUserState = awaitItem()
|
||||
assertThat(withUserState.matrixUser).isNotNull()
|
||||
assertThat(withUserState.matrixUser!!.userId).isEqualTo(A_USER_ID)
|
||||
assertThat(withUserState.matrixUser!!.displayName).isEqualTo(A_USER_NAME)
|
||||
assertThat(withUserState.matrixUser!!.avatarUrl).isEqualTo(AN_AVATAR_URL)
|
||||
assertThat(withUserState.matrixUser.userId).isEqualTo(A_USER_ID)
|
||||
assertThat(withUserState.matrixUser.displayName).isEqualTo(A_USER_NAME)
|
||||
assertThat(withUserState.matrixUser.avatarUrl).isEqualTo(AN_AVATAR_URL)
|
||||
assertThat(withUserState.showAvatarIndicator).isTrue()
|
||||
scope.cancel()
|
||||
}
|
||||
|
|
@ -128,7 +136,6 @@ class RoomListPresenterTests {
|
|||
val initialState = awaitItem()
|
||||
assertThat(initialState.showAvatarIndicator).isTrue()
|
||||
sessionVerificationService.givenCanVerifySession(false)
|
||||
assertThat(awaitItem().showAvatarIndicator).isTrue()
|
||||
encryptionService.emitBackupState(BackupState.ENABLED)
|
||||
val finalState = awaitItem()
|
||||
assertThat(finalState.showAvatarIndicator).isFalse()
|
||||
|
|
@ -139,19 +146,18 @@ class RoomListPresenterTests {
|
|||
@Test
|
||||
fun `present - should start with no user and then load user with error`() = runTest {
|
||||
val matrixClient = FakeMatrixClient(
|
||||
userDisplayName = Result.failure(AN_EXCEPTION),
|
||||
userAvatarUrl = Result.failure(AN_EXCEPTION),
|
||||
userDisplayName = null,
|
||||
userAvatarUrl = null,
|
||||
)
|
||||
matrixClient.givenGetProfileResult(matrixClient.sessionId, Result.failure(AN_EXCEPTION))
|
||||
val scope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
val presenter = createRoomListPresenter(client = matrixClient, coroutineScope = scope)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.matrixUser).isNull()
|
||||
val withUserState = awaitItem()
|
||||
assertThat(withUserState.matrixUser).isNotNull()
|
||||
scope.cancel()
|
||||
assertThat(initialState.matrixUser).isEqualTo(MatrixUser(matrixClient.sessionId))
|
||||
// No new state is coming
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -364,7 +370,6 @@ class RoomListPresenterTests {
|
|||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
val summary = createRoomListRoomSummary()
|
||||
initialState.eventSink(RoomListEvents.ShowContextMenu(summary))
|
||||
|
|
@ -414,8 +419,6 @@ class RoomListPresenterTests {
|
|||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
|
||||
val initialState = awaitItem()
|
||||
val summary = createRoomListRoomSummary()
|
||||
initialState.eventSink(RoomListEvents.ShowContextMenu(summary))
|
||||
|
|
@ -473,7 +476,6 @@ class RoomListPresenterTests {
|
|||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
eventRecorder.assertEmpty()
|
||||
initialState.eventSink(RoomListEvents.ToggleSearchResults)
|
||||
|
|
@ -558,7 +560,6 @@ class RoomListPresenterTests {
|
|||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
// The migration screen is shown if the migration screen has not been shown before
|
||||
assertThat(initialState.contentState).isInstanceOf(RoomListContentState.Migration::class.java)
|
||||
|
|
@ -585,7 +586,6 @@ class RoomListPresenterTests {
|
|||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
assertThat(awaitItem().contentState).isInstanceOf(RoomListContentState.Empty::class.java)
|
||||
scope.cancel()
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue