mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-02 12:07:28 +00:00
Merge pull request #7517 from element-hq/feature/bma/removeUserStatusFeatureFlag
Enable user status feature and hide status text in the timeline
This commit is contained in:
@@ -30,8 +30,6 @@ import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.core.extensions.runCatchingExceptions
|
||||
import io.element.android.libraries.core.log.logger.LoggerTag
|
||||
import io.element.android.libraries.core.meta.BuildMeta
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
||||
import io.element.android.libraries.matrix.api.encryption.RecoveryState
|
||||
@@ -67,7 +65,6 @@ class LoggedInPresenter(
|
||||
private val buildMeta: BuildMeta,
|
||||
private val networkMonitor: NetworkMonitor,
|
||||
private val localNetworkPermissionAdvisor: LocalNetworkPermissionAdvisor,
|
||||
private val featureFlagService: FeatureFlagService,
|
||||
permissionsPresenterFactory: PermissionsPresenter.Factory,
|
||||
) : Presenter<LoggedInState> {
|
||||
private val localNetworkPermissionsPresenter: PermissionsPresenter =
|
||||
@@ -124,13 +121,9 @@ class LoggedInPresenter(
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
// Keep automatic call status (m.call) in sync with the feature flag and homeserver support.
|
||||
featureFlagService.isFeatureEnabledFlow(FeatureFlags.UserStatus)
|
||||
.onEach { isUserStatusEnabled ->
|
||||
val enabled = isUserStatusEnabled && matrixClient.isUserStatusSupported().getOrDefault(false)
|
||||
matrixClient.enableAutomaticCallStatus(enabled)
|
||||
}
|
||||
.launchIn(this)
|
||||
// Keep automatic call status (m.call) in sync with homeserver support.
|
||||
val enabled = matrixClient.isUserStatusSupported().getOrDefault(false)
|
||||
matrixClient.enableAutomaticCallStatus(enabled)
|
||||
}
|
||||
|
||||
val networkConnectivity by networkMonitor.connectivity.collectAsState()
|
||||
|
||||
@@ -17,9 +17,6 @@ import im.vector.app.features.analytics.plan.UserProperties
|
||||
import io.element.android.features.networkmonitor.api.NetworkStatus
|
||||
import io.element.android.features.networkmonitor.test.FakeNetworkMonitor
|
||||
import io.element.android.libraries.core.meta.BuildMeta
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
||||
@@ -131,7 +128,6 @@ class LoggedInPresenterTest {
|
||||
buildMeta = buildMeta,
|
||||
networkMonitor = networkMonitor,
|
||||
localNetworkPermissionAdvisor = FakeLocalNetworkPermissionAdvisor(),
|
||||
featureFlagService = FakeFeatureFlagService(),
|
||||
permissionsPresenterFactory = FakePermissionsPresenterFactory(),
|
||||
).test {
|
||||
encryptionService.emitRecoveryState(RecoveryState.UNKNOWN)
|
||||
@@ -362,9 +358,6 @@ class LoggedInPresenterTest {
|
||||
}
|
||||
createLoggedInPresenter(
|
||||
matrixClient = matrixClient,
|
||||
featureFlagService = FakeFeatureFlagService(
|
||||
initialState = mapOf(FeatureFlags.UserStatus.key to true),
|
||||
),
|
||||
).test {
|
||||
cancelAndConsumeRemainingEvents()
|
||||
assert(enableAutomaticCallStatusLambda)
|
||||
@@ -374,7 +367,7 @@ class LoggedInPresenterTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - reacts to user status flag being toggled at runtime`() = runTest {
|
||||
fun `present - enables automatic call status when server supports it`() = runTest {
|
||||
val enableAutomaticCallStatusLambda = lambdaRecorder<Boolean, Unit> { }
|
||||
val matrixClient = FakeMatrixClient(
|
||||
accountManagementUrlResult = { Result.success(null) },
|
||||
@@ -382,23 +375,13 @@ class LoggedInPresenterTest {
|
||||
).apply {
|
||||
isUserStatusSupportedResult = Result.success(true)
|
||||
}
|
||||
val featureFlagService = FakeFeatureFlagService(
|
||||
initialState = mapOf(FeatureFlags.UserStatus.key to false),
|
||||
)
|
||||
createLoggedInPresenter(
|
||||
matrixClient = matrixClient,
|
||||
featureFlagService = featureFlagService,
|
||||
).test {
|
||||
awaitItem()
|
||||
// Turning the flag on at runtime enables it.
|
||||
featureFlagService.setFeatureEnabled(FeatureFlags.UserStatus, true)
|
||||
cancelAndConsumeRemainingEvents()
|
||||
assert(enableAutomaticCallStatusLambda)
|
||||
.isCalledExactly(2)
|
||||
.withSequence(
|
||||
listOf(value(false)),
|
||||
listOf(value(true)),
|
||||
)
|
||||
.isCalledOnce()
|
||||
.with(value(true))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -418,7 +401,6 @@ class LoggedInPresenterTest {
|
||||
),
|
||||
buildMeta: BuildMeta = aBuildMeta(),
|
||||
networkMonitor: FakeNetworkMonitor = FakeNetworkMonitor(),
|
||||
featureFlagService: FeatureFlagService = FakeFeatureFlagService(),
|
||||
): LoggedInPresenter {
|
||||
return LoggedInPresenter(
|
||||
matrixClient = matrixClient,
|
||||
@@ -430,7 +412,6 @@ class LoggedInPresenterTest {
|
||||
buildMeta = buildMeta,
|
||||
networkMonitor = networkMonitor,
|
||||
localNetworkPermissionAdvisor = FakeLocalNetworkPermissionAdvisor(),
|
||||
featureFlagService = featureFlagService,
|
||||
permissionsPresenterFactory = FakePermissionsPresenterFactory(),
|
||||
)
|
||||
}
|
||||
|
||||
+1
-4
@@ -76,10 +76,7 @@ class PreferencesRootPresenter(
|
||||
}.collectAsState(initial = false)
|
||||
|
||||
val isUserStatusSupported by produceState(false) {
|
||||
featureFlagService.isFeatureEnabledFlow(FeatureFlags.UserStatus)
|
||||
.collect { isUserStatusEnabled ->
|
||||
value = isUserStatusEnabled && matrixClient.isUserStatusSupported().getOrDefault(false)
|
||||
}
|
||||
value = matrixClient.isUserStatusSupported().getOrDefault(false)
|
||||
}
|
||||
val userStatusState = if (isUserStatusSupported) userStatusPresenter.present() else null
|
||||
|
||||
|
||||
-7
@@ -152,13 +152,6 @@ enum class FeatureFlags(
|
||||
defaultValue = { false },
|
||||
isFinished = false,
|
||||
),
|
||||
UserStatus(
|
||||
key = "feature.user_status",
|
||||
title = "User status",
|
||||
description = "Allow users to set a status (e.g. In a meeting, Away) visible to their contacts.",
|
||||
defaultValue = { false },
|
||||
isFinished = false,
|
||||
),
|
||||
MessageSearch(
|
||||
key = "feature.message_search",
|
||||
title = "Message search",
|
||||
|
||||
+3
-1
@@ -32,6 +32,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageTy
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecryptContent
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.VideoMessageType
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.VoiceMessageType
|
||||
import io.element.android.libraries.matrix.api.user.DisplayedStatus
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.persistentMapOf
|
||||
|
||||
@@ -163,9 +164,10 @@ fun aProfileDetailsReady(
|
||||
displayName: String? = USER_NAME_SENDER,
|
||||
displayNameAmbiguous: Boolean = false,
|
||||
avatarUrl: String? = null,
|
||||
displayedStatus: DisplayedStatus? = null
|
||||
) = ProfileDetails.Ready(
|
||||
displayName = displayName,
|
||||
displayNameAmbiguous = displayNameAmbiguous,
|
||||
avatarUrl = avatarUrl,
|
||||
displayedStatus = null,
|
||||
displayedStatus = displayedStatus,
|
||||
)
|
||||
|
||||
+28
-7
@@ -9,12 +9,15 @@
|
||||
package io.element.android.libraries.matrix.ui.messages.sender
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clipToBounds
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -24,7 +27,7 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
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.timeline.item.event.ProfileDetails
|
||||
import io.element.android.libraries.matrix.ui.model.toText
|
||||
import io.element.android.libraries.matrix.ui.model.toEmojiText
|
||||
|
||||
// https://www.figma.com/file/Ni6Ii8YKtmXCKYNE90cC67/Timeline-(new)?type=design&node-id=917-80169&mode=design&t=A0CJCBbMqR8NOwUQ-0
|
||||
@Composable
|
||||
@@ -61,7 +64,7 @@ fun SenderName(
|
||||
modifier = Modifier
|
||||
.clipToBounds()
|
||||
.alignByBaseline(),
|
||||
text = userStatus.toText(),
|
||||
text = userStatus.toEmojiText(),
|
||||
style = ElementTheme.typography.fontBodyMdMedium,
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
maxLines = 1,
|
||||
@@ -136,9 +139,27 @@ private fun RowScope.SecondaryText(
|
||||
internal fun SenderNamePreview(
|
||||
@PreviewParameter(SenderNameDataPreviewParam::class) senderNameData: SenderNameData,
|
||||
) = ElementPreview {
|
||||
SenderName(
|
||||
senderId = senderNameData.userId,
|
||||
senderProfile = senderNameData.profileDetails,
|
||||
senderNameMode = senderNameData.senderNameMode,
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
sequenceOf(
|
||||
SenderNameMode.Timeline(mainColor = Color.Red),
|
||||
SenderNameMode.Reply,
|
||||
SenderNameMode.ActionList,
|
||||
).forEach { mode ->
|
||||
Column {
|
||||
Text(
|
||||
"Rendering in mode ${mode::class.simpleName}:",
|
||||
style = ElementTheme.typography.fontBodyXsRegular,
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
)
|
||||
SenderName(
|
||||
senderId = senderNameData.userId,
|
||||
senderProfile = senderNameData.profileDetails,
|
||||
senderNameMode = mode,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+40
-29
@@ -8,54 +8,65 @@
|
||||
|
||||
package io.element.android.libraries.matrix.ui.messages.sender
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileDetails
|
||||
import io.element.android.libraries.matrix.api.user.DisplayedStatus
|
||||
import io.element.android.libraries.matrix.api.user.UserStatus
|
||||
import io.element.android.libraries.matrix.ui.messages.reply.aProfileDetailsReady
|
||||
|
||||
data class SenderNameData(
|
||||
val userId: UserId,
|
||||
val profileDetails: ProfileDetails,
|
||||
val senderNameMode: SenderNameMode,
|
||||
)
|
||||
|
||||
open class SenderNameDataPreviewParam : PreviewParameterProvider<SenderNameData> {
|
||||
override val values: Sequence<SenderNameData>
|
||||
get() = sequenceOf(
|
||||
SenderNameMode.Timeline(mainColor = Color.Red),
|
||||
SenderNameMode.Reply,
|
||||
SenderNameMode.ActionList,
|
||||
aSenderNameData(),
|
||||
aSenderNameData(
|
||||
displayNameAmbiguous = true,
|
||||
),
|
||||
aSenderNameData(
|
||||
profileDetails = ProfileDetails.Unavailable,
|
||||
),
|
||||
aSenderNameData(
|
||||
displayName = null,
|
||||
),
|
||||
aSenderNameData(
|
||||
displayedStatus = DisplayedStatus.UserSet(
|
||||
status = UserStatus(
|
||||
emoji = "😀",
|
||||
text = "Should not be rendered",
|
||||
),
|
||||
),
|
||||
),
|
||||
aSenderNameData(
|
||||
displayedStatus = DisplayedStatus.InCall(0L),
|
||||
),
|
||||
aSenderNameData(
|
||||
displayNameAmbiguous = true,
|
||||
displayedStatus = DisplayedStatus.UserSet(
|
||||
status = UserStatus(
|
||||
emoji = "😀",
|
||||
text = "Should not be rendered",
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.flatMap { senderNameMode ->
|
||||
sequenceOf(
|
||||
aSenderNameData(
|
||||
senderNameMode = senderNameMode,
|
||||
),
|
||||
aSenderNameData(
|
||||
senderNameMode = senderNameMode,
|
||||
displayNameAmbiguous = true,
|
||||
),
|
||||
SenderNameData(
|
||||
senderNameMode = senderNameMode,
|
||||
userId = UserId("@alice:${senderNameMode.javaClass.simpleName.lowercase()}"),
|
||||
profileDetails = ProfileDetails.Unavailable,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun aSenderNameData(
|
||||
senderNameMode: SenderNameMode,
|
||||
userId: UserId = UserId("@alice:example.com"),
|
||||
displayName: String? = "Alice",
|
||||
displayNameAmbiguous: Boolean = false,
|
||||
displayedStatus: DisplayedStatus? = null,
|
||||
) = SenderNameData(
|
||||
userId = UserId("@alice:${senderNameMode.javaClass.simpleName.lowercase()}"),
|
||||
profileDetails = ProfileDetails.Ready(
|
||||
displayName = "Alice ${senderNameMode.javaClass.simpleName}",
|
||||
profileDetails: ProfileDetails = aProfileDetailsReady(
|
||||
displayName = displayName,
|
||||
displayNameAmbiguous = displayNameAmbiguous,
|
||||
avatarUrl = null,
|
||||
displayedStatus = displayedStatus,
|
||||
),
|
||||
senderNameMode = senderNameMode,
|
||||
)
|
||||
) = SenderNameData(
|
||||
userId = userId,
|
||||
profileDetails = profileDetails,
|
||||
)
|
||||
|
||||
LFS
BIN
Binary file not shown.
LFS
BIN
Binary file not shown.
LFS
BIN
Binary file not shown.
LFS
BIN
Binary file not shown.
LFS
BIN
Binary file not shown.
LFS
BIN
Binary file not shown.
LFS
BIN
Binary file not shown.
LFS
BIN
Binary file not shown.
LFS
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Reference in New Issue
Block a user