diff --git a/appnav/build.gradle.kts b/appnav/build.gradle.kts index 1c1c1635b4..ea4c2d9a94 100644 --- a/appnav/build.gradle.kts +++ b/appnav/build.gradle.kts @@ -46,6 +46,7 @@ dependencies { implementation(projects.libraries.designsystem) implementation(projects.libraries.matrixui) implementation(projects.features.verifysession.api) + implementation(projects.features.roomdetails.api) implementation(projects.tests.uitests) implementation(libs.coil) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/RoomFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/RoomFlowNode.kt index af862129dc..6ef47e4a61 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/RoomFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/RoomFlowNode.kt @@ -26,12 +26,15 @@ import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin import com.bumble.appyx.core.plugin.plugins import com.bumble.appyx.navmodel.backstack.BackStack +import com.bumble.appyx.navmodel.backstack.operation.push import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode import io.element.android.features.messages.api.MessagesEntryPoint +import io.element.android.features.roomdetails.api.RoomDetailsEntryPoint import io.element.android.libraries.architecture.BackstackNode import io.element.android.libraries.architecture.NodeInputs +import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler import io.element.android.libraries.architecture.inputs import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.room.MatrixRoom @@ -44,6 +47,7 @@ class RoomFlowNode @AssistedInject constructor( @Assisted buildContext: BuildContext, @Assisted plugins: List, private val messagesEntryPoint: MessagesEntryPoint, + private val roomDetailsEntryPoint: RoomDetailsEntryPoint, private val appNavigationStateService: AppNavigationStateService, ) : BackstackNode( backstack = BackStack( @@ -84,7 +88,14 @@ class RoomFlowNode @AssistedInject constructor( override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { return when (navTarget) { NavTarget.Messages -> { - messagesEntryPoint.createNode(this, buildContext) + messagesEntryPoint.createNode(this, buildContext, object : MessagesEntryPoint.Callback { + override fun onRoomDetailsClicked() { + backstack.push(NavTarget.RoomDetails) + } + }) + } + NavTarget.RoomDetails -> { + roomDetailsEntryPoint.createNode(this, buildContext) } } } @@ -92,6 +103,9 @@ class RoomFlowNode @AssistedInject constructor( sealed interface NavTarget : Parcelable { @Parcelize object Messages : NavTarget + + @Parcelize + object RoomDetails : NavTarget } @Composable @@ -99,6 +113,7 @@ class RoomFlowNode @AssistedInject constructor( Children( navModel = backstack, modifier = modifier, + transitionHandler = rememberDefaultTransitionHandler(), ) } } diff --git a/build.gradle.kts b/build.gradle.kts index 431a83540f..d228aa2acb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -245,6 +245,7 @@ koverMerged { includes += "*State" excludes += "io.element.android.libraries.matrix.api.timeline.item.event.OtherState$*" excludes += "io.element.android.libraries.matrix.api.timeline.item.event.EventSendState$*" + excludes += "io.element.android.libraries.matrix.api.room.RoomMembershipState*" } bound { minValue = 90 diff --git a/changelog.d/251.feature b/changelog.d/251.feature new file mode 100644 index 0000000000..8c7bb95fd7 --- /dev/null +++ b/changelog.d/251.feature @@ -0,0 +1 @@ +Implement Room Details screen diff --git a/features/messages/api/build.gradle.kts b/features/messages/api/build.gradle.kts index 555e631a4d..31591d6a97 100644 --- a/features/messages/api/build.gradle.kts +++ b/features/messages/api/build.gradle.kts @@ -26,4 +26,5 @@ android { dependencies { implementation(projects.libraries.architecture) + implementation(projects.libraries.matrix.api) } diff --git a/features/messages/api/src/main/kotlin/io/element/android/features/messages/api/MessagesEntryPoint.kt b/features/messages/api/src/main/kotlin/io/element/android/features/messages/api/MessagesEntryPoint.kt index 718c5826e6..9f15a77f4c 100644 --- a/features/messages/api/src/main/kotlin/io/element/android/features/messages/api/MessagesEntryPoint.kt +++ b/features/messages/api/src/main/kotlin/io/element/android/features/messages/api/MessagesEntryPoint.kt @@ -16,6 +16,19 @@ package io.element.android.features.messages.api -import io.element.android.libraries.architecture.SimpleFeatureEntryPoint +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import io.element.android.libraries.architecture.FeatureEntryPoint -interface MessagesEntryPoint : SimpleFeatureEntryPoint +interface MessagesEntryPoint : FeatureEntryPoint { + fun createNode( + parentNode: Node, + buildContext: BuildContext, + callback: Callback, + ): Node + + interface Callback : Plugin { + fun onRoomDetailsClicked() + } +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/DefaultMessagesEntryPoint.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/DefaultMessagesEntryPoint.kt index 505cba1a7b..6b0df92f09 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/DefaultMessagesEntryPoint.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/DefaultMessagesEntryPoint.kt @@ -18,15 +18,21 @@ package io.element.android.features.messages.impl import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin import com.squareup.anvil.annotations.ContributesBinding import io.element.android.features.messages.api.MessagesEntryPoint import io.element.android.libraries.architecture.createNode import io.element.android.libraries.di.AppScope +import io.element.android.libraries.matrix.api.core.RoomId import javax.inject.Inject @ContributesBinding(AppScope::class) class DefaultMessagesEntryPoint @Inject constructor() : MessagesEntryPoint { - override fun createNode(parentNode: Node, buildContext: BuildContext): Node { - return parentNode.createNode(buildContext) + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + callback: MessagesEntryPoint.Callback + ): Node { + return parentNode.createNode(buildContext, listOf(callback)) } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt index 2ef24c0158..9665907eb2 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt @@ -21,10 +21,13 @@ import androidx.compose.ui.Modifier import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin +import com.bumble.appyx.core.plugin.plugins import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode +import io.element.android.features.messages.api.MessagesEntryPoint import io.element.android.libraries.di.RoomScope +import io.element.android.libraries.matrix.api.core.RoomId @ContributesNode(RoomScope::class) class MessagesNode @AssistedInject constructor( @@ -33,12 +36,19 @@ class MessagesNode @AssistedInject constructor( private val presenter: MessagesPresenter, ) : Node(buildContext, plugins = plugins) { + private val callback = plugins().firstOrNull() + + private fun onRoomDetailsClicked() { + callback?.onRoomDetailsClicked() + } + @Composable override fun View(modifier: Modifier) { val state = presenter.present() MessagesView( state = state, onBackPressed = this::navigateUp, + onRoomDetailsClicked = this::onRoomDetailsClicked, modifier = modifier ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt index 6a408b3044..f59b35319c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt @@ -21,6 +21,7 @@ package io.element.android.features.messages.impl +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -69,6 +70,7 @@ import io.element.android.libraries.designsystem.theme.components.Scaffold import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.TopAppBar import io.element.android.libraries.designsystem.utils.LogCompositions +import io.element.android.libraries.matrix.api.core.RoomId import kotlinx.coroutines.launch import timber.log.Timber @@ -77,6 +79,7 @@ fun MessagesView( state: MessagesState, modifier: Modifier = Modifier, onBackPressed: () -> Unit = {}, + onRoomDetailsClicked: () -> Unit = {}, ) { LogCompositions(tag = "MessagesScreen", msg = "Root") val itemActionsBottomSheetState = rememberModalBottomSheetState( @@ -112,7 +115,8 @@ fun MessagesView( MessagesViewTopBar( roomTitle = state.roomName, roomAvatar = state.roomAvatar, - onBackPressed = onBackPressed + onBackPressed = onBackPressed, + onRoomDetailsClicked = onRoomDetailsClicked, ) }, content = { padding -> @@ -174,6 +178,7 @@ fun MessagesViewTopBar( roomTitle: String?, roomAvatar: AvatarData?, modifier: Modifier = Modifier, + onRoomDetailsClicked: () -> Unit = {}, onBackPressed: () -> Unit = {}, ) { TopAppBar( @@ -187,7 +192,10 @@ fun MessagesViewTopBar( } }, title = { - Row(verticalAlignment = Alignment.CenterVertically) { + Row( + modifier = Modifier.clickable { onRoomDetailsClicked() }, + verticalAlignment = Alignment.CenterVertically + ) { if (roomAvatar != null) { Avatar(roomAvatar) Spacer(modifier = Modifier.width(8.dp)) diff --git a/features/rageshake/impl/src/main/res/values/localazy.xml b/features/rageshake/impl/src/main/res/values/localazy.xml index 6512513d96..192666fc1c 100644 --- a/features/rageshake/impl/src/main/res/values/localazy.xml +++ b/features/rageshake/impl/src/main/res/values/localazy.xml @@ -2,6 +2,7 @@ "Attach screenshot" "You may contact me if you have any follow up questions" + "Edit screenshot" "Please describe the bug. What did you do? What did you expect to happen? What actually happened. Please go into as much detail as you can." "Describe the bug…" "If possible, please write the description in English." diff --git a/features/roomdetails/api/.gitignore b/features/roomdetails/api/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/features/roomdetails/api/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/features/roomdetails/api/build.gradle.kts b/features/roomdetails/api/build.gradle.kts new file mode 100644 index 0000000000..545be3de57 --- /dev/null +++ b/features/roomdetails/api/build.gradle.kts @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// TODO: Remove once https://youtrack.jetbrains.com/issue/KTIJ-19369 is fixed +@Suppress("DSL_SCOPE_VIOLATION") +plugins { + id("io.element.android-library") +} + +android { + namespace = "io.element.android.features.roomdetails.api" +} + +dependencies { + implementation(projects.libraries.architecture) + implementation(projects.libraries.matrix.api) +} diff --git a/features/roomdetails/api/src/main/kotlin/io/element/android/features/roomdetails/api/RoomDetailsEntryPoint.kt b/features/roomdetails/api/src/main/kotlin/io/element/android/features/roomdetails/api/RoomDetailsEntryPoint.kt new file mode 100644 index 0000000000..e56cc7e705 --- /dev/null +++ b/features/roomdetails/api/src/main/kotlin/io/element/android/features/roomdetails/api/RoomDetailsEntryPoint.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.roomdetails.api + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import io.element.android.libraries.architecture.FeatureEntryPoint + +interface RoomDetailsEntryPoint : FeatureEntryPoint { + fun createNode(parentNode: Node, buildContext: BuildContext): Node + +} diff --git a/features/roomdetails/impl/.gitignore b/features/roomdetails/impl/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/features/roomdetails/impl/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/features/roomdetails/impl/build.gradle.kts b/features/roomdetails/impl/build.gradle.kts new file mode 100644 index 0000000000..19cf94af02 --- /dev/null +++ b/features/roomdetails/impl/build.gradle.kts @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// TODO: Remove once https://youtrack.jetbrains.com/issue/KTIJ-19369 is fixed +@Suppress("DSL_SCOPE_VIOLATION") +plugins { + id("io.element.android-compose-library") + alias(libs.plugins.anvil) + alias(libs.plugins.ksp) + id("kotlin-parcelize") +} + +android { + namespace = "io.element.android.features.roomdetails.impl" +} + +anvil { + generateDaggerFactories.set(true) +} + +dependencies { + anvil(projects.anvilcodegen) + implementation(projects.anvilannotations) + + implementation(projects.libraries.core) + implementation(projects.libraries.architecture) + implementation(projects.libraries.matrix.api) + implementation(projects.libraries.matrixui) + implementation(projects.libraries.designsystem) + implementation(projects.libraries.elementresources) + implementation(projects.libraries.uiStrings) + implementation(projects.libraries.androidutils) + api(projects.features.roomdetails.api) + implementation(libs.coil.compose) + + testImplementation(libs.test.junit) + testImplementation(libs.coroutines.test) + testImplementation(libs.molecule.runtime) + testImplementation(libs.test.truth) + testImplementation(libs.test.turbine) + testImplementation(projects.libraries.matrix.test) + + ksp(libs.showkase.processor) +} diff --git a/features/roomdetails/impl/consumer-rules.pro b/features/roomdetails/impl/consumer-rules.pro new file mode 100644 index 0000000000..e69de29bb2 diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/DefaultRoomDetailsEntryPoint.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/DefaultRoomDetailsEntryPoint.kt new file mode 100644 index 0000000000..a52575606c --- /dev/null +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/DefaultRoomDetailsEntryPoint.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.roomdetails.impl + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.features.roomdetails.api.RoomDetailsEntryPoint +import io.element.android.libraries.architecture.createNode +import io.element.android.libraries.di.AppScope +import javax.inject.Inject + +@ContributesBinding(AppScope::class) +class DefaultRoomDetailsEntryPoint @Inject constructor() : RoomDetailsEntryPoint { + override fun createNode(parentNode: Node, buildContext: BuildContext): Node { + return parentNode.createNode(buildContext) + } +} diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsEvent.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsEvent.kt new file mode 100644 index 0000000000..ad6b4ea78f --- /dev/null +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsEvent.kt @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.roomdetails.impl + +sealed interface RoomDetailsEvent diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt new file mode 100644 index 0000000000..7c7e08632d --- /dev/null +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.roomdetails.impl + +import android.os.Parcelable +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.bumble.appyx.core.composable.Children +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import com.bumble.appyx.navmodel.backstack.BackStack +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import io.element.android.anvilannotations.ContributesNode +import io.element.android.libraries.architecture.BackstackNode +import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler +import io.element.android.libraries.architecture.createNode +import io.element.android.libraries.di.RoomScope +import kotlinx.parcelize.Parcelize + +@ContributesNode(RoomScope::class) +class RoomDetailsFlowNode @AssistedInject constructor( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, +) : BackstackNode( + backstack = BackStack( + initialElement = NavTarget.RoomDetails, + savedStateMap = buildContext.savedStateMap, + ), + buildContext = buildContext, + plugins = plugins, +) { + + sealed interface NavTarget : Parcelable { + @Parcelize + object RoomDetails : NavTarget + } + + override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { + return when (navTarget) { + NavTarget.RoomDetails -> createNode(buildContext) + } + } + + @Composable + override fun View(modifier: Modifier) { + Children( + navModel = backstack, + modifier = modifier, + transitionHandler = rememberDefaultTransitionHandler(), + ) + } +} diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt new file mode 100644 index 0000000000..8ba373756b --- /dev/null +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.roomdetails.impl + +import android.content.Context +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import io.element.android.anvilannotations.ContributesNode +import io.element.android.libraries.androidutils.system.startSharePlainTextIntent +import io.element.android.libraries.di.RoomScope +import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder +import io.element.android.libraries.matrix.api.room.MatrixRoom +import io.element.android.libraries.ui.strings.R as StringR + +@ContributesNode(RoomScope::class) +class RoomDetailsNode @AssistedInject constructor( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, + private val presenter: RoomDetailsPresenter, + private val room: MatrixRoom, +) : Node(buildContext, plugins = plugins) { + + private fun onShareRoom(context: Context) { + val alias = room.alias ?: room.alternativeAliases.firstOrNull() + val permalinkResult = alias?.let { PermalinkBuilder.permalinkForRoomAlias(it) } + ?: PermalinkBuilder.permalinkForRoomId(room.roomId) + permalinkResult.onSuccess { permalink -> + startSharePlainTextIntent( + context = context, + activityResultLauncher = null, + chooserTitle = context.getString(R.string.screen_room_details_share_room_title), + text = permalink, + noActivityFoundMessage = context.getString(StringR.string.error_no_compatible_app_found) + ) + } + } + + @Composable + override fun View(modifier: Modifier) { + val context = LocalContext.current + val state = presenter.present() + RoomDetailsView( + state = state, + modifier = modifier, + goBack = { navigateUp() }, + onShareRoom = { onShareRoom(context) }, + ) + } +} diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt new file mode 100644 index 0000000000..f038787909 --- /dev/null +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.roomdetails.impl + +import androidx.compose.runtime.Composable +import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.matrix.api.room.MatrixRoom +import javax.inject.Inject + +class RoomDetailsPresenter @Inject constructor( + private val room: MatrixRoom, +) : Presenter { + + @Composable + override fun present(): RoomDetailsState { +// fun handleEvents(event: RoomDetailsEvent) {} + + return RoomDetailsState( + roomId = room.roomId.value, + roomName = room.name ?: room.displayName, + roomAlias = room.alias, + roomAvatarUrl = room.avatarUrl, + roomTopic = room.topic, + memberCount = room.members.size, + isEncrypted = room.isEncrypted, +// eventSink = ::handleEvents + ) + } +} diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt new file mode 100644 index 0000000000..ccad10bbc4 --- /dev/null +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.roomdetails.impl + +data class RoomDetailsState( + val roomId: String, + val roomName: String, + val roomAlias: String?, + val roomAvatarUrl: String?, + val roomTopic: String?, + val memberCount: Int, + val isEncrypted: Boolean, +// val eventSink: (RoomDetailsEvent) -> Unit +) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt new file mode 100644 index 0000000000..29ecc4f995 --- /dev/null +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.roomdetails.impl + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider + +open class RoomDetailsStateProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + aRoomDetailsState(), + aRoomDetailsState().copy(roomTopic = null), + aRoomDetailsState().copy(isEncrypted = false), + aRoomDetailsState().copy(roomAlias = null), + // Add other state here + ) +} + +fun aRoomDetailsState() = RoomDetailsState( + roomId = "a room id", + roomName = "Marketing", + roomAlias = "#marketing:domain.com", + roomAvatarUrl = null, + roomTopic = "Welcome to #marketing, home of the Marketing team " + + "|| WIKI PAGE: https://domain.org/wiki/Marketing " + + "|| MAIL iki/Marketing " + + "|| MAI iki/Marketing " + + "|| MAI iki/Marketing...", + memberCount = 32, + isEncrypted = true, +// eventSink = {} +) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt new file mode 100644 index 0000000000..322bc5f8ac --- /dev/null +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.roomdetails.impl + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Lock +import androidx.compose.material.icons.outlined.Person +import androidx.compose.material.icons.outlined.PersonAddAlt +import androidx.compose.material.icons.outlined.Share +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.unit.dp +import io.element.android.libraries.designsystem.ElementTextStyles +import io.element.android.libraries.designsystem.components.avatar.Avatar +import io.element.android.libraries.designsystem.components.avatar.AvatarData +import io.element.android.libraries.designsystem.components.avatar.AvatarSize +import io.element.android.libraries.designsystem.components.button.BackButton +import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory +import io.element.android.libraries.designsystem.components.preferences.PreferenceText +import io.element.android.libraries.designsystem.preview.ElementPreviewDark +import io.element.android.libraries.designsystem.preview.ElementPreviewLight +import io.element.android.libraries.designsystem.theme.LocalColors +import io.element.android.libraries.designsystem.theme.components.Scaffold +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.designsystem.theme.components.TopAppBar + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun RoomDetailsView( + state: RoomDetailsState, + goBack: () -> Unit, + onShareRoom: () -> Unit, + modifier: Modifier = Modifier, +) { + Scaffold( + modifier = modifier, + topBar = { + TopAppBar(title = { }, navigationIcon = { BackButton(onClick = goBack) }) + }, + ) { padding -> + Column(modifier = Modifier + .padding(padding) + .verticalScroll(rememberScrollState()) + ) { + HeaderSection( + avatarUrl = state.roomAvatarUrl, + roomId = state.roomId, + roomName = state.roomName, + roomAlias = state.roomAlias + ) + + ShareSection(onShareRoom = onShareRoom) + + if (state.roomTopic != null) { + TopicSection(roomTopic = state.roomTopic) + } + + MembersSection(memberCount = state.memberCount) + + if (state.isEncrypted) { + SecuritySection() + } + + OtherActionsSection() + } + } +} + +@Composable +internal fun ShareSection(onShareRoom: () -> Unit, modifier: Modifier = Modifier) { + PreferenceCategory(modifier = modifier) { + PreferenceText( + title = stringResource(R.string.screen_room_details_share_room_title), + icon = Icons.Outlined.Share, + onClick = onShareRoom, + ) + } +} + +@Composable +internal fun HeaderSection( + avatarUrl: String?, + roomId: String, + roomName: String, + roomAlias: String?, + modifier: Modifier = Modifier +) { + Column(modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) { + Box(modifier = Modifier.size(70.dp)) { + Avatar( + avatarData = AvatarData(roomId, roomName, avatarUrl, AvatarSize.HUGE), + modifier = Modifier.fillMaxSize() + ) + } + Spacer(modifier = Modifier.height(30.dp)) + Text(roomName, style = ElementTextStyles.Bold.title1) + if (roomAlias != null) { + Spacer(modifier = Modifier.height(8.dp)) + Text(roomAlias, style = ElementTextStyles.Regular.body, color = MaterialTheme.colorScheme.secondary) + } + Spacer(Modifier.height(32.dp)) + } +} + +@Composable +internal fun TopicSection(roomTopic: String, modifier: Modifier = Modifier) { + PreferenceCategory(title = stringResource(R.string.screen_room_details_topic_title), modifier = modifier) { + Text( + roomTopic, + modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 8.dp, bottom = 12.dp), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.tertiary + ) + } + +} + +@Composable +internal fun MembersSection(memberCount: Int, modifier: Modifier = Modifier) { + PreferenceCategory(modifier = modifier) { + PreferenceText( + title = stringResource(R.string.screen_room_details_people_title), + icon = Icons.Outlined.Person, + currentValue = memberCount.toString(), + ) + PreferenceText( + title = stringResource(R.string.screen_room_details_invite_people_title), + icon = Icons.Outlined.PersonAddAlt, + ) + } +} + +@Composable +internal fun SecuritySection(modifier: Modifier = Modifier) { + PreferenceCategory(title = stringResource(R.string.screen_room_details_security_title), modifier = modifier) { + PreferenceText( + title = stringResource(R.string.screen_room_details_encryption_enabled_title), + subtitle = stringResource(R.string.screen_room_details_encryption_enabled_subtitle), + icon = Icons.Outlined.Lock, + ) + } +} + +@Composable +internal fun OtherActionsSection(modifier: Modifier = Modifier) { + PreferenceCategory(showDivider = false, modifier = modifier) { + PreferenceText( + title = stringResource(R.string.screen_room_details_leave_room_title), + icon = ImageVector.vectorResource(R.drawable.ic_door_open), + tintColor = LocalColors.current.textActionCritical, + ) + } +} + +@Preview +@Composable +fun RoomDetailsLightPreview(@PreviewParameter(RoomDetailsStateProvider::class) state: RoomDetailsState) = + ElementPreviewLight { ContentToPreview(state) } + +@Preview +@Composable +fun RoomDetailsDarkPreview(@PreviewParameter(RoomDetailsStateProvider::class) state: RoomDetailsState) = + ElementPreviewDark { ContentToPreview(state) } + +@Composable +private fun ContentToPreview(state: RoomDetailsState) { + RoomDetailsView( + state = state, + goBack = {}, + onShareRoom = {}, + ) +} diff --git a/features/roomdetails/impl/src/main/res/drawable/ic_door_open.xml b/features/roomdetails/impl/src/main/res/drawable/ic_door_open.xml new file mode 100644 index 0000000000..5247201807 --- /dev/null +++ b/features/roomdetails/impl/src/main/res/drawable/ic_door_open.xml @@ -0,0 +1,10 @@ + + + diff --git a/features/roomdetails/impl/src/main/res/values/localazy.xml b/features/roomdetails/impl/src/main/res/values/localazy.xml new file mode 100644 index 0000000000..d373ed9bd1 --- /dev/null +++ b/features/roomdetails/impl/src/main/res/values/localazy.xml @@ -0,0 +1,11 @@ + + + "Messages are secured with locks. Only you and the recipients have the unique keys to unlock them." + "Message encryption enabled" + "Invite people" + "Leave room" + "People" + "Security" + "Share room" + "Topic" + \ No newline at end of file diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt new file mode 100644 index 0000000000..f77b606119 --- /dev/null +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.roomdetails + +import app.cash.molecule.RecompositionClock +import app.cash.molecule.moleculeFlow +import app.cash.turbine.test +import com.google.common.truth.Truth +import io.element.android.features.roomdetails.impl.RoomDetailsPresenter +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.room.RoomMember +import io.element.android.libraries.matrix.test.A_ROOM_ID +import io.element.android.libraries.matrix.test.A_ROOM_NAME +import io.element.android.libraries.matrix.test.room.FakeMatrixRoom +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class RoomDetailsPresenterTests { + @Test + fun `present - initial state is created from room info`() = runTest { + val room = aMatrixRoom() + val presenter = RoomDetailsPresenter(room) + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + Truth.assertThat(initialState.roomId).isEqualTo(room.roomId.value) + Truth.assertThat(initialState.roomName).isEqualTo(room.name) + Truth.assertThat(initialState.roomAvatarUrl).isEqualTo(room.avatarUrl) + Truth.assertThat(initialState.roomTopic).isEqualTo(room.topic) + Truth.assertThat(initialState.memberCount).isEqualTo(room.members.count()) + Truth.assertThat(initialState.isEncrypted).isEqualTo(room.isEncrypted) + } + } + + @Test + fun `present - initial state with no room name`() = runTest { + val room = aMatrixRoom(name = null) + val presenter = RoomDetailsPresenter(room) + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + Truth.assertThat(initialState.roomName).isEqualTo(room.displayName) + } + } +} + +fun aMatrixRoom( + roomId: RoomId = A_ROOM_ID, + name: String? = A_ROOM_NAME, + displayName: String = "A fallback display name", + topic: String? = "A topic", + avatarUrl: String? = "https://matrix.org/avatar.jpg", + members: List = emptyList(), + isEncrypted: Boolean = true, +) = FakeMatrixRoom( + roomId = roomId, + name = name, + displayName = displayName, + topic = topic, + avatarUrl = avatarUrl, + members = members, + isEncrypted = isEncrypted, +) diff --git a/features/template/src/main/kotlin/io/element/android/features/template/TemplateView.kt b/features/template/src/main/kotlin/io/element/android/features/template/TemplateView.kt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/features/template/src/test/kotlin/io/element/android/features/template/TemplatePresenterTests.kt b/features/template/src/test/kotlin/io/element/android/features/template/TemplatePresenterTests.kt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/Color.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/Color.kt index d5417b4ac5..69cfa79df1 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/Color.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/Color.kt @@ -69,3 +69,6 @@ val ElementOrange = Color(0xFFD9B072) val Vermilion = Color(0xFFFF5B55) val LinkColor = Color(0xFF0086E6) + +val TextColorCriticalLight = Color(0xFFD51928) +val TextColorCriticalDark = Color(0xfffd3e3c) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/ColorUtil.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/ColorUtil.kt index 469c70913a..7e2154b4db 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/ColorUtil.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/ColorUtil.kt @@ -28,3 +28,12 @@ fun Boolean.toEnabledColor(): Color { MaterialTheme.colorScheme.primary.copy(alpha = 0.40f) } } + +@Composable +fun Boolean.toSecondaryEnabledColor(): Color { + return if (this) { + MaterialTheme.colorScheme.secondary + } else { + MaterialTheme.colorScheme.secondary.copy(alpha = 0.40f) + } +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/Config.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/Config.kt index 1bae5df7cc..3b3bc8460e 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/Config.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/Config.kt @@ -18,5 +18,7 @@ package io.element.android.libraries.designsystem.components.preferences import androidx.compose.ui.unit.dp -internal val preferenceMinHeight = 80.dp -internal val preferencePaddingEnd = 16.dp +internal val preferenceMinHeightOnlyTitle = 48.dp +internal val preferenceMinHeight = 64.dp +internal val preferencePaddingHorizontal = 16.dp +internal val preferencePaddingVertical = 16.dp diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceCategory.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceCategory.kt index eb8decf852..2342f9fa6b 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceCategory.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceCategory.kt @@ -35,29 +35,35 @@ import io.element.android.libraries.designsystem.theme.components.Text @Composable fun PreferenceCategory( - title: String, modifier: Modifier = Modifier, + title: String? = null, + showDivider: Boolean = true, content: @Composable ColumnScope.() -> Unit, ) { Column( modifier = modifier .fillMaxWidth() ) { - Divider( - modifier = Modifier.padding(horizontal = 16.dp), - color = MaterialTheme.colorScheme.secondary, - thickness = 1.dp - ) - Text( - modifier = Modifier.padding(top = 8.dp, start = 56.dp), - style = MaterialTheme.typography.titleSmall, - color = MaterialTheme.colorScheme.primary, - text = title, - ) + if (title != null) { + PreferenceCategoryTitle(title = title) + } content() + if (showDivider) { + PreferenceDivider() + } } } +@Composable +fun PreferenceCategoryTitle(title: String, modifier: Modifier = Modifier) { + Text( + modifier = modifier.padding(top = 12.dp, start = 16.dp, end = 16.dp), + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.primary, + text = title, + ) +} + @Preview @Composable internal fun PreferenceCategoryLightPreview() = ElementPreviewLight { ContentToPreview() } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceDivider.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceDivider.kt new file mode 100644 index 0000000000..5a3bb7db44 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceDivider.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.designsystem.components.preferences + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import io.element.android.libraries.designsystem.theme.components.Divider + +@Composable +fun PreferenceDivider(modifier: Modifier = Modifier) { + Divider(modifier, thickness = 0.5.dp) +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceScreen.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceScreen.kt index dd2dd783d0..531c18ed6b 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceScreen.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceScreen.kt @@ -29,7 +29,6 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Announcement -import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.BugReport import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable @@ -39,10 +38,9 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.sp +import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewLight -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.Scaffold import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.TopAppBar @@ -92,12 +90,7 @@ fun PreferenceTopAppBar( TopAppBar( modifier = modifier, navigationIcon = { - IconButton(onClick = onBackPressed) { - Icon( - imageVector = Icons.Filled.ArrowBack, - contentDescription = "Back" - ) - } + BackButton(onClick = onBackPressed) }, title = { Row(verticalAlignment = Alignment.CenterVertically) { @@ -132,13 +125,16 @@ private fun ContentToPreview() { ) { PreferenceText( title = "Title", + subtitle = "Some other text", icon = Icons.Default.BugReport, ) + PreferenceDivider() PreferenceSwitch( title = "Switch", icon = Icons.Default.Announcement, - isChecked = true + isChecked = true, ) + PreferenceDivider() PreferenceSlide( title = "Slide", summary = "Summary", diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceSlide.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceSlide.kt index e5e6010d80..4a20b5ba28 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceSlide.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceSlide.kt @@ -23,9 +23,10 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Person import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.tooling.preview.Preview @@ -51,15 +52,15 @@ fun PreferenceSlide( Box( modifier = modifier .fillMaxWidth() - .defaultMinSize(minHeight = preferenceMinHeight), - contentAlignment = Alignment.CenterStart + .defaultMinSize(minHeight = preferenceMinHeight) + .padding(top = preferencePaddingVertical), ) { Row(modifier = Modifier.fillMaxWidth()) { PreferenceIcon(icon = icon) Column( modifier = Modifier .weight(1f) - .padding(end = preferencePaddingEnd), + .padding(end = preferencePaddingHorizontal), ) { Text( modifier = Modifier.fillMaxWidth(), @@ -97,6 +98,7 @@ internal fun PreferenceSlideDarkPreview() = ElementPreviewDark { ContentToPrevie @Composable private fun ContentToPreview() { PreferenceSlide( + icon = Icons.Default.Person, title = "Slide", summary = "Summary", value = 0.75F diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceSwitch.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceSwitch.kt index d2c5c3c5a9..61a61b1481 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceSwitch.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceSwitch.kt @@ -53,22 +53,20 @@ fun PreferenceSwitch( .clickable { onCheckedChange(!isChecked) }, contentAlignment = Alignment.CenterStart ) { - Row( - modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically - ) { + Row(modifier = Modifier.fillMaxWidth()) { PreferenceIcon( + modifier = Modifier.padding(vertical = preferencePaddingVertical), icon = icon, enabled = enabled ) Text( - modifier = Modifier.weight(1f), + modifier = Modifier.weight(1f).padding(vertical = preferencePaddingVertical), style = MaterialTheme.typography.bodyLarge, color = enabled.toEnabledColor(), text = title ) Checkbox( - modifier = Modifier.padding(end = preferencePaddingEnd), + modifier = Modifier.padding(end = preferencePaddingHorizontal).align(Alignment.CenterVertically), checked = isChecked, enabled = enabled, onCheckedChange = onCheckedChange diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceText.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceText.kt index 6ce4388c5c..7d8d219cbf 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceText.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceText.kt @@ -18,18 +18,24 @@ package io.element.android.libraries.designsystem.components.preferences import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.BugReport import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp import io.element.android.libraries.designsystem.components.preferences.components.PreferenceIcon import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewLight @@ -37,31 +43,52 @@ import io.element.android.libraries.designsystem.theme.components.Text @Composable fun PreferenceText( - title: String, - // TODO subtitle + title: String?, modifier: Modifier = Modifier, + subtitle: String? = null, + currentValue: String? = null, icon: ImageVector? = null, + tintColor: Color? = null, onClick: () -> Unit = {}, ) { + val minHeight = if (subtitle == null) preferenceMinHeightOnlyTitle else preferenceMinHeight Box( modifier = modifier .fillMaxWidth() - .defaultMinSize(minHeight = preferenceMinHeight) + .defaultMinSize(minHeight = minHeight) + .padding(end = preferencePaddingHorizontal) .clickable { onClick() }, - contentAlignment = Alignment.Center ) { Row( - modifier = Modifier.fillMaxWidth() + modifier = Modifier.fillMaxWidth().padding(vertical = preferencePaddingVertical) ) { - PreferenceIcon(icon = icon) - Text( - modifier = Modifier - .weight(1f) - .padding(end = preferencePaddingEnd), - style = MaterialTheme.typography.bodyLarge, - text = title, - color = MaterialTheme.colorScheme.primary, - ) + PreferenceIcon(icon = icon, tintColor = tintColor) + Column(modifier = Modifier + .weight(1f) + .align(Alignment.CenterVertically) + ) { + if (title != null) { + Text( + style = MaterialTheme.typography.bodyLarge, + text = title, + color = tintColor ?: MaterialTheme.colorScheme.primary, + ) + } + if (title != null && subtitle != null) { + Spacer(modifier = Modifier.height(8.dp)) + } + if (subtitle != null) { + Text( + style = MaterialTheme.typography.bodySmall, + text = subtitle, + color = tintColor ?: MaterialTheme.colorScheme.tertiary, + ) + } + } + if (currentValue != null) { + Text(currentValue, style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.secondary) + Spacer(Modifier.width(16.dp)) + } } } } @@ -78,6 +105,7 @@ internal fun PreferenceTextDarkPreview() = ElementPreviewDark { ContentToPreview private fun ContentToPreview() { PreferenceText( title = "Title", + subtitle = "Some content", icon = Icons.Default.BugReport, ) } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/components/PreferenceIcon.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/components/PreferenceIcon.kt index 7f8f3ea17d..991e4f06aa 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/components/PreferenceIcon.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/components/PreferenceIcon.kt @@ -17,10 +17,12 @@ package io.element.android.libraries.designsystem.components.preferences.components import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter @@ -28,25 +30,30 @@ import androidx.compose.ui.unit.dp import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.theme.components.Icon -import io.element.android.libraries.designsystem.toEnabledColor +import io.element.android.libraries.designsystem.toSecondaryEnabledColor @Composable fun PreferenceIcon( icon: ImageVector?, modifier: Modifier = Modifier, - enabled: Boolean = true + tintColor: Color? = null, + enabled: Boolean = true, + isVisible: Boolean = true, ) { if (icon != null) { Icon( imageVector = icon, contentDescription = "", - tint = enabled.toEnabledColor(), + tint = tintColor ?: enabled.toSecondaryEnabledColor(), modifier = modifier .padding(start = 8.dp) - .width(48.dp), + .width(48.dp) + .heightIn(max = 48.dp), ) - } else { + } else if (isVisible) { Spacer(modifier = modifier.width(56.dp)) + } else { + Spacer(modifier = modifier.width(16.dp)) } } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ColorsDark.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ColorsDark.kt index 46b44cd9e5..42e97a5090 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ColorsDark.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ColorsDark.kt @@ -29,6 +29,7 @@ import io.element.android.libraries.designsystem.Gray_400 import io.element.android.libraries.designsystem.Gray_450 import io.element.android.libraries.designsystem.SystemGrey5Dark import io.element.android.libraries.designsystem.SystemGrey6Dark +import io.element.android.libraries.designsystem.TextColorCriticalDark import io.element.android.libraries.designsystem.theme.previews.ColorsSchemePreview fun elementColorsDark() = ElementColors( @@ -37,6 +38,7 @@ fun elementColorsDark() = ElementColors( messageHighlightedBackground = Azure, quaternary = Gray_400, quinary = Gray_450, + textActionCritical = TextColorCriticalDark, isLight = false, ) @@ -69,7 +71,7 @@ val materialColorSchemeDark = darkColorScheme( // TODO errorContainer = ColorDarkTokens.ErrorContainer, // TODO onErrorContainer = ColorDarkTokens.OnErrorContainer, // TODO outline = ColorDarkTokens.Outline, - // TODO outlineVariant = ColorDarkTokens.OutlineVariant, + outlineVariant = Gray_450, // TODO scrim = ColorDarkTokens.Scrim, ) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ColorsLight.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ColorsLight.kt index 96f0db2c78..77ced0b9f4 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ColorsLight.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ColorsLight.kt @@ -29,6 +29,7 @@ import io.element.android.libraries.designsystem.Gray_25 import io.element.android.libraries.designsystem.Gray_50 import io.element.android.libraries.designsystem.SystemGrey5Light import io.element.android.libraries.designsystem.SystemGrey6Light +import io.element.android.libraries.designsystem.TextColorCriticalLight import io.element.android.libraries.designsystem.theme.previews.ColorsSchemePreview fun elementColorsLight() = ElementColors( @@ -37,6 +38,7 @@ fun elementColorsLight() = ElementColors( messageHighlightedBackground = Azure, quaternary = Gray_100, quinary = Gray_50, + textActionCritical = TextColorCriticalLight, isLight = true, ) @@ -69,7 +71,7 @@ val materialColorSchemeLight = lightColorScheme( // TODO errorContainer = ColorLightTokens.ErrorContainer, // TODO onErrorContainer = ColorLightTokens.OnErrorContainer, // TODO outline = ColorLightTokens.Outline, - // TODO outlineVariant = ColorLightTokens.OutlineVariant, + outlineVariant = Gray_50, // TODO scrim = ColorLightTokens.Scrim, ) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ElementColors.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ElementColors.kt index 6aa489940d..c15f0382c0 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ElementColors.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ElementColors.kt @@ -29,7 +29,8 @@ class ElementColors( messageHighlightedBackground: Color, quaternary: Color, quinary: Color, - isLight: Boolean, + textActionCritical: Color, + isLight: Boolean ) { var messageFromMeBackground by mutableStateOf(messageFromMeBackground) private set @@ -44,6 +45,9 @@ class ElementColors( var quinary by mutableStateOf(quinary) private set + var textActionCritical by mutableStateOf(textActionCritical) + private set + var isLight by mutableStateOf(isLight) private set @@ -53,6 +57,7 @@ class ElementColors( messageHighlightedBackground: Color = this.messageHighlightedBackground, quaternary: Color = this.quaternary, quinary: Color = this.quinary, + textActionCritical: Color = this.textActionCritical, isLight: Boolean = this.isLight, ) = ElementColors( messageFromMeBackground = messageFromMeBackground, @@ -60,6 +65,7 @@ class ElementColors( messageHighlightedBackground = messageHighlightedBackground, quaternary = quaternary, quinary = quinary, + textActionCritical = textActionCritical, isLight = isLight, ) @@ -69,6 +75,7 @@ class ElementColors( messageHighlightedBackground = other.messageHighlightedBackground quaternary = other.quaternary quinary = other.quinary + textActionCritical = other.textActionCritical isLight = other.isLight } } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/TopAppBar.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/TopAppBar.kt index a187a0ae12..fb9dfba1d9 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/TopAppBar.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/TopAppBar.kt @@ -38,7 +38,7 @@ fun TopAppBar( navigationIcon: @Composable () -> Unit = {}, actions: @Composable RowScope.() -> Unit = {}, windowInsets: WindowInsets = TopAppBarDefaults.windowInsets, - colors: TopAppBarColors = TopAppBarDefaults.smallTopAppBarColors(), + colors: TopAppBarColors = TopAppBarDefaults.topAppBarColors(), scrollBehavior: TopAppBarScrollBehavior? = null ) { androidx.compose.material3.TopAppBar( diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/config/MatrixConfiguration.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/config/MatrixConfiguration.kt new file mode 100644 index 0000000000..ddce776627 --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/config/MatrixConfiguration.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.api.config + +object MatrixConfiguration { + const val matrixToPermalinkBaseUrl: String = "https://matrix.to/#/" + val clientPermalinkBaseUrl: String? = null +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/MatrixToConverter.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/MatrixToConverter.kt index 032f45def6..e352dd5cfc 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/MatrixToConverter.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/MatrixToConverter.kt @@ -17,14 +17,13 @@ package io.element.android.libraries.matrix.api.permalink import android.net.Uri +import io.element.android.libraries.matrix.api.config.MatrixConfiguration /** * Mapping of an input URI to a matrix.to compliant URI. */ object MatrixToConverter { - const val MATRIX_TO_URL_BASE = "https://matrix.to/#/" - /** * Try to convert a URL from an element web instance or from a client permalink to a matrix.to url. * To be successfully converted, URL path should contain one of the [SUPPORTED_PATHS]. @@ -35,14 +34,15 @@ object MatrixToConverter { */ fun convert(uri: Uri): Uri? { val uriString = uri.toString() + val baseUrl = MatrixConfiguration.matrixToPermalinkBaseUrl return when { // URL is already a matrix.to - uriString.startsWith(MATRIX_TO_URL_BASE) -> uri + uriString.startsWith(baseUrl) -> uri // Web or client url SUPPORTED_PATHS.any { it in uriString } -> { val path = SUPPORTED_PATHS.first { it in uriString } - Uri.parse(MATRIX_TO_URL_BASE + uriString.substringAfter(path)) + Uri.parse(baseUrl + uriString.substringAfter(path)) } // URL is not supported else -> null diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkBuilder.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkBuilder.kt new file mode 100644 index 0000000000..92e2bcef1d --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkBuilder.kt @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.api.permalink + +import io.element.android.libraries.matrix.api.config.MatrixConfiguration +import io.element.android.libraries.matrix.api.core.MatrixPatterns +import io.element.android.libraries.matrix.api.core.RoomId + +object PermalinkBuilder { + private val permalinkBaseUrl get() = (MatrixConfiguration.clientPermalinkBaseUrl ?: MatrixConfiguration.matrixToPermalinkBaseUrl).also { + var baseUrl = it + if (!baseUrl.endsWith("/")) { + baseUrl += "/" + } + if (!baseUrl.endsWith("/#/")) { + baseUrl += "/#/" + } + } + + fun permalinkForRoomAlias(roomAlias: String): Result { + return if (MatrixPatterns.isRoomAlias(roomAlias)) { + Result.success(permalinkForRoomAliasOrId(roomAlias)) + } else { + Result.failure(PermalinkBuilderError.InvalidRoomAlias) + } + } + + fun permalinkForRoomId(roomId: RoomId): Result { + return if (MatrixPatterns.isRoomId(roomId.value)) { + Result.success(permalinkForRoomAliasOrId(roomId.value)) + } else { + Result.failure(PermalinkBuilderError.InvalidRoomId) + } + } + + private fun permalinkForRoomAliasOrId(value: String): String { + val id = escapeId(value) + return permalinkBaseUrl + id + } + + private fun escapeId(value: String) = value.replace("/", "%2F") +} + +sealed class PermalinkBuilderError : Throwable() { + object InvalidRoomAlias : PermalinkBuilderError() + object InvalidRoomId : PermalinkBuilderError() +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt index 4215fb08c9..07d466d428 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt @@ -27,8 +27,12 @@ interface MatrixRoom: Closeable { val name: String? val bestName: String val displayName: String + val alias: String? + val alternativeAliases: List val topic: String? val avatarUrl: String? + val members: List + val isEncrypted: Boolean fun syncUpdateFlow(): Flow diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMember.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMember.kt new file mode 100644 index 0000000000..66de9bd627 --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMember.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.api.room + +data class RoomMember( + val userId: String, + val displayName: String?, + val avatarUrl: String?, + val membership: RoomMembershipState, + val isNameAmbiguous: Boolean, + val powerLevel: Long, + val normalizedPowerLevel: Long +) + +enum class RoomMembershipState { + BAN, INVITE, JOIN, KNOCK, LEAVE +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomMemberMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomMemberMapper.kt new file mode 100644 index 0000000000..0347bf3b76 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomMemberMapper.kt @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.impl.room + +import io.element.android.libraries.matrix.api.room.RoomMember +import io.element.android.libraries.matrix.api.room.RoomMembershipState +import org.matrix.rustcomponents.sdk.MembershipState as RustMembershipState +import org.matrix.rustcomponents.sdk.RoomMember as RustRoomMember + +object RoomMemberMapper { + + fun map(roomMember: RustRoomMember): RoomMember = + RoomMember( + roomMember.userId, + roomMember.displayName, + roomMember.avatarUrl, + mapMembership(roomMember.membership), + roomMember.isNameAmbiguous, + roomMember.powerLevel, + roomMember.normalizedPowerLevel, + ) + + fun mapMembership(membershipState: RustMembershipState): RoomMembershipState = + when (membershipState) { + RustMembershipState.BAN -> RoomMembershipState.BAN + RustMembershipState.INVITE -> RoomMembershipState.INVITE + RustMembershipState.JOIN -> RoomMembershipState.JOIN + RustMembershipState.KNOCK -> RoomMembershipState.KNOCK + RustMembershipState.LEAVE -> RoomMembershipState.LEAVE + } + +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt index a86c08dec0..eb3bc1c7d2 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt @@ -20,6 +20,7 @@ import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.room.MatrixRoom +import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.timeline.MatrixTimeline import io.element.android.libraries.matrix.impl.timeline.RustMatrixTimeline import kotlinx.coroutines.CoroutineScope @@ -94,6 +95,18 @@ class RustMatrixRoom( return innerRoom.avatarUrl() } + override val members: List + get() = innerRoom.members().map(RoomMemberMapper::map) + + override val isEncrypted: Boolean + get() = innerRoom.isEncrypted() + + override val alias: String? + get() = innerRoom.canonicalAlias() + + override val alternativeAliases: List + get() = innerRoom.alternativeAliases() + override suspend fun fetchMembers(): Result = withContext(coroutineDispatchers.io) { runCatching { innerRoom.fetchMembers() diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt index 4e4c19458d..15a79c3586 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt @@ -35,7 +35,9 @@ import kotlinx.coroutines.flow.sample import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.matrix.rustcomponents.sdk.PaginationOptions +import org.matrix.rustcomponents.sdk.RequiredState import org.matrix.rustcomponents.sdk.Room +import org.matrix.rustcomponents.sdk.RoomSubscription import org.matrix.rustcomponents.sdk.SlidingSyncRoom import org.matrix.rustcomponents.sdk.TimelineItem import org.matrix.rustcomponents.sdk.TimelineListener @@ -145,7 +147,8 @@ class RustMatrixTimeline( private suspend fun addListener(timelineListener: TimelineListener): Result> = withContext(coroutineDispatchers.io) { runCatching { - val result = slidingSyncRoom.subscribeAndAddTimelineListener(timelineListener, null) + val settings = RoomSubscription(requiredState = listOf(RequiredState(key = "m.room.canonical_alias", value = "")), timelineLimit = null) + val result = slidingSyncRoom.subscribeAndAddTimelineListener(timelineListener, settings) listenerTokens += result.taskHandle result.items } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt index 3d11e4d75b..32e73121c8 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt @@ -19,6 +19,7 @@ package io.element.android.libraries.matrix.test.room import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.room.MatrixRoom +import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.timeline.FakeMatrixTimeline import io.element.android.libraries.matrix.api.timeline.MatrixTimeline @@ -33,6 +34,10 @@ class FakeMatrixRoom( override val displayName: String = "", override val topic: String? = null, override val avatarUrl: String? = null, + override val members: List = emptyList(), + override val isEncrypted: Boolean = false, + override val alias: String? = null, + override val alternativeAliases: List = emptyList(), private val matrixTimeline: MatrixTimeline = FakeMatrixTimeline(), ) : MatrixRoom { diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index e79dafbdf6..a28d8bb7bc 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -17,7 +17,6 @@ "Disable" "Done" "Edit" - "Edit screenshot" "Enable" "Invite" "Invite friends to %1$s" @@ -39,6 +38,7 @@ "Save" "Search" "Send" + "Share link" "Skip" "Start" "Start chat" @@ -63,6 +63,7 @@ "Message layout" "Message removed" "Modern" + "No results" "Offline" "Password" "People" @@ -103,6 +104,7 @@ "Symbols" "Failed creating the permalink" "Failed loading messages" + "No compatible app was found to handle this action." "Some messages have not been sent" "Sorry, an error occurred" "Hey, talk to me on %1$s: %2$s" @@ -125,13 +127,13 @@ "This is the beginning of %1$s." "This is the beginning of this conversation." "New" - "Messages are secured with locks. Only you and the recipients have the unique keys to unlock them." - "Message encryption enabled" - "Invite people" - "Leave room" - "People" - "Security" - "Topic" + "Block" + "Blocked users will not be able to send you messages and all message by them will be hidden. You can reverse this action anytime." + "Block user" + "Unblock" + "On unblocking the user, you will be able to see all messages by them again." + "Unblock user" + "An error occurred when trying to start a chat" "Rageshake" "Detection threshold" "General" diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/MobileScreen.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/MobileScreen.kt index 69e637b01e..a474dd1faf 100644 --- a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/MobileScreen.kt +++ b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/MobileScreen.kt @@ -124,7 +124,7 @@ data class MobileScreen( * The screen shown when tapping the name of a room from the Room * screen. */ - RoomDetails, + RoomDetailss, /** * The screen that lists public rooms for you to discover. diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.root_null_DefaultGroup_PreferencesRootViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.root_null_DefaultGroup_PreferencesRootViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png index a59e085274..9a09df0348 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.root_null_DefaultGroup_PreferencesRootViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.root_null_DefaultGroup_PreferencesRootViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:47606ad889ab978c5ccb3bcac3ead43a85cc2a83ce15e5520ac2e271d6b5f473 -size 44415 +oid sha256:4da4fd3b7c17b00bd17639a8ec5c6315abdf36b491c7da874f865bc386e622d8 +size 45198 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.root_null_DefaultGroup_PreferencesRootViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.root_null_DefaultGroup_PreferencesRootViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png index 0a5ae4c7cd..3b86dfc9c4 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.root_null_DefaultGroup_PreferencesRootViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.root_null_DefaultGroup_PreferencesRootViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:92439979ab7bdb1a2c6ff1e98137a358ed788c3977c0ba7a1100eff41e22de79 -size 44282 +oid sha256:3a191ed88e984ca94c890b644fcee602539bf72a2e402be64caeb189db12bb80 +size 44940 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.root_null_DefaultGroup_PreferencesRootViewLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.root_null_DefaultGroup_PreferencesRootViewLightPreview_0_null_0,NEXUS_5,1.0,en].png index cf7f21e2f8..93698a1ecd 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.root_null_DefaultGroup_PreferencesRootViewLightPreview_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.root_null_DefaultGroup_PreferencesRootViewLightPreview_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:453dc174a87e3f43fe3bf7915634c3351b395ee05e488e37e37fd30c643c98fb -size 43160 +oid sha256:9083a3f1146c5aa30c39ced0f535eddd38cafb2bad6c509717c10b74356a844b +size 43877 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.root_null_DefaultGroup_PreferencesRootViewLightPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.root_null_DefaultGroup_PreferencesRootViewLightPreview_0_null_1,NEXUS_5,1.0,en].png index 6ae27227b3..52fef485fb 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.root_null_DefaultGroup_PreferencesRootViewLightPreview_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.root_null_DefaultGroup_PreferencesRootViewLightPreview_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8f2da5aa58c2c32994c11ae03bb3aa3ff4f310ea306eec056cc521eccf305772 -size 42980 +oid sha256:e4b03c928ec2cd4dbcc7fce37c0374b1e2b794d473d6ecc11bc5c33cc6953cca +size 43699 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview_0_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..e10bcdc525 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview_0_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6e1e4858da2e25786e5ef953d0456271ca7dcecc27edb1bf1bd2c46575d3c241 +size 70626 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview_0_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..e7b43b6259 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview_0_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7f5cce767021a2b93d7c98f05d566a9107c72805a446504b5da2e15a9298e586 +size 55944 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview_0_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..6ed5f72ddc --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview_0_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b4a97b7be4416c384ac4f8f9adde64f82dfc0fd0dcd137cd763508215785ea27 +size 56122 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview_0_null_3,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..4dcea7f871 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview_0_null_3,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:12919612683b962d9a835b4442a29247c64c17c152ac617319eb5d14d80631b9 +size 70453 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview_0_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..eec2e03e89 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview_0_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:20c39d6c718e3831a454e06a854addf65537a4fc15d2e09adb6b00e87491e014 +size 65179 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview_0_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..3808244b53 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview_0_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8bc9b7dde35c828d0158de1b8cc872416a1b4778a352b0c64cdf93c3bbc41c86 +size 52891 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview_0_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..740e7dd3c5 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview_0_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:68b646c429285f1827c7646fb170c0ac1a3e995872292bc2ddcf298fb565bcbe +size 52388 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview_0_null_3,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..032bbecb26 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview_0_null_3,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1c05c36d07d5b3b95d66233cd45053419a460e38ae940b86f125a42b57307985 +size 64443 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.preferences.components_null_DefaultGroup_PreferenceIconDarkPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.preferences.components_null_DefaultGroup_PreferenceIconDarkPreview_0_null_1,NEXUS_5,1.0,en].png index 7993de7638..82934e1d10 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.preferences.components_null_DefaultGroup_PreferenceIconDarkPreview_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.preferences.components_null_DefaultGroup_PreferenceIconDarkPreview_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eedd92351d9ffb9ebd519b88e0875196a10d7b7bf7db82e6d044cb6ec200970e -size 5195 +oid sha256:d59b2a6b17f548953ce8521acae9e9f89b3c56245c4722c8c099af18453018d4 +size 5285 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.preferences.components_null_DefaultGroup_PreferenceIconLightPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.preferences.components_null_DefaultGroup_PreferenceIconLightPreview_0_null_1,NEXUS_5,1.0,en].png index 8801406443..761f6b8c49 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.preferences.components_null_DefaultGroup_PreferenceIconLightPreview_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.preferences.components_null_DefaultGroup_PreferenceIconLightPreview_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ceab55b3080020e622f1c8e44270907716fc777bd25d9d0573a9ea8dab643490 -size 5043 +oid sha256:5ee3a6bcf3ce1f24c143de680dd8aa326166d5fbc95fd8fecc4c0b15dfb6d7ce +size 5008 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.preferences_null_DefaultGroup_PreferenceCategoryDarkPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.preferences_null_DefaultGroup_PreferenceCategoryDarkPreview_0_null,NEXUS_5,1.0,en].png index fb5194c12c..27b0af77cb 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.preferences_null_DefaultGroup_PreferenceCategoryDarkPreview_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.preferences_null_DefaultGroup_PreferenceCategoryDarkPreview_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:854525a9d9ca96fc53329faa4f816587da18663bb66c89a09870a01926370699 -size 16394 +oid sha256:4a36dadd693d2bf6c9ca2bfea6ec4e783248f8d3e5ca88d5cfde468b4bda116a +size 17055 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.preferences_null_DefaultGroup_PreferenceCategoryLightPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.preferences_null_DefaultGroup_PreferenceCategoryLightPreview_0_null,NEXUS_5,1.0,en].png index 589cafdf2f..ab7bbd2bbd 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.preferences_null_DefaultGroup_PreferenceCategoryLightPreview_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.preferences_null_DefaultGroup_PreferenceCategoryLightPreview_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b1ad6738aa4046b8c46e2b581489c6114ae669e94142d64198423e28797c3843 -size 15659 +oid sha256:e5403566ef1d4d8cb25904fc74353197ad83e7ec452517a9cb696264a1609e32 +size 15999 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.preferences_null_DefaultGroup_PreferenceSlideDarkPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.preferences_null_DefaultGroup_PreferenceSlideDarkPreview_0_null,NEXUS_5,1.0,en].png index e6dea99479..2b029eb5ab 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.preferences_null_DefaultGroup_PreferenceSlideDarkPreview_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.preferences_null_DefaultGroup_PreferenceSlideDarkPreview_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2dba6b0eaa74d3c9dd3b62de76506198ce7c3aa6e5c7fca3d52bea3b56fb2ae3 -size 9103 +oid sha256:ed6b297eeed4301c11caa9b59b8e7bdcebd63ca89219dee2efba11936c681abe +size 9617 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.preferences_null_DefaultGroup_PreferenceSlideLightPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.preferences_null_DefaultGroup_PreferenceSlideLightPreview_0_null,NEXUS_5,1.0,en].png index ca4d441ede..6a419386b9 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.preferences_null_DefaultGroup_PreferenceSlideLightPreview_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.preferences_null_DefaultGroup_PreferenceSlideLightPreview_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7b944575976d4971f76188d21740fd5b7c3a2937ab22b6e60688142e56296a8e -size 8797 +oid sha256:73c4ab7efd89ec8d9ca035346bbca9b073112d31065495f148b6510ff1ee23e6 +size 9203 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.preferences_null_DefaultGroup_PreferenceSwitchDarkPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.preferences_null_DefaultGroup_PreferenceSwitchDarkPreview_0_null,NEXUS_5,1.0,en].png index 4963d0cf7a..beca0f73ed 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.preferences_null_DefaultGroup_PreferenceSwitchDarkPreview_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.preferences_null_DefaultGroup_PreferenceSwitchDarkPreview_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:44fd34661da1a384fbb0df08f3dc7226bd723a33138d1ac8ad3ef50930c84e0c -size 7587 +oid sha256:4665f288b04b8407f2ba25c272a56dd1d96b65523d76e08e6283156bf91f20ed +size 7818 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.preferences_null_DefaultGroup_PreferenceSwitchLightPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.preferences_null_DefaultGroup_PreferenceSwitchLightPreview_0_null,NEXUS_5,1.0,en].png index de9c3cf0ae..66460e224e 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.preferences_null_DefaultGroup_PreferenceSwitchLightPreview_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.preferences_null_DefaultGroup_PreferenceSwitchLightPreview_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:46bf34a9b246650a019c6983b17b76b11d4af6a0525e4ca87ca170d11c209144 -size 7303 +oid sha256:8b45fd891f3e3b38301be79159ec1e6d04114de58b2170a0649656780f64f5ea +size 7312 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.preferences_null_DefaultGroup_PreferenceTextDarkPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.preferences_null_DefaultGroup_PreferenceTextDarkPreview_0_null,NEXUS_5,1.0,en].png index c5b6e353a2..80a6d4719e 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.preferences_null_DefaultGroup_PreferenceTextDarkPreview_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.preferences_null_DefaultGroup_PreferenceTextDarkPreview_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:26f29ecbb43cc403577c79747d21ef821810fe741df781541f0b8426be85f4cd -size 6145 +oid sha256:3781254877c86737c2e042483f4a93e4608ce49994ec70a8c3e9e73124ea1388 +size 8764 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.preferences_null_DefaultGroup_PreferenceTextLightPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.preferences_null_DefaultGroup_PreferenceTextLightPreview_0_null,NEXUS_5,1.0,en].png index 70039953f6..c0f777fd4d 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.preferences_null_DefaultGroup_PreferenceTextLightPreview_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.preferences_null_DefaultGroup_PreferenceTextLightPreview_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dfbb4b967d7b5a2bf5cb9f9b8ccffa72d87a77442d31dcce44e5b9f68c0fb259 -size 5896 +oid sha256:7505951ac4acf5cc0887af7bde9b8e659ae76643f5b6e639e24ee1df85448ece +size 7970 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.preferences_null_DefaultGroup_PreferenceViewDarkPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.preferences_null_DefaultGroup_PreferenceViewDarkPreview_0_null,NEXUS_5,1.0,en].png index eee33ef274..ffbfcb7846 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.preferences_null_DefaultGroup_PreferenceViewDarkPreview_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.preferences_null_DefaultGroup_PreferenceViewDarkPreview_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:df1b8d096d1b93065c7afb24873296c2ff19c91b8bbab662c42e7aa83b6ad9e0 -size 19801 +oid sha256:f250b20941e26ca156589a49f8e10ba34f1d29e56af60ab39dca65e83bc617fb +size 22411 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.preferences_null_DefaultGroup_PreferenceViewLightPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.preferences_null_DefaultGroup_PreferenceViewLightPreview_0_null,NEXUS_5,1.0,en].png index 51133fdb4b..73c772154e 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.preferences_null_DefaultGroup_PreferenceViewLightPreview_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.preferences_null_DefaultGroup_PreferenceViewLightPreview_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:576a65721a13bdbbb4bb5ff76d0727886e5c89931d1258c9d946aed6e8477256 -size 19299 +oid sha256:1f13b118db744d879d693a6145d86152cc57182ff706597f7bfc83f2826d9ddc +size 21566 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.theme.components_null_DefaultGroup_DividerDarkPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.theme.components_null_DefaultGroup_DividerDarkPreview_0_null,NEXUS_5,1.0,en].png index 8d3376900b..30a7c386a5 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.theme.components_null_DefaultGroup_DividerDarkPreview_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.theme.components_null_DefaultGroup_DividerDarkPreview_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:274521bb420e73dcce6bca9d4328909d896998e4fdf3cebe79dd7c6c4873db45 +oid sha256:047fbf34978c9b8d9b800b91e97bf95a94cedb31ddaf347d5e0000571ea491a9 size 4469 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.theme.components_null_DefaultGroup_DividerLightPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.theme.components_null_DefaultGroup_DividerLightPreview_0_null,NEXUS_5,1.0,en].png index 3ffdafd3ac..00e3a9ac3f 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.theme.components_null_DefaultGroup_DividerLightPreview_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.theme.components_null_DefaultGroup_DividerLightPreview_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9e3a82fb55f7a5be9ddbb5a8146d4fe53ad6111e21b40912428fd95dde85346c -size 4470 +oid sha256:234e0efe349185340b3efcaa181b31670a9dd55b57378e0b9c0b543f9ded70c2 +size 4468 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.theme_null_DefaultGroup_ColorsSchemePreviewDark_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.theme_null_DefaultGroup_ColorsSchemePreviewDark_0_null,NEXUS_5,1.0,en].png index 9ebec7daa3..76a0ceaabd 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.theme_null_DefaultGroup_ColorsSchemePreviewDark_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.theme_null_DefaultGroup_ColorsSchemePreviewDark_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cc8026b2bf6fbc4afaf2641d2deba1b3ae27d582dbc30c70841356232aa8cbbe -size 117237 +oid sha256:62b824f55b718ea805b97ce75810cd5c5de5498831aa8e5c8fa449121c48ea0c +size 117342 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.theme_null_DefaultGroup_ColorsSchemePreviewLight_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.theme_null_DefaultGroup_ColorsSchemePreviewLight_0_null,NEXUS_5,1.0,en].png index 121c346acf..616e1b07f1 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.theme_null_DefaultGroup_ColorsSchemePreviewLight_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.theme_null_DefaultGroup_ColorsSchemePreviewLight_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8c0847975608f68e32e497a043c373cd471b18dcefb1cfd0a6abbfb2e3963265 -size 115619 +oid sha256:da75446e5bc981fd21e95ff1b23ccccbf11bc4f9461982ed46b72c0182061abb +size 115627 diff --git a/tools/localazy/config.json b/tools/localazy/config.json index 8061d126a9..781807d057 100644 --- a/tools/localazy/config.json +++ b/tools/localazy/config.json @@ -57,6 +57,12 @@ "screen_roomlist_.*", "session_verification_banner_.*" ] + }, + { + "name": ":features:roomdetails:impl", + "includeRegex": [ + "screen_room_details_.*" + ] } ] }