[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
4a121fbd0f
commit
ecc73dd325
83 changed files with 1203 additions and 117 deletions
|
|
@ -69,3 +69,6 @@ val ElementOrange = Color(0xFFD9B072)
|
|||
val Vermilion = Color(0xFFFF5B55)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@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
|
||||
|
||||
internal val preferenceMinHeight = 80.dp
|
||||
internal val preferencePaddingEnd = 16.dp
|
||||
internal val preferenceMinHeightOnlyTitle = 48.dp
|
||||
internal val preferenceMinHeight = 64.dp
|
||||
internal val preferencePaddingHorizontal = 16.dp
|
||||
internal val preferencePaddingVertical = 16.dp
|
||||
|
|
|
|||
|
|
@ -35,29 +35,35 @@ import io.element.android.libraries.designsystem.theme.components.Text
|
|||
|
||||
@Composable
|
||||
fun PreferenceCategory(
|
||||
title: String,
|
||||
modifier: Modifier = Modifier,
|
||||
title: String? = null,
|
||||
showDivider: Boolean = true,
|
||||
content: @Composable ColumnScope.() -> Unit,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
Divider(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
thickness = 1.dp
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.padding(top = 8.dp, start = 56.dp),
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
text = title,
|
||||
)
|
||||
if (title != null) {
|
||||
PreferenceCategoryTitle(title = title)
|
||||
}
|
||||
content()
|
||||
if (showDivider) {
|
||||
PreferenceDivider()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PreferenceCategoryTitle(title: String, modifier: Modifier = Modifier) {
|
||||
Text(
|
||||
modifier = modifier.padding(top = 12.dp, start = 16.dp, end = 16.dp),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
text = title,
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
internal fun PreferenceCategoryLightPreview() = ElementPreviewLight { ContentToPreview() }
|
||||
|
|
|
|||
|
|
@ -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.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Announcement
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.BugReport
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -39,10 +38,9 @@ import androidx.compose.ui.text.font.FontWeight
|
|||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.sp
|
||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
|
||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
import io.element.android.libraries.designsystem.theme.components.IconButton
|
||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||
|
|
@ -92,12 +90,7 @@ fun PreferenceTopAppBar(
|
|||
TopAppBar(
|
||||
modifier = modifier,
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onBackPressed) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.ArrowBack,
|
||||
contentDescription = "Back"
|
||||
)
|
||||
}
|
||||
BackButton(onClick = onBackPressed)
|
||||
},
|
||||
title = {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
|
|
@ -132,13 +125,16 @@ private fun ContentToPreview() {
|
|||
) {
|
||||
PreferenceText(
|
||||
title = "Title",
|
||||
subtitle = "Some other text",
|
||||
icon = Icons.Default.BugReport,
|
||||
)
|
||||
PreferenceDivider()
|
||||
PreferenceSwitch(
|
||||
title = "Switch",
|
||||
icon = Icons.Default.Announcement,
|
||||
isChecked = true
|
||||
isChecked = true,
|
||||
)
|
||||
PreferenceDivider()
|
||||
PreferenceSlide(
|
||||
title = "Slide",
|
||||
summary = "Summary",
|
||||
|
|
|
|||
|
|
@ -23,9 +23,10 @@ import androidx.compose.foundation.layout.Row
|
|||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Person
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
|
|
@ -51,15 +52,15 @@ fun PreferenceSlide(
|
|||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.defaultMinSize(minHeight = preferenceMinHeight),
|
||||
contentAlignment = Alignment.CenterStart
|
||||
.defaultMinSize(minHeight = preferenceMinHeight)
|
||||
.padding(top = preferencePaddingVertical),
|
||||
) {
|
||||
Row(modifier = Modifier.fillMaxWidth()) {
|
||||
PreferenceIcon(icon = icon)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(end = preferencePaddingEnd),
|
||||
.padding(end = preferencePaddingHorizontal),
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
|
|
@ -97,6 +98,7 @@ internal fun PreferenceSlideDarkPreview() = ElementPreviewDark { ContentToPrevie
|
|||
@Composable
|
||||
private fun ContentToPreview() {
|
||||
PreferenceSlide(
|
||||
icon = Icons.Default.Person,
|
||||
title = "Slide",
|
||||
summary = "Summary",
|
||||
value = 0.75F
|
||||
|
|
|
|||
|
|
@ -53,22 +53,20 @@ fun PreferenceSwitch(
|
|||
.clickable { onCheckedChange(!isChecked) },
|
||||
contentAlignment = Alignment.CenterStart
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Row(modifier = Modifier.fillMaxWidth()) {
|
||||
PreferenceIcon(
|
||||
modifier = Modifier.padding(vertical = preferencePaddingVertical),
|
||||
icon = icon,
|
||||
enabled = enabled
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.weight(1f),
|
||||
modifier = Modifier.weight(1f).padding(vertical = preferencePaddingVertical),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = enabled.toEnabledColor(),
|
||||
text = title
|
||||
)
|
||||
Checkbox(
|
||||
modifier = Modifier.padding(end = preferencePaddingEnd),
|
||||
modifier = Modifier.padding(end = preferencePaddingHorizontal).align(Alignment.CenterVertically),
|
||||
checked = isChecked,
|
||||
enabled = enabled,
|
||||
onCheckedChange = onCheckedChange
|
||||
|
|
|
|||
|
|
@ -18,18 +18,24 @@ package io.element.android.libraries.designsystem.components.preferences
|
|||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.BugReport
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.libraries.designsystem.components.preferences.components.PreferenceIcon
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
|
||||
|
|
@ -37,31 +43,52 @@ import io.element.android.libraries.designsystem.theme.components.Text
|
|||
|
||||
@Composable
|
||||
fun PreferenceText(
|
||||
title: String,
|
||||
// TODO subtitle
|
||||
title: String?,
|
||||
modifier: Modifier = Modifier,
|
||||
subtitle: String? = null,
|
||||
currentValue: String? = null,
|
||||
icon: ImageVector? = null,
|
||||
tintColor: Color? = null,
|
||||
onClick: () -> Unit = {},
|
||||
) {
|
||||
val minHeight = if (subtitle == null) preferenceMinHeightOnlyTitle else preferenceMinHeight
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.defaultMinSize(minHeight = preferenceMinHeight)
|
||||
.defaultMinSize(minHeight = minHeight)
|
||||
.padding(end = preferencePaddingHorizontal)
|
||||
.clickable { onClick() },
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = preferencePaddingVertical)
|
||||
) {
|
||||
PreferenceIcon(icon = icon)
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(end = preferencePaddingEnd),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
text = title,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
PreferenceIcon(icon = icon, tintColor = tintColor)
|
||||
Column(modifier = Modifier
|
||||
.weight(1f)
|
||||
.align(Alignment.CenterVertically)
|
||||
) {
|
||||
if (title != null) {
|
||||
Text(
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
text = title,
|
||||
color = tintColor ?: MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
}
|
||||
if (title != null && subtitle != null) {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
if (subtitle != null) {
|
||||
Text(
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
text = subtitle,
|
||||
color = tintColor ?: MaterialTheme.colorScheme.tertiary,
|
||||
)
|
||||
}
|
||||
}
|
||||
if (currentValue != null) {
|
||||
Text(currentValue, style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.secondary)
|
||||
Spacer(Modifier.width(16.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -78,6 +105,7 @@ internal fun PreferenceTextDarkPreview() = ElementPreviewDark { ContentToPreview
|
|||
private fun ContentToPreview() {
|
||||
PreferenceText(
|
||||
title = "Title",
|
||||
subtitle = "Some content",
|
||||
icon = Icons.Default.BugReport,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,10 +17,12 @@
|
|||
package io.element.android.libraries.designsystem.components.preferences.components
|
||||
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
|
|
@ -28,25 +30,30 @@ import androidx.compose.ui.unit.dp
|
|||
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
|
||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
import io.element.android.libraries.designsystem.toEnabledColor
|
||||
import io.element.android.libraries.designsystem.toSecondaryEnabledColor
|
||||
|
||||
@Composable
|
||||
fun PreferenceIcon(
|
||||
icon: ImageVector?,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true
|
||||
tintColor: Color? = null,
|
||||
enabled: Boolean = true,
|
||||
isVisible: Boolean = true,
|
||||
) {
|
||||
if (icon != null) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = "",
|
||||
tint = enabled.toEnabledColor(),
|
||||
tint = tintColor ?: enabled.toSecondaryEnabledColor(),
|
||||
modifier = modifier
|
||||
.padding(start = 8.dp)
|
||||
.width(48.dp),
|
||||
.width(48.dp)
|
||||
.heightIn(max = 48.dp),
|
||||
)
|
||||
} else {
|
||||
} else if (isVisible) {
|
||||
Spacer(modifier = modifier.width(56.dp))
|
||||
} else {
|
||||
Spacer(modifier = modifier.width(16.dp))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import io.element.android.libraries.designsystem.Gray_400
|
|||
import io.element.android.libraries.designsystem.Gray_450
|
||||
import io.element.android.libraries.designsystem.SystemGrey5Dark
|
||||
import io.element.android.libraries.designsystem.SystemGrey6Dark
|
||||
import io.element.android.libraries.designsystem.TextColorCriticalDark
|
||||
import io.element.android.libraries.designsystem.theme.previews.ColorsSchemePreview
|
||||
|
||||
fun elementColorsDark() = ElementColors(
|
||||
|
|
@ -37,6 +38,7 @@ fun elementColorsDark() = ElementColors(
|
|||
messageHighlightedBackground = Azure,
|
||||
quaternary = Gray_400,
|
||||
quinary = Gray_450,
|
||||
textActionCritical = TextColorCriticalDark,
|
||||
isLight = false,
|
||||
)
|
||||
|
||||
|
|
@ -69,7 +71,7 @@ val materialColorSchemeDark = darkColorScheme(
|
|||
// TODO errorContainer = ColorDarkTokens.ErrorContainer,
|
||||
// TODO onErrorContainer = ColorDarkTokens.OnErrorContainer,
|
||||
// TODO outline = ColorDarkTokens.Outline,
|
||||
// TODO outlineVariant = ColorDarkTokens.OutlineVariant,
|
||||
outlineVariant = Gray_450,
|
||||
// TODO scrim = ColorDarkTokens.Scrim,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import io.element.android.libraries.designsystem.Gray_25
|
|||
import io.element.android.libraries.designsystem.Gray_50
|
||||
import io.element.android.libraries.designsystem.SystemGrey5Light
|
||||
import io.element.android.libraries.designsystem.SystemGrey6Light
|
||||
import io.element.android.libraries.designsystem.TextColorCriticalLight
|
||||
import io.element.android.libraries.designsystem.theme.previews.ColorsSchemePreview
|
||||
|
||||
fun elementColorsLight() = ElementColors(
|
||||
|
|
@ -37,6 +38,7 @@ fun elementColorsLight() = ElementColors(
|
|||
messageHighlightedBackground = Azure,
|
||||
quaternary = Gray_100,
|
||||
quinary = Gray_50,
|
||||
textActionCritical = TextColorCriticalLight,
|
||||
isLight = true,
|
||||
)
|
||||
|
||||
|
|
@ -69,7 +71,7 @@ val materialColorSchemeLight = lightColorScheme(
|
|||
// TODO errorContainer = ColorLightTokens.ErrorContainer,
|
||||
// TODO onErrorContainer = ColorLightTokens.OnErrorContainer,
|
||||
// TODO outline = ColorLightTokens.Outline,
|
||||
// TODO outlineVariant = ColorLightTokens.OutlineVariant,
|
||||
outlineVariant = Gray_50,
|
||||
// TODO scrim = ColorLightTokens.Scrim,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,8 @@ class ElementColors(
|
|||
messageHighlightedBackground: Color,
|
||||
quaternary: Color,
|
||||
quinary: Color,
|
||||
isLight: Boolean,
|
||||
textActionCritical: Color,
|
||||
isLight: Boolean
|
||||
) {
|
||||
var messageFromMeBackground by mutableStateOf(messageFromMeBackground)
|
||||
private set
|
||||
|
|
@ -44,6 +45,9 @@ class ElementColors(
|
|||
var quinary by mutableStateOf(quinary)
|
||||
private set
|
||||
|
||||
var textActionCritical by mutableStateOf(textActionCritical)
|
||||
private set
|
||||
|
||||
var isLight by mutableStateOf(isLight)
|
||||
private set
|
||||
|
||||
|
|
@ -53,6 +57,7 @@ class ElementColors(
|
|||
messageHighlightedBackground: Color = this.messageHighlightedBackground,
|
||||
quaternary: Color = this.quaternary,
|
||||
quinary: Color = this.quinary,
|
||||
textActionCritical: Color = this.textActionCritical,
|
||||
isLight: Boolean = this.isLight,
|
||||
) = ElementColors(
|
||||
messageFromMeBackground = messageFromMeBackground,
|
||||
|
|
@ -60,6 +65,7 @@ class ElementColors(
|
|||
messageHighlightedBackground = messageHighlightedBackground,
|
||||
quaternary = quaternary,
|
||||
quinary = quinary,
|
||||
textActionCritical = textActionCritical,
|
||||
isLight = isLight,
|
||||
)
|
||||
|
||||
|
|
@ -69,6 +75,7 @@ class ElementColors(
|
|||
messageHighlightedBackground = other.messageHighlightedBackground
|
||||
quaternary = other.quaternary
|
||||
quinary = other.quinary
|
||||
textActionCritical = other.textActionCritical
|
||||
isLight = other.isLight
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ fun TopAppBar(
|
|||
navigationIcon: @Composable () -> Unit = {},
|
||||
actions: @Composable RowScope.() -> Unit = {},
|
||||
windowInsets: WindowInsets = TopAppBarDefaults.windowInsets,
|
||||
colors: TopAppBarColors = TopAppBarDefaults.smallTopAppBarColors(),
|
||||
colors: TopAppBarColors = TopAppBarDefaults.topAppBarColors(),
|
||||
scrollBehavior: TopAppBarScrollBehavior? = null
|
||||
) {
|
||||
androidx.compose.material3.TopAppBar(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
import android.net.Uri
|
||||
import io.element.android.libraries.matrix.api.config.MatrixConfiguration
|
||||
|
||||
/**
|
||||
* Mapping of an input URI to a matrix.to compliant URI.
|
||||
*/
|
||||
object MatrixToConverter {
|
||||
|
||||
const val MATRIX_TO_URL_BASE = "https://matrix.to/#/"
|
||||
|
||||
/**
|
||||
* Try to convert a URL from an element web instance or from a client permalink to a matrix.to url.
|
||||
* To be successfully converted, URL path should contain one of the [SUPPORTED_PATHS].
|
||||
|
|
@ -35,14 +34,15 @@ object MatrixToConverter {
|
|||
*/
|
||||
fun convert(uri: Uri): Uri? {
|
||||
val uriString = uri.toString()
|
||||
val baseUrl = MatrixConfiguration.matrixToPermalinkBaseUrl
|
||||
|
||||
return when {
|
||||
// URL is already a matrix.to
|
||||
uriString.startsWith(MATRIX_TO_URL_BASE) -> uri
|
||||
uriString.startsWith(baseUrl) -> uri
|
||||
// Web or client url
|
||||
SUPPORTED_PATHS.any { it in uriString } -> {
|
||||
val path = SUPPORTED_PATHS.first { it in uriString }
|
||||
Uri.parse(MATRIX_TO_URL_BASE + uriString.substringAfter(path))
|
||||
Uri.parse(baseUrl + uriString.substringAfter(path))
|
||||
}
|
||||
// URL is not supported
|
||||
else -> null
|
||||
|
|
|
|||
|
|
@ -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 bestName: String
|
||||
val displayName: String
|
||||
val alias: String?
|
||||
val alternativeAliases: List<String>
|
||||
val topic: String?
|
||||
val avatarUrl: String?
|
||||
val members: List<RoomMember>
|
||||
val isEncrypted: Boolean
|
||||
|
||||
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.RoomId
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
|
||||
import io.element.android.libraries.matrix.impl.timeline.RustMatrixTimeline
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
|
@ -94,6 +95,18 @@ class RustMatrixRoom(
|
|||
return innerRoom.avatarUrl()
|
||||
}
|
||||
|
||||
override val members: List<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) {
|
||||
runCatching {
|
||||
innerRoom.fetchMembers()
|
||||
|
|
|
|||
|
|
@ -35,7 +35,9 @@ import kotlinx.coroutines.flow.sample
|
|||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.matrix.rustcomponents.sdk.PaginationOptions
|
||||
import org.matrix.rustcomponents.sdk.RequiredState
|
||||
import org.matrix.rustcomponents.sdk.Room
|
||||
import org.matrix.rustcomponents.sdk.RoomSubscription
|
||||
import org.matrix.rustcomponents.sdk.SlidingSyncRoom
|
||||
import org.matrix.rustcomponents.sdk.TimelineItem
|
||||
import org.matrix.rustcomponents.sdk.TimelineListener
|
||||
|
|
@ -145,7 +147,8 @@ class RustMatrixTimeline(
|
|||
|
||||
private suspend fun addListener(timelineListener: TimelineListener): Result<List<TimelineItem>> = withContext(coroutineDispatchers.io) {
|
||||
runCatching {
|
||||
val result = slidingSyncRoom.subscribeAndAddTimelineListener(timelineListener, null)
|
||||
val settings = RoomSubscription(requiredState = listOf(RequiredState(key = "m.room.canonical_alias", value = "")), timelineLimit = null)
|
||||
val result = slidingSyncRoom.subscribeAndAddTimelineListener(timelineListener, settings)
|
||||
listenerTokens += result.taskHandle
|
||||
result.items
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package io.element.android.libraries.matrix.test.room
|
|||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
import io.element.android.libraries.matrix.test.timeline.FakeMatrixTimeline
|
||||
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
|
||||
|
|
@ -33,6 +34,10 @@ class FakeMatrixRoom(
|
|||
override val displayName: String = "",
|
||||
override val topic: String? = null,
|
||||
override val avatarUrl: String? = null,
|
||||
override val members: List<RoomMember> = emptyList(),
|
||||
override val isEncrypted: Boolean = false,
|
||||
override val alias: String? = null,
|
||||
override val alternativeAliases: List<String> = emptyList(),
|
||||
private val matrixTimeline: MatrixTimeline = FakeMatrixTimeline(),
|
||||
) : MatrixRoom {
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@
|
|||
<string name="action_disable">"Disable"</string>
|
||||
<string name="action_done">"Done"</string>
|
||||
<string name="action_edit">"Edit"</string>
|
||||
<string name="action_edit_screenshot">"Edit screenshot"</string>
|
||||
<string name="action_enable">"Enable"</string>
|
||||
<string name="action_invite">"Invite"</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_search">"Search"</string>
|
||||
<string name="action_send">"Send"</string>
|
||||
<string name="action_share_link">"Share link"</string>
|
||||
<string name="action_skip">"Skip"</string>
|
||||
<string name="action_start">"Start"</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_removed">"Message removed"</string>
|
||||
<string name="common_modern">"Modern"</string>
|
||||
<string name="common_no_results">"No results"</string>
|
||||
<string name="common_offline">"Offline"</string>
|
||||
<string name="common_password">"Password"</string>
|
||||
<string name="common_people">"People"</string>
|
||||
|
|
@ -103,6 +104,7 @@
|
|||
<string name="emoji_picker_category_symbols">"Symbols"</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_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_unknown">"Sorry, an error occurred"</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_no_name">"This is the beginning of this conversation."</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_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_topic_title">"Topic"</string>
|
||||
<string name="screen_room_member_details_block_alert_action">"Block"</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_member_details_block_user">"Block user"</string>
|
||||
<string name="screen_room_member_details_unblock_alert_action">"Unblock"</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_member_details_unblock_user">"Unblock user"</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_detection_threshold">"Detection threshold"</string>
|
||||
<string name="settings_title_general">"General"</string>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue