[Room Details] Implement room details screen (#256)
* Implement Room Details screen * Add option to create permalink from room id and alias, add share room action
This commit is contained in:
parent
c75ebfb1e7
commit
0fb061fb2e
83 changed files with 1203 additions and 117 deletions
|
|
@ -46,6 +46,7 @@ dependencies {
|
||||||
implementation(projects.libraries.designsystem)
|
implementation(projects.libraries.designsystem)
|
||||||
implementation(projects.libraries.matrixui)
|
implementation(projects.libraries.matrixui)
|
||||||
implementation(projects.features.verifysession.api)
|
implementation(projects.features.verifysession.api)
|
||||||
|
implementation(projects.features.roomdetails.api)
|
||||||
implementation(projects.tests.uitests)
|
implementation(projects.tests.uitests)
|
||||||
implementation(libs.coil)
|
implementation(libs.coil)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,12 +26,15 @@ import com.bumble.appyx.core.node.Node
|
||||||
import com.bumble.appyx.core.plugin.Plugin
|
import com.bumble.appyx.core.plugin.Plugin
|
||||||
import com.bumble.appyx.core.plugin.plugins
|
import com.bumble.appyx.core.plugin.plugins
|
||||||
import com.bumble.appyx.navmodel.backstack.BackStack
|
import com.bumble.appyx.navmodel.backstack.BackStack
|
||||||
|
import com.bumble.appyx.navmodel.backstack.operation.push
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import io.element.android.anvilannotations.ContributesNode
|
import io.element.android.anvilannotations.ContributesNode
|
||||||
import io.element.android.features.messages.api.MessagesEntryPoint
|
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.BackstackNode
|
||||||
import io.element.android.libraries.architecture.NodeInputs
|
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.architecture.inputs
|
||||||
import io.element.android.libraries.di.SessionScope
|
import io.element.android.libraries.di.SessionScope
|
||||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||||
|
|
@ -44,6 +47,7 @@ class RoomFlowNode @AssistedInject constructor(
|
||||||
@Assisted buildContext: BuildContext,
|
@Assisted buildContext: BuildContext,
|
||||||
@Assisted plugins: List<Plugin>,
|
@Assisted plugins: List<Plugin>,
|
||||||
private val messagesEntryPoint: MessagesEntryPoint,
|
private val messagesEntryPoint: MessagesEntryPoint,
|
||||||
|
private val roomDetailsEntryPoint: RoomDetailsEntryPoint,
|
||||||
private val appNavigationStateService: AppNavigationStateService,
|
private val appNavigationStateService: AppNavigationStateService,
|
||||||
) : BackstackNode<RoomFlowNode.NavTarget>(
|
) : BackstackNode<RoomFlowNode.NavTarget>(
|
||||||
backstack = BackStack(
|
backstack = BackStack(
|
||||||
|
|
@ -84,7 +88,14 @@ class RoomFlowNode @AssistedInject constructor(
|
||||||
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
||||||
return when (navTarget) {
|
return when (navTarget) {
|
||||||
NavTarget.Messages -> {
|
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 {
|
sealed interface NavTarget : Parcelable {
|
||||||
@Parcelize
|
@Parcelize
|
||||||
object Messages : NavTarget
|
object Messages : NavTarget
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
object RoomDetails : NavTarget
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -99,6 +113,7 @@ class RoomFlowNode @AssistedInject constructor(
|
||||||
Children(
|
Children(
|
||||||
navModel = backstack,
|
navModel = backstack,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
|
transitionHandler = rememberDefaultTransitionHandler(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -245,6 +245,7 @@ koverMerged {
|
||||||
includes += "*State"
|
includes += "*State"
|
||||||
excludes += "io.element.android.libraries.matrix.api.timeline.item.event.OtherState$*"
|
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.timeline.item.event.EventSendState$*"
|
||||||
|
excludes += "io.element.android.libraries.matrix.api.room.RoomMembershipState*"
|
||||||
}
|
}
|
||||||
bound {
|
bound {
|
||||||
minValue = 90
|
minValue = 90
|
||||||
|
|
|
||||||
1
changelog.d/251.feature
Normal file
1
changelog.d/251.feature
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Implement Room Details screen
|
||||||
|
|
@ -26,4 +26,5 @@ android {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(projects.libraries.architecture)
|
implementation(projects.libraries.architecture)
|
||||||
|
implementation(projects.libraries.matrix.api)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,19 @@
|
||||||
|
|
||||||
package io.element.android.features.messages.api
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,15 +18,21 @@ package io.element.android.features.messages.impl
|
||||||
|
|
||||||
import com.bumble.appyx.core.modality.BuildContext
|
import com.bumble.appyx.core.modality.BuildContext
|
||||||
import com.bumble.appyx.core.node.Node
|
import com.bumble.appyx.core.node.Node
|
||||||
|
import com.bumble.appyx.core.plugin.Plugin
|
||||||
import com.squareup.anvil.annotations.ContributesBinding
|
import com.squareup.anvil.annotations.ContributesBinding
|
||||||
import io.element.android.features.messages.api.MessagesEntryPoint
|
import io.element.android.features.messages.api.MessagesEntryPoint
|
||||||
import io.element.android.libraries.architecture.createNode
|
import io.element.android.libraries.architecture.createNode
|
||||||
import io.element.android.libraries.di.AppScope
|
import io.element.android.libraries.di.AppScope
|
||||||
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ContributesBinding(AppScope::class)
|
@ContributesBinding(AppScope::class)
|
||||||
class DefaultMessagesEntryPoint @Inject constructor() : MessagesEntryPoint {
|
class DefaultMessagesEntryPoint @Inject constructor() : MessagesEntryPoint {
|
||||||
override fun createNode(parentNode: Node, buildContext: BuildContext): Node {
|
override fun createNode(
|
||||||
return parentNode.createNode<MessagesNode>(buildContext)
|
parentNode: Node,
|
||||||
|
buildContext: BuildContext,
|
||||||
|
callback: MessagesEntryPoint.Callback
|
||||||
|
): Node {
|
||||||
|
return parentNode.createNode<MessagesNode>(buildContext, listOf(callback))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,10 +21,13 @@ import androidx.compose.ui.Modifier
|
||||||
import com.bumble.appyx.core.modality.BuildContext
|
import com.bumble.appyx.core.modality.BuildContext
|
||||||
import com.bumble.appyx.core.node.Node
|
import com.bumble.appyx.core.node.Node
|
||||||
import com.bumble.appyx.core.plugin.Plugin
|
import com.bumble.appyx.core.plugin.Plugin
|
||||||
|
import com.bumble.appyx.core.plugin.plugins
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import io.element.android.anvilannotations.ContributesNode
|
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.di.RoomScope
|
||||||
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
|
||||||
@ContributesNode(RoomScope::class)
|
@ContributesNode(RoomScope::class)
|
||||||
class MessagesNode @AssistedInject constructor(
|
class MessagesNode @AssistedInject constructor(
|
||||||
|
|
@ -33,12 +36,19 @@ class MessagesNode @AssistedInject constructor(
|
||||||
private val presenter: MessagesPresenter,
|
private val presenter: MessagesPresenter,
|
||||||
) : Node(buildContext, plugins = plugins) {
|
) : Node(buildContext, plugins = plugins) {
|
||||||
|
|
||||||
|
private val callback = plugins<MessagesEntryPoint.Callback>().firstOrNull()
|
||||||
|
|
||||||
|
private fun onRoomDetailsClicked() {
|
||||||
|
callback?.onRoomDetailsClicked()
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun View(modifier: Modifier) {
|
override fun View(modifier: Modifier) {
|
||||||
val state = presenter.present()
|
val state = presenter.present()
|
||||||
MessagesView(
|
MessagesView(
|
||||||
state = state,
|
state = state,
|
||||||
onBackPressed = this::navigateUp,
|
onBackPressed = this::navigateUp,
|
||||||
|
onRoomDetailsClicked = this::onRoomDetailsClicked,
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@
|
||||||
|
|
||||||
package io.element.android.features.messages.impl
|
package io.element.android.features.messages.impl
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
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.Text
|
||||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||||
import io.element.android.libraries.designsystem.utils.LogCompositions
|
import io.element.android.libraries.designsystem.utils.LogCompositions
|
||||||
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
|
|
@ -77,6 +79,7 @@ fun MessagesView(
|
||||||
state: MessagesState,
|
state: MessagesState,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onBackPressed: () -> Unit = {},
|
onBackPressed: () -> Unit = {},
|
||||||
|
onRoomDetailsClicked: () -> Unit = {},
|
||||||
) {
|
) {
|
||||||
LogCompositions(tag = "MessagesScreen", msg = "Root")
|
LogCompositions(tag = "MessagesScreen", msg = "Root")
|
||||||
val itemActionsBottomSheetState = rememberModalBottomSheetState(
|
val itemActionsBottomSheetState = rememberModalBottomSheetState(
|
||||||
|
|
@ -112,7 +115,8 @@ fun MessagesView(
|
||||||
MessagesViewTopBar(
|
MessagesViewTopBar(
|
||||||
roomTitle = state.roomName,
|
roomTitle = state.roomName,
|
||||||
roomAvatar = state.roomAvatar,
|
roomAvatar = state.roomAvatar,
|
||||||
onBackPressed = onBackPressed
|
onBackPressed = onBackPressed,
|
||||||
|
onRoomDetailsClicked = onRoomDetailsClicked,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
content = { padding ->
|
content = { padding ->
|
||||||
|
|
@ -174,6 +178,7 @@ fun MessagesViewTopBar(
|
||||||
roomTitle: String?,
|
roomTitle: String?,
|
||||||
roomAvatar: AvatarData?,
|
roomAvatar: AvatarData?,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
onRoomDetailsClicked: () -> Unit = {},
|
||||||
onBackPressed: () -> Unit = {},
|
onBackPressed: () -> Unit = {},
|
||||||
) {
|
) {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
|
|
@ -187,7 +192,10 @@ fun MessagesViewTopBar(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
title = {
|
title = {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(
|
||||||
|
modifier = Modifier.clickable { onRoomDetailsClicked() },
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
if (roomAvatar != null) {
|
if (roomAvatar != null) {
|
||||||
Avatar(roomAvatar)
|
Avatar(roomAvatar)
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="screen_bug_report_attach_screenshot">"Attach screenshot"</string>
|
<string name="screen_bug_report_attach_screenshot">"Attach screenshot"</string>
|
||||||
<string name="screen_bug_report_contact_me">"You may contact me if you have any follow up questions"</string>
|
<string name="screen_bug_report_contact_me">"You may contact me if you have any follow up questions"</string>
|
||||||
|
<string name="screen_bug_report_edit_screenshot">"Edit screenshot"</string>
|
||||||
<string name="screen_bug_report_editor_description">"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."</string>
|
<string name="screen_bug_report_editor_description">"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."</string>
|
||||||
<string name="screen_bug_report_editor_placeholder">"Describe the bug…"</string>
|
<string name="screen_bug_report_editor_placeholder">"Describe the bug…"</string>
|
||||||
<string name="screen_bug_report_editor_supporting">"If possible, please write the description in English."</string>
|
<string name="screen_bug_report_editor_supporting">"If possible, please write the description in English."</string>
|
||||||
|
|
|
||||||
1
features/roomdetails/api/.gitignore
vendored
Normal file
1
features/roomdetails/api/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/build
|
||||||
30
features/roomdetails/api/build.gradle.kts
Normal file
30
features/roomdetails/api/build.gradle.kts
Normal file
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
}
|
||||||
1
features/roomdetails/impl/.gitignore
vendored
Normal file
1
features/roomdetails/impl/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/build
|
||||||
57
features/roomdetails/impl/build.gradle.kts
Normal file
57
features/roomdetails/impl/build.gradle.kts
Normal file
|
|
@ -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)
|
||||||
|
}
|
||||||
0
features/roomdetails/impl/consumer-rules.pro
Normal file
0
features/roomdetails/impl/consumer-rules.pro
Normal file
|
|
@ -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<RoomDetailsFlowNode>(buildContext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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<Plugin>,
|
||||||
|
) : BackstackNode<RoomDetailsFlowNode.NavTarget>(
|
||||||
|
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<RoomDetailsNode>(buildContext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun View(modifier: Modifier) {
|
||||||
|
Children(
|
||||||
|
navModel = backstack,
|
||||||
|
modifier = modifier,
|
||||||
|
transitionHandler = rememberDefaultTransitionHandler(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<Plugin>,
|
||||||
|
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) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<RoomDetailsState> {
|
||||||
|
|
||||||
|
@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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
)
|
||||||
|
|
@ -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<RoomDetailsState> {
|
||||||
|
override val values: Sequence<RoomDetailsState>
|
||||||
|
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 = {}
|
||||||
|
)
|
||||||
|
|
@ -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 = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="960"
|
||||||
|
android:viewportHeight="960"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M440,520Q457,520 468.5,508.5Q480,497 480,480Q480,463 468.5,451.5Q457,440 440,440Q423,440 411.5,451.5Q400,463 400,480Q400,497 411.5,508.5Q423,520 440,520ZM280,840L280,760L520,720Q520,720 520,720Q520,720 520,720L520,275Q520,260 511,248Q502,236 488,234L280,200L280,120L500,156Q544,164 572,197Q600,230 600,274L600,718Q600,747 581,769.5Q562,792 533,797L280,840ZM280,760L680,760L680,200Q680,200 680,200Q680,200 680,200L280,200Q280,200 280,200Q280,200 280,200L280,760ZM160,840Q143,840 131.5,828.5Q120,817 120,800Q120,783 131.5,771.5Q143,760 160,760L200,760L200,200Q200,166 223.5,143Q247,120 280,120L680,120Q714,120 737,143Q760,166 760,200L760,760L800,760Q817,760 828.5,771.5Q840,783 840,800Q840,817 828.5,828.5Q817,840 800,840L160,840Z"/>
|
||||||
|
</vector>
|
||||||
11
features/roomdetails/impl/src/main/res/values/localazy.xml
Normal file
11
features/roomdetails/impl/src/main/res/values/localazy.xml
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_room_details_encryption_enabled_subtitle">"Messages are secured with locks. Only you and the recipients have the unique keys to unlock them."</string>
|
||||||
|
<string name="screen_room_details_encryption_enabled_title">"Message encryption enabled"</string>
|
||||||
|
<string name="screen_room_details_invite_people_title">"Invite people"</string>
|
||||||
|
<string name="screen_room_details_leave_room_title">"Leave room"</string>
|
||||||
|
<string name="screen_room_details_people_title">"People"</string>
|
||||||
|
<string name="screen_room_details_security_title">"Security"</string>
|
||||||
|
<string name="screen_room_details_share_room_title">"Share room"</string>
|
||||||
|
<string name="screen_room_details_topic_title">"Topic"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -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<RoomMember> = emptyList(),
|
||||||
|
isEncrypted: Boolean = true,
|
||||||
|
) = FakeMatrixRoom(
|
||||||
|
roomId = roomId,
|
||||||
|
name = name,
|
||||||
|
displayName = displayName,
|
||||||
|
topic = topic,
|
||||||
|
avatarUrl = avatarUrl,
|
||||||
|
members = members,
|
||||||
|
isEncrypted = isEncrypted,
|
||||||
|
)
|
||||||
|
|
@ -69,3 +69,6 @@ val ElementOrange = Color(0xFFD9B072)
|
||||||
val Vermilion = Color(0xFFFF5B55)
|
val Vermilion = Color(0xFFFF5B55)
|
||||||
|
|
||||||
val LinkColor = Color(0xFF0086E6)
|
val LinkColor = Color(0xFF0086E6)
|
||||||
|
|
||||||
|
val TextColorCriticalLight = Color(0xFFD51928)
|
||||||
|
val TextColorCriticalDark = Color(0xfffd3e3c)
|
||||||
|
|
|
||||||
|
|
@ -28,3 +28,12 @@ fun Boolean.toEnabledColor(): Color {
|
||||||
MaterialTheme.colorScheme.primary.copy(alpha = 0.40f)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,5 +18,7 @@ package io.element.android.libraries.designsystem.components.preferences
|
||||||
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
internal val preferenceMinHeight = 80.dp
|
internal val preferenceMinHeightOnlyTitle = 48.dp
|
||||||
internal val preferencePaddingEnd = 16.dp
|
internal val preferenceMinHeight = 64.dp
|
||||||
|
internal val preferencePaddingHorizontal = 16.dp
|
||||||
|
internal val preferencePaddingVertical = 16.dp
|
||||||
|
|
|
||||||
|
|
@ -35,29 +35,35 @@ import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PreferenceCategory(
|
fun PreferenceCategory(
|
||||||
title: String,
|
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
title: String? = null,
|
||||||
|
showDivider: Boolean = true,
|
||||||
content: @Composable ColumnScope.() -> Unit,
|
content: @Composable ColumnScope.() -> Unit,
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
) {
|
) {
|
||||||
Divider(
|
if (title != null) {
|
||||||
modifier = Modifier.padding(horizontal = 16.dp),
|
PreferenceCategoryTitle(title = title)
|
||||||
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,
|
|
||||||
)
|
|
||||||
content()
|
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
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
internal fun PreferenceCategoryLightPreview() = ElementPreviewLight { ContentToPreview() }
|
internal fun PreferenceCategoryLightPreview() = ElementPreviewLight { ContentToPreview() }
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
@ -29,7 +29,6 @@ import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Announcement
|
import androidx.compose.material.icons.filled.Announcement
|
||||||
import androidx.compose.material.icons.filled.ArrowBack
|
|
||||||
import androidx.compose.material.icons.filled.BugReport
|
import androidx.compose.material.icons.filled.BugReport
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.runtime.Composable
|
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.text.style.TextOverflow
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.sp
|
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.ElementPreviewDark
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
|
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.Scaffold
|
||||||
import io.element.android.libraries.designsystem.theme.components.Text
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||||
|
|
@ -92,12 +90,7 @@ fun PreferenceTopAppBar(
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
IconButton(onClick = onBackPressed) {
|
BackButton(onClick = onBackPressed)
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Filled.ArrowBack,
|
|
||||||
contentDescription = "Back"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
title = {
|
title = {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
|
@ -132,13 +125,16 @@ private fun ContentToPreview() {
|
||||||
) {
|
) {
|
||||||
PreferenceText(
|
PreferenceText(
|
||||||
title = "Title",
|
title = "Title",
|
||||||
|
subtitle = "Some other text",
|
||||||
icon = Icons.Default.BugReport,
|
icon = Icons.Default.BugReport,
|
||||||
)
|
)
|
||||||
|
PreferenceDivider()
|
||||||
PreferenceSwitch(
|
PreferenceSwitch(
|
||||||
title = "Switch",
|
title = "Switch",
|
||||||
icon = Icons.Default.Announcement,
|
icon = Icons.Default.Announcement,
|
||||||
isChecked = true
|
isChecked = true,
|
||||||
)
|
)
|
||||||
|
PreferenceDivider()
|
||||||
PreferenceSlide(
|
PreferenceSlide(
|
||||||
title = "Slide",
|
title = "Slide",
|
||||||
summary = "Summary",
|
summary = "Summary",
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,10 @@ import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.defaultMinSize
|
import androidx.compose.foundation.layout.defaultMinSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
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.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
|
@ -51,15 +52,15 @@ fun PreferenceSlide(
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.defaultMinSize(minHeight = preferenceMinHeight),
|
.defaultMinSize(minHeight = preferenceMinHeight)
|
||||||
contentAlignment = Alignment.CenterStart
|
.padding(top = preferencePaddingVertical),
|
||||||
) {
|
) {
|
||||||
Row(modifier = Modifier.fillMaxWidth()) {
|
Row(modifier = Modifier.fillMaxWidth()) {
|
||||||
PreferenceIcon(icon = icon)
|
PreferenceIcon(icon = icon)
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
.padding(end = preferencePaddingEnd),
|
.padding(end = preferencePaddingHorizontal),
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
|
@ -97,6 +98,7 @@ internal fun PreferenceSlideDarkPreview() = ElementPreviewDark { ContentToPrevie
|
||||||
@Composable
|
@Composable
|
||||||
private fun ContentToPreview() {
|
private fun ContentToPreview() {
|
||||||
PreferenceSlide(
|
PreferenceSlide(
|
||||||
|
icon = Icons.Default.Person,
|
||||||
title = "Slide",
|
title = "Slide",
|
||||||
summary = "Summary",
|
summary = "Summary",
|
||||||
value = 0.75F
|
value = 0.75F
|
||||||
|
|
|
||||||
|
|
@ -53,22 +53,20 @@ fun PreferenceSwitch(
|
||||||
.clickable { onCheckedChange(!isChecked) },
|
.clickable { onCheckedChange(!isChecked) },
|
||||||
contentAlignment = Alignment.CenterStart
|
contentAlignment = Alignment.CenterStart
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(modifier = Modifier.fillMaxWidth()) {
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
PreferenceIcon(
|
PreferenceIcon(
|
||||||
|
modifier = Modifier.padding(vertical = preferencePaddingVertical),
|
||||||
icon = icon,
|
icon = icon,
|
||||||
enabled = enabled
|
enabled = enabled
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f).padding(vertical = preferencePaddingVertical),
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
color = enabled.toEnabledColor(),
|
color = enabled.toEnabledColor(),
|
||||||
text = title
|
text = title
|
||||||
)
|
)
|
||||||
Checkbox(
|
Checkbox(
|
||||||
modifier = Modifier.padding(end = preferencePaddingEnd),
|
modifier = Modifier.padding(end = preferencePaddingHorizontal).align(Alignment.CenterVertically),
|
||||||
checked = isChecked,
|
checked = isChecked,
|
||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
onCheckedChange = onCheckedChange
|
onCheckedChange = onCheckedChange
|
||||||
|
|
|
||||||
|
|
@ -18,18 +18,24 @@ package io.element.android.libraries.designsystem.components.preferences
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.defaultMinSize
|
import androidx.compose.foundation.layout.defaultMinSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.BugReport
|
import androidx.compose.material.icons.filled.BugReport
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
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.components.preferences.components.PreferenceIcon
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
|
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
|
||||||
|
|
@ -37,31 +43,52 @@ import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PreferenceText(
|
fun PreferenceText(
|
||||||
title: String,
|
title: String?,
|
||||||
// TODO subtitle
|
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
subtitle: String? = null,
|
||||||
|
currentValue: String? = null,
|
||||||
icon: ImageVector? = null,
|
icon: ImageVector? = null,
|
||||||
|
tintColor: Color? = null,
|
||||||
onClick: () -> Unit = {},
|
onClick: () -> Unit = {},
|
||||||
) {
|
) {
|
||||||
|
val minHeight = if (subtitle == null) preferenceMinHeightOnlyTitle else preferenceMinHeight
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.defaultMinSize(minHeight = preferenceMinHeight)
|
.defaultMinSize(minHeight = minHeight)
|
||||||
|
.padding(end = preferencePaddingHorizontal)
|
||||||
.clickable { onClick() },
|
.clickable { onClick() },
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth().padding(vertical = preferencePaddingVertical)
|
||||||
) {
|
) {
|
||||||
PreferenceIcon(icon = icon)
|
PreferenceIcon(icon = icon, tintColor = tintColor)
|
||||||
Text(
|
Column(modifier = Modifier
|
||||||
modifier = Modifier
|
.weight(1f)
|
||||||
.weight(1f)
|
.align(Alignment.CenterVertically)
|
||||||
.padding(end = preferencePaddingEnd),
|
) {
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
if (title != null) {
|
||||||
text = title,
|
Text(
|
||||||
color = MaterialTheme.colorScheme.primary,
|
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() {
|
private fun ContentToPreview() {
|
||||||
PreferenceText(
|
PreferenceText(
|
||||||
title = "Title",
|
title = "Title",
|
||||||
|
subtitle = "Some content",
|
||||||
icon = Icons.Default.BugReport,
|
icon = Icons.Default.BugReport,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,10 +17,12 @@
|
||||||
package io.element.android.libraries.designsystem.components.preferences.components
|
package io.element.android.libraries.designsystem.components.preferences.components
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.heightIn
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
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.ElementPreviewDark
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
|
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.Icon
|
||||||
import io.element.android.libraries.designsystem.toEnabledColor
|
import io.element.android.libraries.designsystem.toSecondaryEnabledColor
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PreferenceIcon(
|
fun PreferenceIcon(
|
||||||
icon: ImageVector?,
|
icon: ImageVector?,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
enabled: Boolean = true
|
tintColor: Color? = null,
|
||||||
|
enabled: Boolean = true,
|
||||||
|
isVisible: Boolean = true,
|
||||||
) {
|
) {
|
||||||
if (icon != null) {
|
if (icon != null) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = icon,
|
imageVector = icon,
|
||||||
contentDescription = "",
|
contentDescription = "",
|
||||||
tint = enabled.toEnabledColor(),
|
tint = tintColor ?: enabled.toSecondaryEnabledColor(),
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.padding(start = 8.dp)
|
.padding(start = 8.dp)
|
||||||
.width(48.dp),
|
.width(48.dp)
|
||||||
|
.heightIn(max = 48.dp),
|
||||||
)
|
)
|
||||||
} else {
|
} else if (isVisible) {
|
||||||
Spacer(modifier = modifier.width(56.dp))
|
Spacer(modifier = modifier.width(56.dp))
|
||||||
|
} else {
|
||||||
|
Spacer(modifier = modifier.width(16.dp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.Gray_450
|
||||||
import io.element.android.libraries.designsystem.SystemGrey5Dark
|
import io.element.android.libraries.designsystem.SystemGrey5Dark
|
||||||
import io.element.android.libraries.designsystem.SystemGrey6Dark
|
import io.element.android.libraries.designsystem.SystemGrey6Dark
|
||||||
|
import io.element.android.libraries.designsystem.TextColorCriticalDark
|
||||||
import io.element.android.libraries.designsystem.theme.previews.ColorsSchemePreview
|
import io.element.android.libraries.designsystem.theme.previews.ColorsSchemePreview
|
||||||
|
|
||||||
fun elementColorsDark() = ElementColors(
|
fun elementColorsDark() = ElementColors(
|
||||||
|
|
@ -37,6 +38,7 @@ fun elementColorsDark() = ElementColors(
|
||||||
messageHighlightedBackground = Azure,
|
messageHighlightedBackground = Azure,
|
||||||
quaternary = Gray_400,
|
quaternary = Gray_400,
|
||||||
quinary = Gray_450,
|
quinary = Gray_450,
|
||||||
|
textActionCritical = TextColorCriticalDark,
|
||||||
isLight = false,
|
isLight = false,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -69,7 +71,7 @@ val materialColorSchemeDark = darkColorScheme(
|
||||||
// TODO errorContainer = ColorDarkTokens.ErrorContainer,
|
// TODO errorContainer = ColorDarkTokens.ErrorContainer,
|
||||||
// TODO onErrorContainer = ColorDarkTokens.OnErrorContainer,
|
// TODO onErrorContainer = ColorDarkTokens.OnErrorContainer,
|
||||||
// TODO outline = ColorDarkTokens.Outline,
|
// TODO outline = ColorDarkTokens.Outline,
|
||||||
// TODO outlineVariant = ColorDarkTokens.OutlineVariant,
|
outlineVariant = Gray_450,
|
||||||
// TODO scrim = ColorDarkTokens.Scrim,
|
// TODO scrim = ColorDarkTokens.Scrim,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.Gray_50
|
||||||
import io.element.android.libraries.designsystem.SystemGrey5Light
|
import io.element.android.libraries.designsystem.SystemGrey5Light
|
||||||
import io.element.android.libraries.designsystem.SystemGrey6Light
|
import io.element.android.libraries.designsystem.SystemGrey6Light
|
||||||
|
import io.element.android.libraries.designsystem.TextColorCriticalLight
|
||||||
import io.element.android.libraries.designsystem.theme.previews.ColorsSchemePreview
|
import io.element.android.libraries.designsystem.theme.previews.ColorsSchemePreview
|
||||||
|
|
||||||
fun elementColorsLight() = ElementColors(
|
fun elementColorsLight() = ElementColors(
|
||||||
|
|
@ -37,6 +38,7 @@ fun elementColorsLight() = ElementColors(
|
||||||
messageHighlightedBackground = Azure,
|
messageHighlightedBackground = Azure,
|
||||||
quaternary = Gray_100,
|
quaternary = Gray_100,
|
||||||
quinary = Gray_50,
|
quinary = Gray_50,
|
||||||
|
textActionCritical = TextColorCriticalLight,
|
||||||
isLight = true,
|
isLight = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -69,7 +71,7 @@ val materialColorSchemeLight = lightColorScheme(
|
||||||
// TODO errorContainer = ColorLightTokens.ErrorContainer,
|
// TODO errorContainer = ColorLightTokens.ErrorContainer,
|
||||||
// TODO onErrorContainer = ColorLightTokens.OnErrorContainer,
|
// TODO onErrorContainer = ColorLightTokens.OnErrorContainer,
|
||||||
// TODO outline = ColorLightTokens.Outline,
|
// TODO outline = ColorLightTokens.Outline,
|
||||||
// TODO outlineVariant = ColorLightTokens.OutlineVariant,
|
outlineVariant = Gray_50,
|
||||||
// TODO scrim = ColorLightTokens.Scrim,
|
// TODO scrim = ColorLightTokens.Scrim,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,8 @@ class ElementColors(
|
||||||
messageHighlightedBackground: Color,
|
messageHighlightedBackground: Color,
|
||||||
quaternary: Color,
|
quaternary: Color,
|
||||||
quinary: Color,
|
quinary: Color,
|
||||||
isLight: Boolean,
|
textActionCritical: Color,
|
||||||
|
isLight: Boolean
|
||||||
) {
|
) {
|
||||||
var messageFromMeBackground by mutableStateOf(messageFromMeBackground)
|
var messageFromMeBackground by mutableStateOf(messageFromMeBackground)
|
||||||
private set
|
private set
|
||||||
|
|
@ -44,6 +45,9 @@ class ElementColors(
|
||||||
var quinary by mutableStateOf(quinary)
|
var quinary by mutableStateOf(quinary)
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
var textActionCritical by mutableStateOf(textActionCritical)
|
||||||
|
private set
|
||||||
|
|
||||||
var isLight by mutableStateOf(isLight)
|
var isLight by mutableStateOf(isLight)
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
|
@ -53,6 +57,7 @@ class ElementColors(
|
||||||
messageHighlightedBackground: Color = this.messageHighlightedBackground,
|
messageHighlightedBackground: Color = this.messageHighlightedBackground,
|
||||||
quaternary: Color = this.quaternary,
|
quaternary: Color = this.quaternary,
|
||||||
quinary: Color = this.quinary,
|
quinary: Color = this.quinary,
|
||||||
|
textActionCritical: Color = this.textActionCritical,
|
||||||
isLight: Boolean = this.isLight,
|
isLight: Boolean = this.isLight,
|
||||||
) = ElementColors(
|
) = ElementColors(
|
||||||
messageFromMeBackground = messageFromMeBackground,
|
messageFromMeBackground = messageFromMeBackground,
|
||||||
|
|
@ -60,6 +65,7 @@ class ElementColors(
|
||||||
messageHighlightedBackground = messageHighlightedBackground,
|
messageHighlightedBackground = messageHighlightedBackground,
|
||||||
quaternary = quaternary,
|
quaternary = quaternary,
|
||||||
quinary = quinary,
|
quinary = quinary,
|
||||||
|
textActionCritical = textActionCritical,
|
||||||
isLight = isLight,
|
isLight = isLight,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -69,6 +75,7 @@ class ElementColors(
|
||||||
messageHighlightedBackground = other.messageHighlightedBackground
|
messageHighlightedBackground = other.messageHighlightedBackground
|
||||||
quaternary = other.quaternary
|
quaternary = other.quaternary
|
||||||
quinary = other.quinary
|
quinary = other.quinary
|
||||||
|
textActionCritical = other.textActionCritical
|
||||||
isLight = other.isLight
|
isLight = other.isLight
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ fun TopAppBar(
|
||||||
navigationIcon: @Composable () -> Unit = {},
|
navigationIcon: @Composable () -> Unit = {},
|
||||||
actions: @Composable RowScope.() -> Unit = {},
|
actions: @Composable RowScope.() -> Unit = {},
|
||||||
windowInsets: WindowInsets = TopAppBarDefaults.windowInsets,
|
windowInsets: WindowInsets = TopAppBarDefaults.windowInsets,
|
||||||
colors: TopAppBarColors = TopAppBarDefaults.smallTopAppBarColors(),
|
colors: TopAppBarColors = TopAppBarDefaults.topAppBarColors(),
|
||||||
scrollBehavior: TopAppBarScrollBehavior? = null
|
scrollBehavior: TopAppBarScrollBehavior? = null
|
||||||
) {
|
) {
|
||||||
androidx.compose.material3.TopAppBar(
|
androidx.compose.material3.TopAppBar(
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -17,14 +17,13 @@
|
||||||
package io.element.android.libraries.matrix.api.permalink
|
package io.element.android.libraries.matrix.api.permalink
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import io.element.android.libraries.matrix.api.config.MatrixConfiguration
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mapping of an input URI to a matrix.to compliant URI.
|
* Mapping of an input URI to a matrix.to compliant URI.
|
||||||
*/
|
*/
|
||||||
object MatrixToConverter {
|
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.
|
* 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].
|
* To be successfully converted, URL path should contain one of the [SUPPORTED_PATHS].
|
||||||
|
|
@ -35,14 +34,15 @@ object MatrixToConverter {
|
||||||
*/
|
*/
|
||||||
fun convert(uri: Uri): Uri? {
|
fun convert(uri: Uri): Uri? {
|
||||||
val uriString = uri.toString()
|
val uriString = uri.toString()
|
||||||
|
val baseUrl = MatrixConfiguration.matrixToPermalinkBaseUrl
|
||||||
|
|
||||||
return when {
|
return when {
|
||||||
// URL is already a matrix.to
|
// URL is already a matrix.to
|
||||||
uriString.startsWith(MATRIX_TO_URL_BASE) -> uri
|
uriString.startsWith(baseUrl) -> uri
|
||||||
// Web or client url
|
// Web or client url
|
||||||
SUPPORTED_PATHS.any { it in uriString } -> {
|
SUPPORTED_PATHS.any { it in uriString } -> {
|
||||||
val path = SUPPORTED_PATHS.first { 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
|
// URL is not supported
|
||||||
else -> null
|
else -> null
|
||||||
|
|
|
||||||
|
|
@ -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<String> {
|
||||||
|
return if (MatrixPatterns.isRoomAlias(roomAlias)) {
|
||||||
|
Result.success(permalinkForRoomAliasOrId(roomAlias))
|
||||||
|
} else {
|
||||||
|
Result.failure(PermalinkBuilderError.InvalidRoomAlias)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun permalinkForRoomId(roomId: RoomId): Result<String> {
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
@ -27,8 +27,12 @@ interface MatrixRoom: Closeable {
|
||||||
val name: String?
|
val name: String?
|
||||||
val bestName: String
|
val bestName: String
|
||||||
val displayName: String
|
val displayName: String
|
||||||
|
val alias: String?
|
||||||
|
val alternativeAliases: List<String>
|
||||||
val topic: String?
|
val topic: String?
|
||||||
val avatarUrl: String?
|
val avatarUrl: String?
|
||||||
|
val members: List<RoomMember>
|
||||||
|
val isEncrypted: Boolean
|
||||||
|
|
||||||
fun syncUpdateFlow(): Flow<Long>
|
fun syncUpdateFlow(): Flow<Long>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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.EventId
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
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.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.api.timeline.MatrixTimeline
|
||||||
import io.element.android.libraries.matrix.impl.timeline.RustMatrixTimeline
|
import io.element.android.libraries.matrix.impl.timeline.RustMatrixTimeline
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
|
@ -94,6 +95,18 @@ class RustMatrixRoom(
|
||||||
return innerRoom.avatarUrl()
|
return innerRoom.avatarUrl()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override val members: List<RoomMember>
|
||||||
|
get() = innerRoom.members().map(RoomMemberMapper::map)
|
||||||
|
|
||||||
|
override val isEncrypted: Boolean
|
||||||
|
get() = innerRoom.isEncrypted()
|
||||||
|
|
||||||
|
override val alias: String?
|
||||||
|
get() = innerRoom.canonicalAlias()
|
||||||
|
|
||||||
|
override val alternativeAliases: List<String>
|
||||||
|
get() = innerRoom.alternativeAliases()
|
||||||
|
|
||||||
override suspend fun fetchMembers(): Result<Unit> = withContext(coroutineDispatchers.io) {
|
override suspend fun fetchMembers(): Result<Unit> = withContext(coroutineDispatchers.io) {
|
||||||
runCatching {
|
runCatching {
|
||||||
innerRoom.fetchMembers()
|
innerRoom.fetchMembers()
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,9 @@ import kotlinx.coroutines.flow.sample
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.matrix.rustcomponents.sdk.PaginationOptions
|
import org.matrix.rustcomponents.sdk.PaginationOptions
|
||||||
|
import org.matrix.rustcomponents.sdk.RequiredState
|
||||||
import org.matrix.rustcomponents.sdk.Room
|
import org.matrix.rustcomponents.sdk.Room
|
||||||
|
import org.matrix.rustcomponents.sdk.RoomSubscription
|
||||||
import org.matrix.rustcomponents.sdk.SlidingSyncRoom
|
import org.matrix.rustcomponents.sdk.SlidingSyncRoom
|
||||||
import org.matrix.rustcomponents.sdk.TimelineItem
|
import org.matrix.rustcomponents.sdk.TimelineItem
|
||||||
import org.matrix.rustcomponents.sdk.TimelineListener
|
import org.matrix.rustcomponents.sdk.TimelineListener
|
||||||
|
|
@ -145,7 +147,8 @@ class RustMatrixTimeline(
|
||||||
|
|
||||||
private suspend fun addListener(timelineListener: TimelineListener): Result<List<TimelineItem>> = withContext(coroutineDispatchers.io) {
|
private suspend fun addListener(timelineListener: TimelineListener): Result<List<TimelineItem>> = withContext(coroutineDispatchers.io) {
|
||||||
runCatching {
|
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
|
listenerTokens += result.taskHandle
|
||||||
result.items
|
result.items
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.EventId
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
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.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.A_ROOM_ID
|
||||||
import io.element.android.libraries.matrix.test.timeline.FakeMatrixTimeline
|
import io.element.android.libraries.matrix.test.timeline.FakeMatrixTimeline
|
||||||
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
|
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
|
||||||
|
|
@ -33,6 +34,10 @@ class FakeMatrixRoom(
|
||||||
override val displayName: String = "",
|
override val displayName: String = "",
|
||||||
override val topic: String? = null,
|
override val topic: String? = null,
|
||||||
override val avatarUrl: String? = null,
|
override val avatarUrl: String? = null,
|
||||||
|
override val members: List<RoomMember> = emptyList(),
|
||||||
|
override val isEncrypted: Boolean = false,
|
||||||
|
override val alias: String? = null,
|
||||||
|
override val alternativeAliases: List<String> = emptyList(),
|
||||||
private val matrixTimeline: MatrixTimeline = FakeMatrixTimeline(),
|
private val matrixTimeline: MatrixTimeline = FakeMatrixTimeline(),
|
||||||
) : MatrixRoom {
|
) : MatrixRoom {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@
|
||||||
<string name="action_disable">"Disable"</string>
|
<string name="action_disable">"Disable"</string>
|
||||||
<string name="action_done">"Done"</string>
|
<string name="action_done">"Done"</string>
|
||||||
<string name="action_edit">"Edit"</string>
|
<string name="action_edit">"Edit"</string>
|
||||||
<string name="action_edit_screenshot">"Edit screenshot"</string>
|
|
||||||
<string name="action_enable">"Enable"</string>
|
<string name="action_enable">"Enable"</string>
|
||||||
<string name="action_invite">"Invite"</string>
|
<string name="action_invite">"Invite"</string>
|
||||||
<string name="action_invite_friends_to_app">"Invite friends to %1$s"</string>
|
<string name="action_invite_friends_to_app">"Invite friends to %1$s"</string>
|
||||||
|
|
@ -39,6 +38,7 @@
|
||||||
<string name="action_save">"Save"</string>
|
<string name="action_save">"Save"</string>
|
||||||
<string name="action_search">"Search"</string>
|
<string name="action_search">"Search"</string>
|
||||||
<string name="action_send">"Send"</string>
|
<string name="action_send">"Send"</string>
|
||||||
|
<string name="action_share_link">"Share link"</string>
|
||||||
<string name="action_skip">"Skip"</string>
|
<string name="action_skip">"Skip"</string>
|
||||||
<string name="action_start">"Start"</string>
|
<string name="action_start">"Start"</string>
|
||||||
<string name="action_start_chat">"Start chat"</string>
|
<string name="action_start_chat">"Start chat"</string>
|
||||||
|
|
@ -63,6 +63,7 @@
|
||||||
<string name="common_message_layout">"Message layout"</string>
|
<string name="common_message_layout">"Message layout"</string>
|
||||||
<string name="common_message_removed">"Message removed"</string>
|
<string name="common_message_removed">"Message removed"</string>
|
||||||
<string name="common_modern">"Modern"</string>
|
<string name="common_modern">"Modern"</string>
|
||||||
|
<string name="common_no_results">"No results"</string>
|
||||||
<string name="common_offline">"Offline"</string>
|
<string name="common_offline">"Offline"</string>
|
||||||
<string name="common_password">"Password"</string>
|
<string name="common_password">"Password"</string>
|
||||||
<string name="common_people">"People"</string>
|
<string name="common_people">"People"</string>
|
||||||
|
|
@ -103,6 +104,7 @@
|
||||||
<string name="emoji_picker_category_symbols">"Symbols"</string>
|
<string name="emoji_picker_category_symbols">"Symbols"</string>
|
||||||
<string name="error_failed_creating_the_permalink">"Failed creating the permalink"</string>
|
<string name="error_failed_creating_the_permalink">"Failed creating the permalink"</string>
|
||||||
<string name="error_failed_loading_messages">"Failed loading messages"</string>
|
<string name="error_failed_loading_messages">"Failed loading messages"</string>
|
||||||
|
<string name="error_no_compatible_app_found">"No compatible app was found to handle this action."</string>
|
||||||
<string name="error_some_messages_have_not_been_sent">"Some messages have not been sent"</string>
|
<string name="error_some_messages_have_not_been_sent">"Some messages have not been sent"</string>
|
||||||
<string name="error_unknown">"Sorry, an error occurred"</string>
|
<string name="error_unknown">"Sorry, an error occurred"</string>
|
||||||
<string name="invite_friends_text">"Hey, talk to me on %1$s: %2$s"</string>
|
<string name="invite_friends_text">"Hey, talk to me on %1$s: %2$s"</string>
|
||||||
|
|
@ -125,13 +127,13 @@
|
||||||
<string name="room_timeline_beginning_of_room">"This is the beginning of %1$s."</string>
|
<string name="room_timeline_beginning_of_room">"This is the beginning of %1$s."</string>
|
||||||
<string name="room_timeline_beginning_of_room_no_name">"This is the beginning of this conversation."</string>
|
<string name="room_timeline_beginning_of_room_no_name">"This is the beginning of this conversation."</string>
|
||||||
<string name="room_timeline_read_marker_title">"New"</string>
|
<string name="room_timeline_read_marker_title">"New"</string>
|
||||||
<string name="screen_room_details_encryption_enabled_subtitle">"Messages are secured with locks. Only you and the recipients have the unique keys to unlock them."</string>
|
<string name="screen_room_member_details_block_alert_action">"Block"</string>
|
||||||
<string name="screen_room_details_encryption_enabled_title">"Message encryption enabled"</string>
|
<string name="screen_room_member_details_block_alert_description">"Blocked users will not be able to send you messages and all message by them will be hidden. You can reverse this action anytime."</string>
|
||||||
<string name="screen_room_details_invite_people_title">"Invite people"</string>
|
<string name="screen_room_member_details_block_user">"Block user"</string>
|
||||||
<string name="screen_room_details_leave_room_title">"Leave room"</string>
|
<string name="screen_room_member_details_unblock_alert_action">"Unblock"</string>
|
||||||
<string name="screen_room_details_people_title">"People"</string>
|
<string name="screen_room_member_details_unblock_alert_description">"On unblocking the user, you will be able to see all messages by them again."</string>
|
||||||
<string name="screen_room_details_security_title">"Security"</string>
|
<string name="screen_room_member_details_unblock_user">"Unblock user"</string>
|
||||||
<string name="screen_room_details_topic_title">"Topic"</string>
|
<string name="screen_start_chat_error_starting_chat">"An error occurred when trying to start a chat"</string>
|
||||||
<string name="settings_rageshake">"Rageshake"</string>
|
<string name="settings_rageshake">"Rageshake"</string>
|
||||||
<string name="settings_rageshake_detection_threshold">"Detection threshold"</string>
|
<string name="settings_rageshake_detection_threshold">"Detection threshold"</string>
|
||||||
<string name="settings_title_general">"General"</string>
|
<string name="settings_title_general">"General"</string>
|
||||||
|
|
|
||||||
|
|
@ -124,7 +124,7 @@ data class MobileScreen(
|
||||||
* The screen shown when tapping the name of a room from the Room
|
* The screen shown when tapping the name of a room from the Room
|
||||||
* screen.
|
* screen.
|
||||||
*/
|
*/
|
||||||
RoomDetails,
|
RoomDetailss,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The screen that lists public rooms for you to discover.
|
* The screen that lists public rooms for you to discover.
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:47606ad889ab978c5ccb3bcac3ead43a85cc2a83ce15e5520ac2e271d6b5f473
|
oid sha256:4da4fd3b7c17b00bd17639a8ec5c6315abdf36b491c7da874f865bc386e622d8
|
||||||
size 44415
|
size 45198
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:92439979ab7bdb1a2c6ff1e98137a358ed788c3977c0ba7a1100eff41e22de79
|
oid sha256:3a191ed88e984ca94c890b644fcee602539bf72a2e402be64caeb189db12bb80
|
||||||
size 44282
|
size 44940
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:453dc174a87e3f43fe3bf7915634c3351b395ee05e488e37e37fd30c643c98fb
|
oid sha256:9083a3f1146c5aa30c39ced0f535eddd38cafb2bad6c509717c10b74356a844b
|
||||||
size 43160
|
size 43877
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:8f2da5aa58c2c32994c11ae03bb3aa3ff4f310ea306eec056cc521eccf305772
|
oid sha256:e4b03c928ec2cd4dbcc7fce37c0374b1e2b794d473d6ecc11bc5c33cc6953cca
|
||||||
size 42980
|
size 43699
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:6e1e4858da2e25786e5ef953d0456271ca7dcecc27edb1bf1bd2c46575d3c241
|
||||||
|
size 70626
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:7f5cce767021a2b93d7c98f05d566a9107c72805a446504b5da2e15a9298e586
|
||||||
|
size 55944
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:b4a97b7be4416c384ac4f8f9adde64f82dfc0fd0dcd137cd763508215785ea27
|
||||||
|
size 56122
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:12919612683b962d9a835b4442a29247c64c17c152ac617319eb5d14d80631b9
|
||||||
|
size 70453
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:20c39d6c718e3831a454e06a854addf65537a4fc15d2e09adb6b00e87491e014
|
||||||
|
size 65179
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:8bc9b7dde35c828d0158de1b8cc872416a1b4778a352b0c64cdf93c3bbc41c86
|
||||||
|
size 52891
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:68b646c429285f1827c7646fb170c0ac1a3e995872292bc2ddcf298fb565bcbe
|
||||||
|
size 52388
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:1c05c36d07d5b3b95d66233cd45053419a460e38ae940b86f125a42b57307985
|
||||||
|
size 64443
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:eedd92351d9ffb9ebd519b88e0875196a10d7b7bf7db82e6d044cb6ec200970e
|
oid sha256:d59b2a6b17f548953ce8521acae9e9f89b3c56245c4722c8c099af18453018d4
|
||||||
size 5195
|
size 5285
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:ceab55b3080020e622f1c8e44270907716fc777bd25d9d0573a9ea8dab643490
|
oid sha256:5ee3a6bcf3ce1f24c143de680dd8aa326166d5fbc95fd8fecc4c0b15dfb6d7ce
|
||||||
size 5043
|
size 5008
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:854525a9d9ca96fc53329faa4f816587da18663bb66c89a09870a01926370699
|
oid sha256:4a36dadd693d2bf6c9ca2bfea6ec4e783248f8d3e5ca88d5cfde468b4bda116a
|
||||||
size 16394
|
size 17055
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:b1ad6738aa4046b8c46e2b581489c6114ae669e94142d64198423e28797c3843
|
oid sha256:e5403566ef1d4d8cb25904fc74353197ad83e7ec452517a9cb696264a1609e32
|
||||||
size 15659
|
size 15999
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:2dba6b0eaa74d3c9dd3b62de76506198ce7c3aa6e5c7fca3d52bea3b56fb2ae3
|
oid sha256:ed6b297eeed4301c11caa9b59b8e7bdcebd63ca89219dee2efba11936c681abe
|
||||||
size 9103
|
size 9617
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:7b944575976d4971f76188d21740fd5b7c3a2937ab22b6e60688142e56296a8e
|
oid sha256:73c4ab7efd89ec8d9ca035346bbca9b073112d31065495f148b6510ff1ee23e6
|
||||||
size 8797
|
size 9203
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:44fd34661da1a384fbb0df08f3dc7226bd723a33138d1ac8ad3ef50930c84e0c
|
oid sha256:4665f288b04b8407f2ba25c272a56dd1d96b65523d76e08e6283156bf91f20ed
|
||||||
size 7587
|
size 7818
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:46bf34a9b246650a019c6983b17b76b11d4af6a0525e4ca87ca170d11c209144
|
oid sha256:8b45fd891f3e3b38301be79159ec1e6d04114de58b2170a0649656780f64f5ea
|
||||||
size 7303
|
size 7312
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:26f29ecbb43cc403577c79747d21ef821810fe741df781541f0b8426be85f4cd
|
oid sha256:3781254877c86737c2e042483f4a93e4608ce49994ec70a8c3e9e73124ea1388
|
||||||
size 6145
|
size 8764
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:dfbb4b967d7b5a2bf5cb9f9b8ccffa72d87a77442d31dcce44e5b9f68c0fb259
|
oid sha256:7505951ac4acf5cc0887af7bde9b8e659ae76643f5b6e639e24ee1df85448ece
|
||||||
size 5896
|
size 7970
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:df1b8d096d1b93065c7afb24873296c2ff19c91b8bbab662c42e7aa83b6ad9e0
|
oid sha256:f250b20941e26ca156589a49f8e10ba34f1d29e56af60ab39dca65e83bc617fb
|
||||||
size 19801
|
size 22411
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:576a65721a13bdbbb4bb5ff76d0727886e5c89931d1258c9d946aed6e8477256
|
oid sha256:1f13b118db744d879d693a6145d86152cc57182ff706597f7bfc83f2826d9ddc
|
||||||
size 19299
|
size 21566
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:274521bb420e73dcce6bca9d4328909d896998e4fdf3cebe79dd7c6c4873db45
|
oid sha256:047fbf34978c9b8d9b800b91e97bf95a94cedb31ddaf347d5e0000571ea491a9
|
||||||
size 4469
|
size 4469
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:9e3a82fb55f7a5be9ddbb5a8146d4fe53ad6111e21b40912428fd95dde85346c
|
oid sha256:234e0efe349185340b3efcaa181b31670a9dd55b57378e0b9c0b543f9ded70c2
|
||||||
size 4470
|
size 4468
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:cc8026b2bf6fbc4afaf2641d2deba1b3ae27d582dbc30c70841356232aa8cbbe
|
oid sha256:62b824f55b718ea805b97ce75810cd5c5de5498831aa8e5c8fa449121c48ea0c
|
||||||
size 117237
|
size 117342
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:8c0847975608f68e32e497a043c373cd471b18dcefb1cfd0a6abbfb2e3963265
|
oid sha256:da75446e5bc981fd21e95ff1b23ccccbf11bc4f9461982ed46b72c0182061abb
|
||||||
size 115619
|
size 115627
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,12 @@
|
||||||
"screen_roomlist_.*",
|
"screen_roomlist_.*",
|
||||||
"session_verification_banner_.*"
|
"session_verification_banner_.*"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": ":features:roomdetails:impl",
|
||||||
|
"includeRegex": [
|
||||||
|
"screen_room_details_.*"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue