Debug info: render the roomId in the room details screen when developer mode is enabled.

This commit is contained in:
Benoit Marty 2025-06-20 10:11:17 +02:00
parent 0dfec51889
commit 009601cf5b
6 changed files with 70 additions and 1 deletions

View file

@ -67,6 +67,7 @@ dependencies {
testImplementation(projects.libraries.mediaupload.test) testImplementation(projects.libraries.mediaupload.test)
testImplementation(projects.libraries.mediapickers.test) testImplementation(projects.libraries.mediapickers.test)
testImplementation(projects.libraries.permissions.test) testImplementation(projects.libraries.permissions.test)
testImplementation(projects.libraries.preferences.test)
testImplementation(projects.libraries.usersearch.test) testImplementation(projects.libraries.usersearch.test)
testImplementation(projects.libraries.featureflag.test) testImplementation(projects.libraries.featureflag.test)
testImplementation(projects.tests.testutils) testImplementation(projects.tests.testutils)

View file

@ -48,6 +48,7 @@ import io.element.android.libraries.matrix.ui.room.getDirectRoomMember
import io.element.android.libraries.matrix.ui.room.isDmAsState import io.element.android.libraries.matrix.ui.room.isDmAsState
import io.element.android.libraries.matrix.ui.room.isOwnUserAdmin import io.element.android.libraries.matrix.ui.room.isOwnUserAdmin
import io.element.android.libraries.matrix.ui.room.roomMemberIdentityStateChange import io.element.android.libraries.matrix.ui.room.roomMemberIdentityStateChange
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analytics.api.AnalyticsService
import io.element.android.services.analyticsproviders.api.trackers.captureInteraction import io.element.android.services.analyticsproviders.api.trackers.captureInteraction
@ -70,6 +71,7 @@ class RoomDetailsPresenter @Inject constructor(
private val analyticsService: AnalyticsService, private val analyticsService: AnalyticsService,
private val isPinnedMessagesFeatureEnabled: IsPinnedMessagesFeatureEnabled, private val isPinnedMessagesFeatureEnabled: IsPinnedMessagesFeatureEnabled,
private val clipboardHelper: ClipboardHelper, private val clipboardHelper: ClipboardHelper,
private val appPreferencesStore: AppPreferencesStore,
) : Presenter<RoomDetailsState> { ) : Presenter<RoomDetailsState> {
@Composable @Composable
override fun present(): RoomDetailsState { override fun present(): RoomDetailsState {
@ -136,6 +138,9 @@ class RoomDetailsPresenter @Inject constructor(
val canShowKnockRequests by remember { val canShowKnockRequests by remember {
derivedStateOf { isKnockRequestsEnabled && canHandleKnockRequests && joinRule == JoinRule.Knock } derivedStateOf { isKnockRequestsEnabled && canHandleKnockRequests && joinRule == JoinRule.Knock }
} }
val isDeveloperModeEnabled by remember {
appPreferencesStore.isDeveloperModeEnabledFlow()
}.collectAsState(initial = false)
val roomNotificationSettingsState by room.roomNotificationSettingsStateFlow.collectAsState() val roomNotificationSettingsState by room.roomNotificationSettingsStateFlow.collectAsState()
@ -211,6 +216,7 @@ class RoomDetailsPresenter @Inject constructor(
hasMemberVerificationViolations = hasMemberVerificationViolations, hasMemberVerificationViolations = hasMemberVerificationViolations,
canReportRoom = canReportRoom, canReportRoom = canReportRoom,
isTombstoned = roomInfo.successorRoom != null, isTombstoned = roomInfo.successorRoom != null,
showDebugInfo = isDeveloperModeEnabled,
eventSink = ::handleEvents, eventSink = ::handleEvents,
) )
} }

View file

@ -50,6 +50,7 @@ data class RoomDetailsState(
val hasMemberVerificationViolations: Boolean, val hasMemberVerificationViolations: Boolean,
val canReportRoom: Boolean, val canReportRoom: Boolean,
val isTombstoned: Boolean, val isTombstoned: Boolean,
val showDebugInfo: Boolean,
val eventSink: (RoomDetailsEvent) -> Unit val eventSink: (RoomDetailsEvent) -> Unit
) { ) {
val roomBadges = buildList { val roomBadges = buildList {

View file

@ -33,7 +33,7 @@ open class RoomDetailsStateProvider : PreviewParameterProvider<RoomDetailsState>
override val values: Sequence<RoomDetailsState> override val values: Sequence<RoomDetailsState>
get() = sequenceOf( get() = sequenceOf(
aRoomDetailsState(displayAdminSettings = true), aRoomDetailsState(displayAdminSettings = true),
aRoomDetailsState(roomTopic = RoomTopicState.Hidden), aRoomDetailsState(roomTopic = RoomTopicState.Hidden, showDebugInfo = true),
aRoomDetailsState(roomTopic = RoomTopicState.CanAddTopic), aRoomDetailsState(roomTopic = RoomTopicState.CanAddTopic),
aRoomDetailsState(isEncrypted = false), aRoomDetailsState(isEncrypted = false),
aRoomDetailsState(roomAlias = null), aRoomDetailsState(roomAlias = null),
@ -120,6 +120,7 @@ fun aRoomDetailsState(
hasMemberVerificationViolations: Boolean = false, hasMemberVerificationViolations: Boolean = false,
canReportRoom: Boolean = true, canReportRoom: Boolean = true,
isTombstoned: Boolean = false, isTombstoned: Boolean = false,
showDebugInfo: Boolean = false,
eventSink: (RoomDetailsEvent) -> Unit = {}, eventSink: (RoomDetailsEvent) -> Unit = {},
) = RoomDetailsState( ) = RoomDetailsState(
roomId = roomId, roomId = roomId,
@ -151,6 +152,7 @@ fun aRoomDetailsState(
hasMemberVerificationViolations = hasMemberVerificationViolations, hasMemberVerificationViolations = hasMemberVerificationViolations,
canReportRoom = canReportRoom, canReportRoom = canReportRoom,
isTombstoned = isTombstoned, isTombstoned = isTombstoned,
showDebugInfo = showDebugInfo,
eventSink = eventSink, eventSink = eventSink,
) )

View file

@ -30,6 +30,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
@ -42,6 +43,7 @@ import io.element.android.features.roomcall.api.hasPermissionToJoin
import io.element.android.features.userprofile.api.UserProfileVerificationState import io.element.android.features.userprofile.api.UserProfileVerificationState
import io.element.android.features.userprofile.shared.blockuser.BlockUserDialogs import io.element.android.features.userprofile.shared.blockuser.BlockUserDialogs
import io.element.android.features.userprofile.shared.blockuser.BlockUserSection import io.element.android.features.userprofile.shared.blockuser.BlockUserSection
import io.element.android.libraries.androidutils.system.copyToClipboard
import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage
import io.element.android.libraries.designsystem.atomic.atoms.MatrixBadgeAtom import io.element.android.libraries.designsystem.atomic.atoms.MatrixBadgeAtom
import io.element.android.libraries.designsystem.atomic.molecules.MatrixBadgeRowMolecule import io.element.android.libraries.designsystem.atomic.molecules.MatrixBadgeRowMolecule
@ -261,6 +263,12 @@ fun RoomDetailsView(
onReportRoomClick = onReportRoomClick, onReportRoomClick = onReportRoomClick,
onLeaveRoomClick = { state.eventSink(RoomDetailsEvent.LeaveRoom) } onLeaveRoomClick = { state.eventSink(RoomDetailsEvent.LeaveRoom) }
) )
if (state.showDebugInfo) {
DebugInfoSection(
roomId = state.roomId,
)
}
} }
} }
} }
@ -701,6 +709,33 @@ private fun OtherActionsSection(
} }
} }
@Composable
private fun DebugInfoSection(roomId: RoomId) {
val context = LocalContext.current
PreferenceCategory(showTopDivider = true) {
ListItem(
headlineContent = {
Text("Internal room ID")
},
supportingContent = {
Text(
text = roomId.value,
style = ElementTheme.typography.fontBodySmRegular,
color = ElementTheme.colors.textSecondary,
)
},
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Code())),
trailingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Copy())),
onClick = {
context.copyToClipboard(
roomId.value,
context.getString(CommonStrings.common_copied_to_clipboard)
)
},
)
}
}
@PreviewWithLargeHeight @PreviewWithLargeHeight
@Composable @Composable
internal fun RoomDetailsPreview(@PreviewParameter(RoomDetailsStateProvider::class) state: RoomDetailsState) = internal fun RoomDetailsPreview(@PreviewParameter(RoomDetailsStateProvider::class) state: RoomDetailsState) =

View file

@ -41,6 +41,8 @@ import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService
import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService
import io.element.android.libraries.matrix.test.room.aRoomInfo import io.element.android.libraries.matrix.test.room.aRoomInfo
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore
import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analytics.api.AnalyticsService
import io.element.android.services.analytics.test.FakeAnalyticsService import io.element.android.services.analytics.test.FakeAnalyticsService
import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.EventsRecorder
@ -84,6 +86,7 @@ class RoomDetailsPresenterTest {
isPinnedMessagesFeatureEnabled: Boolean = true, isPinnedMessagesFeatureEnabled: Boolean = true,
encryptionService: FakeEncryptionService = FakeEncryptionService(), encryptionService: FakeEncryptionService = FakeEncryptionService(),
clipboardHelper: ClipboardHelper = FakeClipboardHelper(), clipboardHelper: ClipboardHelper = FakeClipboardHelper(),
appPreferencesStore: AppPreferencesStore = InMemoryAppPreferencesStore()
): RoomDetailsPresenter { ): RoomDetailsPresenter {
val matrixClient = FakeMatrixClient(notificationSettingsService = notificationSettingsService) val matrixClient = FakeMatrixClient(notificationSettingsService = notificationSettingsService)
val roomMemberDetailsPresenterFactory = object : RoomMemberDetailsPresenter.Factory { val roomMemberDetailsPresenterFactory = object : RoomMemberDetailsPresenter.Factory {
@ -111,6 +114,7 @@ class RoomDetailsPresenterTest {
isPinnedMessagesFeatureEnabled = { isPinnedMessagesFeatureEnabled }, isPinnedMessagesFeatureEnabled = { isPinnedMessagesFeatureEnabled },
analyticsService = analyticsService, analyticsService = analyticsService,
clipboardHelper = clipboardHelper, clipboardHelper = clipboardHelper,
appPreferencesStore = appPreferencesStore,
) )
} }
@ -132,6 +136,7 @@ class RoomDetailsPresenterTest {
assertThat(initialState.canShowPinnedMessages).isTrue() assertThat(initialState.canShowPinnedMessages).isTrue()
assertThat(initialState.pinnedMessagesCount).isEqualTo(0) assertThat(initialState.pinnedMessagesCount).isEqualTo(0)
assertThat(initialState.canShowSecurityAndPrivacy).isFalse() assertThat(initialState.canShowSecurityAndPrivacy).isFalse()
assertThat(initialState.showDebugInfo).isFalse()
cancelAndIgnoreRemainingEvents() cancelAndIgnoreRemainingEvents()
} }
@ -727,4 +732,23 @@ class RoomDetailsPresenterTest {
} }
} }
} }
@Test
fun `present - show debug info`() = runTest {
val room = aJoinedRoom(
canInviteResult = { Result.success(true) },
canUserJoinCallResult = { Result.success(true) },
canSendStateResult = { _, _ -> Result.success(true) },
)
val inMemoryAppPreferencesStore = InMemoryAppPreferencesStore(
isDeveloperModeEnabled = true,
)
val presenter = createRoomDetailsPresenter(room = room, appPreferencesStore = inMemoryAppPreferencesStore)
presenter.testWithLifecycleOwner(lifecycleOwner = fakeLifecycleOwner) {
skipItems(1)
with(awaitItem()) {
assertThat(showDebugInfo).isTrue()
}
}
}
} }