Merge pull request #2565 from element-hq/feature/bma/userDataCache
Read user avatar from cache
This commit is contained in:
commit
62799e1854
20 changed files with 118 additions and 150 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(
|
||||
|
|
|
|||
|
|
@ -223,7 +223,7 @@ private class FakeRoomMemberListNavigator : RoomMemberListNavigator {
|
|||
var openRoomMemberDetailsCallCount = 0
|
||||
private set
|
||||
|
||||
override fun openRoomMemberDetails(userId: UserId) {
|
||||
override fun openRoomMemberDetails(roomMemberId: UserId) {
|
||||
openRoomMemberDetailsCallCount++
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -21,10 +21,8 @@ import androidx.compose.foundation.layout.Column
|
|||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.statusBars
|
||||
import androidx.compose.foundation.layout.statusBarsPadding
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
|
|
@ -73,7 +71,6 @@ import io.element.android.libraries.designsystem.theme.components.HorizontalDivi
|
|||
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.MediumTopAppBar
|
||||
import io.element.android.libraries.designsystem.theme.components.Surface
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
|
|
@ -87,7 +84,7 @@ private val avatarBloomSize = 430.dp
|
|||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun RoomListTopBar(
|
||||
matrixUser: MatrixUser?,
|
||||
matrixUser: MatrixUser,
|
||||
showAvatarIndicator: Boolean,
|
||||
areSearchResultsDisplayed: Boolean,
|
||||
onToggleSearch: () -> Unit,
|
||||
|
|
@ -117,7 +114,7 @@ fun RoomListTopBar(
|
|||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun DefaultRoomListTopBar(
|
||||
matrixUser: MatrixUser?,
|
||||
matrixUser: MatrixUser,
|
||||
showAvatarIndicator: Boolean,
|
||||
areSearchResultsDisplayed: Boolean,
|
||||
scrollBehavior: TopAppBarScrollBehavior,
|
||||
|
|
@ -142,7 +139,7 @@ private fun DefaultRoomListTopBar(
|
|||
|
||||
val avatarData by remember(matrixUser) {
|
||||
derivedStateOf {
|
||||
matrixUser?.getAvatarData(size = AvatarSize.CurrentUserTopBar)
|
||||
matrixUser.getAvatarData(size = AvatarSize.CurrentUserTopBar)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -295,7 +292,7 @@ private fun DefaultRoomListTopBar(
|
|||
|
||||
@Composable
|
||||
private fun NavigationIcon(
|
||||
avatarData: AvatarData?,
|
||||
avatarData: AvatarData,
|
||||
showAvatarIndicator: Boolean,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
|
|
@ -304,20 +301,10 @@ private fun NavigationIcon(
|
|||
onClick = onClick,
|
||||
) {
|
||||
Box {
|
||||
if (avatarData != null) {
|
||||
Avatar(
|
||||
avatarData = avatarData,
|
||||
contentDescription = stringResource(CommonStrings.common_settings),
|
||||
)
|
||||
} else {
|
||||
// Placeholder avatar until the avatarData is available
|
||||
Surface(
|
||||
modifier = Modifier.size(AvatarSize.CurrentUserTopBar.dp),
|
||||
shape = CircleShape,
|
||||
color = ElementTheme.colors.iconSecondary,
|
||||
content = {}
|
||||
)
|
||||
}
|
||||
Avatar(
|
||||
avatarData = avatarData,
|
||||
contentDescription = stringResource(CommonStrings.common_settings),
|
||||
)
|
||||
if (showAvatarIndicator) {
|
||||
RedIndicatorAtom(
|
||||
modifier = Modifier.align(Alignment.TopEnd)
|
||||
|
|
|
|||
|
|
@ -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