mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-10-31 08:15:02 +00:00
Add an extension for room details sections (#7781)
* Add an extension for room details sections Provide a no-op rendering hook for build-specific room details sections and shared room audio labels. Keep the default room details behavior unchanged. * Cover the default room details extension Verify that the no-op extension renders no content and leaves surrounding UI unchanged through recomposition. Cover its constructor and both generated render paths. * Refine the room details extension and link the enterprise picker Expose ColumnScope to contributed room details sections and use room-details string keys. Pin the enterprise submodule to the reviewed picker implementation so integrated builds include it behind the default-off flag. * Make the room details extension a functional interface Declare the single composable rendering contract as a fun interface to satisfy the Sonar check while preserving its named dependency injection binding. * Update localazy strings for the audio section in room details * Bump enterprise commit --------- Co-authored-by: Jorge Martín <jorgem@element.io>
This commit is contained in:
+1
-1
Submodule enterprise updated: 6b422f4c8e...bcffe42d5c
@@ -7,7 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("io.element.android-library")
|
id("io.element.android-compose-library")
|
||||||
id("kotlin-parcelize")
|
id("kotlin-parcelize")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2026 Element Creations Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.roomdetails.api
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
|
||||||
|
/** Optional room-details sections contributed by a build-specific implementation. */
|
||||||
|
fun interface RoomDetailsExtension {
|
||||||
|
@Composable
|
||||||
|
fun ColumnScope.Render(modifier: Modifier)
|
||||||
|
}
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2026 Element Creations Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.roomdetails.impl
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import dev.zacsweers.metro.ContributesBinding
|
||||||
|
import io.element.android.features.roomdetails.api.RoomDetailsExtension
|
||||||
|
import io.element.android.libraries.di.RoomScope
|
||||||
|
|
||||||
|
@ContributesBinding(RoomScope::class)
|
||||||
|
class NoopRoomDetailsExtension : RoomDetailsExtension {
|
||||||
|
@Composable
|
||||||
|
override fun ColumnScope.Render(modifier: Modifier) = Unit
|
||||||
|
}
|
||||||
+3
@@ -24,6 +24,7 @@ import dev.zacsweers.metro.AssistedInject
|
|||||||
import im.vector.app.features.analytics.plan.MobileScreen
|
import im.vector.app.features.analytics.plan.MobileScreen
|
||||||
import io.element.android.annotations.ContributesNode
|
import io.element.android.annotations.ContributesNode
|
||||||
import io.element.android.features.leaveroom.api.LeaveRoomRenderer
|
import io.element.android.features.leaveroom.api.LeaveRoomRenderer
|
||||||
|
import io.element.android.features.roomdetails.api.RoomDetailsExtension
|
||||||
import io.element.android.libraries.androidutils.system.startSharePlainTextIntent
|
import io.element.android.libraries.androidutils.system.startSharePlainTextIntent
|
||||||
import io.element.android.libraries.architecture.appyx.launchMolecule
|
import io.element.android.libraries.architecture.appyx.launchMolecule
|
||||||
import io.element.android.libraries.architecture.callback
|
import io.element.android.libraries.architecture.callback
|
||||||
@@ -46,6 +47,7 @@ class RoomDetailsNode(
|
|||||||
private val room: BaseRoom,
|
private val room: BaseRoom,
|
||||||
private val analyticsService: AnalyticsService,
|
private val analyticsService: AnalyticsService,
|
||||||
private val leaveRoomRenderer: LeaveRoomRenderer,
|
private val leaveRoomRenderer: LeaveRoomRenderer,
|
||||||
|
private val roomDetailsExtension: RoomDetailsExtension,
|
||||||
) : Node(buildContext, plugins = plugins), RoomDetailsNavigator {
|
) : Node(buildContext, plugins = plugins), RoomDetailsNavigator {
|
||||||
interface Callback : Plugin {
|
interface Callback : Plugin {
|
||||||
fun navigateBack()
|
fun navigateBack()
|
||||||
@@ -120,6 +122,7 @@ class RoomDetailsNode(
|
|||||||
|
|
||||||
RoomDetailsView(
|
RoomDetailsView(
|
||||||
state = state,
|
state = state,
|
||||||
|
additionalSections = { with(roomDetailsExtension) { Render(Modifier) } },
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
goBack = ::navigateUp,
|
goBack = ::navigateUp,
|
||||||
onActionClick = ::onActionClick,
|
onActionClick = ::onActionClick,
|
||||||
|
|||||||
+4
@@ -14,6 +14,7 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
|
|||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||||
@@ -123,6 +124,7 @@ fun RoomDetailsView(
|
|||||||
onProfileClick: (UserId) -> Unit,
|
onProfileClick: (UserId) -> Unit,
|
||||||
onReportRoomClick: () -> Unit,
|
onReportRoomClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
additionalSections: @Composable ColumnScope.() -> Unit = {},
|
||||||
leaveRoomView: @Composable () -> Unit,
|
leaveRoomView: @Composable () -> Unit,
|
||||||
) {
|
) {
|
||||||
val snackbarHostState = rememberSnackbarHostState(snackbarMessage = state.snackbarMessage)
|
val snackbarHostState = rememberSnackbarHostState(snackbarMessage = state.snackbarMessage)
|
||||||
@@ -198,6 +200,8 @@ fun RoomDetailsView(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
additionalSections()
|
||||||
|
|
||||||
PreferenceCategory {
|
PreferenceCategory {
|
||||||
if (state.hasNewContent) {
|
if (state.hasNewContent) {
|
||||||
ListItem(
|
ListItem(
|
||||||
|
|||||||
@@ -42,6 +42,9 @@
|
|||||||
<string name="screen_room_change_role_unsaved_changes_description">"You have unsaved changes."</string>
|
<string name="screen_room_change_role_unsaved_changes_description">"You have unsaved changes."</string>
|
||||||
<string name="screen_room_change_role_unsaved_changes_title">"Save changes?"</string>
|
<string name="screen_room_change_role_unsaved_changes_title">"Save changes?"</string>
|
||||||
<string name="screen_room_details_add_topic_title">"Add topic"</string>
|
<string name="screen_room_details_add_topic_title">"Add topic"</string>
|
||||||
|
<string name="screen_room_details_audio_push_to_talk">"Push to talk"</string>
|
||||||
|
<string name="screen_room_details_audio_title">"Room Audio"</string>
|
||||||
|
<string name="screen_room_details_audio_video_calls">"Video calls"</string>
|
||||||
<string name="screen_room_details_badge_encrypted">"Encrypted"</string>
|
<string name="screen_room_details_badge_encrypted">"Encrypted"</string>
|
||||||
<string name="screen_room_details_badge_not_encrypted">"Not encrypted"</string>
|
<string name="screen_room_details_badge_not_encrypted">"Not encrypted"</string>
|
||||||
<string name="screen_room_details_badge_public">"Public room"</string>
|
<string name="screen_room_details_badge_public">"Public room"</string>
|
||||||
|
|||||||
+50
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2026 Element Creations Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
|
package io.element.android.features.roomdetails.impl
|
||||||
|
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.text.BasicText
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
|
import androidx.compose.ui.test.assertCountEquals
|
||||||
|
import androidx.compose.ui.test.onChildren
|
||||||
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
|
import androidx.compose.ui.test.onRoot
|
||||||
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
|
import io.element.android.tests.testutils.robolectric.RobolectricTest
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class NoopRoomDetailsExtensionTest : RobolectricTest() {
|
||||||
|
@Test
|
||||||
|
fun `default extension renders no additional content`() = runAndroidComposeUiTest<ComponentActivity> {
|
||||||
|
val extension = NoopRoomDetailsExtension()
|
||||||
|
setContent { Column { with(extension) { Render(Modifier) } } }
|
||||||
|
onRoot().onChildren().assertCountEquals(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `default extension leaves surrounding content unchanged during updates`() = runAndroidComposeUiTest<ComponentActivity> {
|
||||||
|
val extension = NoopRoomDetailsExtension()
|
||||||
|
val text = mutableStateOf("Before")
|
||||||
|
setContent {
|
||||||
|
Column {
|
||||||
|
BasicText(text.value)
|
||||||
|
with(extension) { Render(Modifier) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onNodeWithText("Before").assertExists()
|
||||||
|
onRoot().onChildren().assertCountEquals(1)
|
||||||
|
runOnIdle { text.value = "After" }
|
||||||
|
onNodeWithText("After").assertExists()
|
||||||
|
onRoot().onChildren().assertCountEquals(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
+12
@@ -11,16 +11,20 @@
|
|||||||
package io.element.android.features.roomdetails.impl
|
package io.element.android.features.roomdetails.impl
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.test.AndroidComposeUiTest
|
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||||
import androidx.compose.ui.test.ExperimentalTestApi
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
import androidx.compose.ui.test.onAllNodesWithText
|
import androidx.compose.ui.test.onAllNodesWithText
|
||||||
import androidx.compose.ui.test.onLast
|
import androidx.compose.ui.test.onLast
|
||||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||||
import androidx.compose.ui.test.onNodeWithTag
|
import androidx.compose.ui.test.onNodeWithTag
|
||||||
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
import androidx.compose.ui.test.performClick
|
import androidx.compose.ui.test.performClick
|
||||||
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import io.element.android.features.roomdetails.impl.members.aRoomMember
|
import io.element.android.features.roomdetails.impl.members.aRoomMember
|
||||||
import io.element.android.features.userprofile.shared.aUserProfileState
|
import io.element.android.features.userprofile.shared.aUserProfileState
|
||||||
|
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.core.UserId
|
||||||
import io.element.android.libraries.matrix.api.notification.CallIntent
|
import io.element.android.libraries.matrix.api.notification.CallIntent
|
||||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||||
@@ -41,6 +45,12 @@ import org.junit.Test
|
|||||||
import org.robolectric.annotation.Config
|
import org.robolectric.annotation.Config
|
||||||
|
|
||||||
class RoomDetailsViewTest : RobolectricTest() {
|
class RoomDetailsViewTest : RobolectricTest() {
|
||||||
|
@Test
|
||||||
|
fun `extension section is rendered`() = runAndroidComposeUiTest {
|
||||||
|
setRoomDetailView(additionalSections = { Text("Extra section") })
|
||||||
|
onNodeWithText("Extra section").assertExists()
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `click on back invokes expected callback`() = runAndroidComposeUiTest {
|
fun `click on back invokes expected callback`() = runAndroidComposeUiTest {
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
@@ -381,6 +391,7 @@ private fun AndroidComposeUiTest<ComponentActivity>.setRoomDetailView(
|
|||||||
onSecurityAndPrivacyClick: () -> Unit = EnsureNeverCalled(),
|
onSecurityAndPrivacyClick: () -> Unit = EnsureNeverCalled(),
|
||||||
onProfileClick: (UserId) -> Unit = EnsureNeverCalledWithParam(),
|
onProfileClick: (UserId) -> Unit = EnsureNeverCalledWithParam(),
|
||||||
onReportRoomClick: () -> Unit = EnsureNeverCalled(),
|
onReportRoomClick: () -> Unit = EnsureNeverCalled(),
|
||||||
|
additionalSections: @Composable ColumnScope.() -> Unit = {},
|
||||||
) {
|
) {
|
||||||
setContent {
|
setContent {
|
||||||
RoomDetailsView(
|
RoomDetailsView(
|
||||||
@@ -402,6 +413,7 @@ private fun AndroidComposeUiTest<ComponentActivity>.setRoomDetailView(
|
|||||||
onProfileClick = onProfileClick,
|
onProfileClick = onProfileClick,
|
||||||
onReportRoomClick = onReportRoomClick,
|
onReportRoomClick = onReportRoomClick,
|
||||||
leaveRoomView = {},
|
leaveRoomView = {},
|
||||||
|
additionalSections = additionalSections,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user