Merge branch 'develop' into feature/fga/improve_node_architecture

This commit is contained in:
ganfra 2023-03-07 12:08:35 +01:00
commit 7f3679a15e
463 changed files with 3133 additions and 1555 deletions

View file

@ -14,6 +14,12 @@
* limitations under the License.
*/
package io.element.android.libraries.matrix
// TODO: Remove once https://youtrack.jetbrains.com/issue/KTIJ-19369 is fixed
@Suppress("DSL_SCOPE_VIOLATION")
plugins {
id("io.element.android-library")
}
internal const val LOG_TAG = "Matrix"
android {
namespace = "io.element.android.libraries.dateformatter.api"
}

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.libraries.dateformatter
package io.element.android.libraries.dateformatter.api
interface DaySeparatorFormatter {
fun format(timestamp: Long): String

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.libraries.dateformatter
package io.element.android.libraries.dateformatter.api
interface LastMessageFormatter {
fun format(timestamp: Long?): String

View file

@ -0,0 +1 @@
/build

View file

@ -27,17 +27,19 @@ anvil {
}
android {
namespace = "io.element.android.libraries.dateformatter"
namespace = "io.element.android.libraries.dateformatter.impl"
dependencies {
anvil(projects.anvilcodegen)
implementation(libs.dagger)
implementation(projects.libraries.di)
implementation(projects.anvilannotations)
implementation(projects.libraries.dateformatter.api)
api(libs.datetime)
ksp(libs.showkase.processor)
testImplementation(libs.test.junit)
testImplementation(libs.test.truth)
testImplementation(projects.libraries.dateformatter.test)
}
}

View file

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.kts.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View file

@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (c) 2023 New Vector Ltd
~
~ Licensed under the Apache License, Version 2.0 (the "License");
@ -14,5 +13,4 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<manifest/>
<manifest />

View file

@ -39,17 +39,17 @@ class DateFormatters @Inject constructor(
private val onlyTimeFormatter: DateTimeFormatter by lazy {
val pattern = DateFormat.getBestDateTimePattern(locale, "HH:mm") ?: "HH:mm"
DateTimeFormatter.ofPattern(pattern)
DateTimeFormatter.ofPattern(pattern, locale)
}
private val dateWithMonthFormatter: DateTimeFormatter by lazy {
val pattern = DateFormat.getBestDateTimePattern(locale, "d MMM") ?: "d MMM"
DateTimeFormatter.ofPattern(pattern)
DateTimeFormatter.ofPattern(pattern, locale)
}
private val dateWithYearFormatter: DateTimeFormatter by lazy {
val pattern = DateFormat.getBestDateTimePattern(locale, "dd.MM.yyyy") ?: "dd.MM.yyyy"
DateTimeFormatter.ofPattern(pattern)
DateTimeFormatter.ofPattern(pattern, locale)
}
internal fun formatTime(localDateTime: LocalDateTime): String {

View file

@ -17,7 +17,7 @@
package io.element.android.libraries.dateformatter.impl
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.dateformatter.DaySeparatorFormatter
import io.element.android.libraries.dateformatter.api.DaySeparatorFormatter
import io.element.android.libraries.di.AppScope
import javax.inject.Inject

View file

@ -17,7 +17,7 @@
package io.element.android.libraries.dateformatter.impl
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.dateformatter.LastMessageFormatter
import io.element.android.libraries.dateformatter.api.LastMessageFormatter
import io.element.android.libraries.di.AppScope
import javax.inject.Inject

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.libraries.dateformatter.di
package io.element.android.libraries.dateformatter.impl.di
import com.squareup.anvil.annotations.ContributesTo
import dagger.Module

View file

@ -17,7 +17,8 @@
package io.element.android.libraries.dateformatter.impl
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.dateformatter.LastMessageFormatter
import io.element.android.libraries.dateformatter.api.LastMessageFormatter
import io.element.android.libraries.dateformatter.test.FakeClock
import kotlinx.datetime.Instant
import kotlinx.datetime.TimeZone
import org.junit.Test

View file

@ -0,0 +1 @@
/build

View 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.libraries.dateformatter.test"
dependencies {
implementation(projects.libraries.dateformatter.api)
api(libs.datetime)
}
}

View file

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.kts.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ 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.
-->
<manifest />

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.libraries.dateformatter.impl
package io.element.android.libraries.dateformatter.test
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant

View 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.libraries.dateformatter.test
import io.element.android.libraries.dateformatter.api.DaySeparatorFormatter
class FakeDaySeparatorFormatter : DaySeparatorFormatter {
private var format = ""
fun givenFormat(format: String) {
this.format = format
}
override fun format(timestamp: Long): String {
return format
}
}

View file

@ -41,6 +41,22 @@ val SystemGrey5Dark = Color(0xFF2C2C2E)
val SystemGrey6Light = Color(0xFFF2F2F7)
val SystemGrey6Dark = Color(0xFF1C1C1E)
// For light themes
val Gray_25 = Color(0xFFF4F6FA)
val Gray_50 = Color(0xFFE3E8F0)
val Gray_100 = Color(0xFFC1C6CD)
val Gray_150 = Color(0xFF8D97A5)
val Gray_200 = Color(0xFF737D8C)
val Black_900 = Color(0xFF17191C)
// For dark themes
val Gray_250 = Color(0xFFA9B2BC)
val Gray_300 = Color(0xFF8E99A4)
val Gray_400 = Color(0xFF6F7882)
val Gray_450 = Color(0xFF394049)
val Black_800 = Color(0xFF15191E)
val Black_950 = Color(0xFF21262C)
val Azure = Color(0xFF368BD6)
val Kiwi = Color(0xFF74D12C)
val Grape = Color(0xFFAC3BA8)

View file

@ -25,6 +25,14 @@ import androidx.compose.ui.unit.sp
// TODO Remove
object ElementTextStyles {
val Button = TextStyle(
fontSize = 17.sp,
fontWeight = FontWeight.Bold,
lineHeight = 22.sp,
fontStyle = FontStyle.Normal,
textAlign = TextAlign.Center,
)
object Bold {
val largeTitle = TextStyle(
fontSize = 34.sp,
@ -180,6 +188,14 @@ object ElementTextStyles {
textAlign = TextAlign.Center
)
val formHeader = TextStyle(
fontSize = 14.sp,
fontWeight = FontWeight.Normal,
fontStyle = FontStyle.Normal,
lineHeight = 20.sp,
textAlign = TextAlign.Start
)
val footnote = TextStyle(
fontSize = 13.sp,
fontWeight = FontWeight.Normal,

View file

@ -17,13 +17,12 @@
package io.element.android.libraries.designsystem.components.dialogs
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.AlertDialogDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@ -31,10 +30,8 @@ import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
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.Button
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.ui.strings.R as StringR
@ -67,43 +64,24 @@ fun ConfirmationDialog(
Text(content)
},
dismissButton = {
Row(
modifier = Modifier.padding(all = 8.dp),
horizontalArrangement = Arrangement.Center
) {
Column {
Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
onCancelClicked()
}) {
Text(cancelText)
}
if (thirdButtonText != null) {
Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
onThirdButtonClicked()
}) {
Text(thirdButtonText)
}
if (thirdButtonText != null) {
// If there is a 3rd item it should be at the end of the dialog
// Having this 3rd action is discouraged, see https://m3.material.io/components/dialogs/guidelines#e13b68f5-e367-4275-ad6f-c552ee8e358f
TextButton(onClick = onThirdButtonClicked) {
Text(thirdButtonText)
}
}
TextButton(onClick = onCancelClicked) {
Text(cancelText)
}
},
confirmButton = {
Row(
modifier = Modifier.padding(all = 8.dp),
horizontalArrangement = Arrangement.Center
) {
Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
onSubmitClicked()
}
) {
Text(submitText)
TextButton(
onClick = {
onSubmitClicked()
}
) {
Text(submitText)
}
},
shape = shape,

View file

@ -23,6 +23,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.AlertDialogDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@ -55,24 +56,14 @@ fun ErrorDialog(
modifier = modifier,
onDismissRequest = onDismiss,
title = {
Text(text = title)
Text(title)
},
text = {
Text(content)
},
confirmButton = {
Row(
modifier = Modifier.padding(all = 8.dp),
horizontalArrangement = Arrangement.Center
) {
Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
onDismiss()
}
) {
Text(submitText)
}
TextButton(onClick = onDismiss) {
Text(submitText)
}
},
shape = shape,

View file

@ -16,12 +16,9 @@
package io.element.android.libraries.designsystem.preview
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import io.element.android.libraries.designsystem.theme.ElementTheme
import io.element.android.libraries.designsystem.theme.components.Surface
@Composable
fun ElementPreviewLight(
@ -55,9 +52,8 @@ private fun ElementPreview(
) {
ElementTheme(darkTheme = darkTheme) {
if (showBackground) {
Box(modifier = Modifier.background(MaterialTheme.colorScheme.background)) {
content()
}
// If we have a proper contentColor applied we need a Surface instead of a Box
Surface { content() }
} else {
content()
}

View file

@ -21,7 +21,11 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import io.element.android.libraries.designsystem.Azure
import io.element.android.libraries.designsystem.Black_800
import io.element.android.libraries.designsystem.Black_950
import io.element.android.libraries.designsystem.DarkGrey
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.theme.previews.ColorsSchemePreview
@ -30,6 +34,8 @@ fun elementColorsDark() = ElementColors(
messageFromMeBackground = SystemGrey5Dark,
messageFromOtherBackground = SystemGrey6Dark,
messageHighlightedBackground = Azure,
quaternary = Gray_400,
quinary = Gray_450,
isLight = false,
)
@ -48,12 +54,12 @@ val materialColorSchemeDark = darkColorScheme(
// TODO onTertiary = ColorDarkTokens.OnTertiary,
// TODO tertiaryContainer = ColorDarkTokens.TertiaryContainer,
// TODO onTertiaryContainer = ColorDarkTokens.OnTertiaryContainer,
background = Color.Black,
background = Black_800,
onBackground = Color.White,
surface = Color.Black,
surface = Black_800,
onSurface = Color.White,
surfaceVariant = SystemGrey5Dark,
// TODO onSurfaceVariant = ColorDarkTokens.OnSurfaceVariant,
surfaceVariant = Black_950,
onSurfaceVariant = Color.White,
// TODO surfaceTint = primary,
// TODO inverseSurface = ColorDarkTokens.InverseSurface,
// TODO inverseOnSurface = ColorDarkTokens.InverseOnSurface,

View file

@ -21,7 +21,12 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import io.element.android.libraries.designsystem.Azure
import io.element.android.libraries.designsystem.LightGrey
import io.element.android.libraries.designsystem.Black_900
import io.element.android.libraries.designsystem.Gray_100
import io.element.android.libraries.designsystem.Gray_150
import io.element.android.libraries.designsystem.Gray_200
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.theme.previews.ColorsSchemePreview
@ -30,21 +35,23 @@ fun elementColorsLight() = ElementColors(
messageFromMeBackground = SystemGrey5Light,
messageFromOtherBackground = SystemGrey6Light,
messageHighlightedBackground = Azure,
quaternary = Gray_100,
quinary = Gray_50,
isLight = true,
)
// TODO Lots of colors are missing
val materialColorSchemeLight = lightColorScheme(
primary = Color.Black,
primary = Black_900,
onPrimary = Color.White,
// TODO primaryContainer = ColorLightTokens.PrimaryContainer,
// TODO onPrimaryContainer = ColorLightTokens.OnPrimaryContainer,
// TODO inversePrimary = ColorLightTokens.InversePrimary,
secondary = LightGrey,
secondary = Gray_200,
// TODO onSecondary = ColorLightTokens.OnSecondary,
// TODO secondaryContainer = ColorLightTokens.SecondaryContainer,
// TODO onSecondaryContainer = ColorLightTokens.OnSecondaryContainer,
tertiary = Color.Black,
tertiary = Gray_150,
// TODO onTertiary = ColorLightTokens.OnTertiary,
// TODO tertiaryContainer = ColorLightTokens.TertiaryContainer,
// TODO onTertiaryContainer = ColorLightTokens.OnTertiaryContainer,
@ -52,8 +59,8 @@ val materialColorSchemeLight = lightColorScheme(
onBackground = Color.Black,
surface = Color.White,
onSurface = Color.Black,
surfaceVariant = SystemGrey5Light,
onSurfaceVariant = Color.Black,
surfaceVariant = Gray_25,
onSurfaceVariant = Gray_150,
// TODO surfaceTint = primary,
// TODO inverseSurface = ColorLightTokens.InverseSurface,
// TODO inverseOnSurface = ColorLightTokens.InverseOnSurface,

View file

@ -16,15 +16,19 @@
package io.element.android.libraries.designsystem.theme
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.graphics.Color
@Stable
class ElementColors(
messageFromMeBackground: Color,
messageFromOtherBackground: Color,
messageHighlightedBackground: Color,
quaternary: Color,
quinary: Color,
isLight: Boolean,
) {
var messageFromMeBackground by mutableStateOf(messageFromMeBackground)
@ -34,6 +38,12 @@ class ElementColors(
var messageHighlightedBackground by mutableStateOf(messageHighlightedBackground)
private set
var quaternary by mutableStateOf(quaternary)
private set
var quinary by mutableStateOf(quinary)
private set
var isLight by mutableStateOf(isLight)
private set
@ -41,11 +51,15 @@ class ElementColors(
messageFromMeBackground: Color = this.messageFromMeBackground,
messageFromOtherBackground: Color = this.messageFromOtherBackground,
messageHighlightedBackground: Color = this.messageHighlightedBackground,
quaternary: Color = this.quaternary,
quinary: Color = this.quinary,
isLight: Boolean = this.isLight,
) = ElementColors(
messageFromMeBackground = messageFromMeBackground,
messageFromOtherBackground = messageFromOtherBackground,
messageHighlightedBackground = messageHighlightedBackground,
quaternary = quaternary,
quinary = quinary,
isLight = isLight,
)
@ -53,6 +67,8 @@ class ElementColors(
messageFromMeBackground = other.messageFromMeBackground
messageFromOtherBackground = other.messageFromOtherBackground
messageHighlightedBackground = other.messageHighlightedBackground
quaternary = other.quaternary
quinary = other.quinary
isLight = other.isLight
}
}

View file

@ -49,15 +49,16 @@ val LocalColors = staticCompositionLocalOf { elementColorsLight() }
fun ElementTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
dynamicColor: Boolean = false, /* true to enable MaterialYou */
lightColors: ElementColors = elementColorsLight(),
darkColors: ElementColors = elementColorsDark(),
colors: ElementColors = if (darkTheme) elementColorsDark() else elementColorsLight(),
materialLightColors: ColorScheme = materialColorSchemeLight,
materialDarkColors: ColorScheme = materialColorSchemeDark,
content: @Composable () -> Unit,
) {
val systemUiController = rememberSystemUiController()
val useDarkIcons = !darkTheme
val currentColor = remember(darkTheme) { if (darkTheme) darkColors else lightColors }
val currentColor = remember(darkTheme) {
colors.copy()
}.apply { updateColorsFrom(colors) }
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
@ -75,9 +76,8 @@ fun ElementTheme(
darkIcons = useDarkIcons
)
}
val rememberedColors = remember { currentColor.copy() }.apply { updateColorsFrom(currentColor) }
CompositionLocalProvider(
LocalColors provides rememberedColors,
LocalColors provides currentColor,
) {
MaterialTheme(
colorScheme = colorScheme,

View file

@ -0,0 +1,62 @@
/*
* 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.theme.components
import androidx.compose.foundation.layout.Column
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.ui.strings.R.string as StringR
@Composable
fun BackButton(
action: () -> Unit,
modifier: Modifier = Modifier,
icon: ImageVector = Icons.Default.ArrowBack,
contentDescription: String = stringResource(StringR.action_back),
enabled: Boolean = true
) {
IconButton(
modifier = modifier,
onClick = action,
enabled = enabled,
) {
Icon(imageVector = icon, contentDescription = contentDescription)
}
}
@Preview
@Composable
internal fun BackButtonPreviewLight() = ElementPreviewLight { ContentToPreview() }
@Preview
@Composable
internal fun BackButtonPreviewDark() = ElementPreviewDark { ContentToPreview() }
@Composable
private fun ContentToPreview() {
Column {
BackButton(action = { }, enabled = true, contentDescription = "Back")
BackButton(action = { }, enabled = false, contentDescription = "Back")
}
}

View file

@ -29,6 +29,7 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
@ -37,11 +38,11 @@ fun Button(
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
shape: Shape = ButtonDefaults.shape,
colors: ButtonColors = ButtonDefaults.buttonColors(),
elevation: ButtonElevation? = ButtonDefaults.buttonElevation(),
shape: Shape = ElementButtonDefaults.shape,
colors: ButtonColors = ElementButtonDefaults.buttonColors(),
elevation: ButtonElevation? = ElementButtonDefaults.buttonElevation(),
border: BorderStroke? = null,
contentPadding: PaddingValues = ButtonDefaults.ContentPadding,
contentPadding: PaddingValues = ElementButtonDefaults.ContentPadding,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
content: @Composable RowScope.() -> Unit
) {
@ -59,6 +60,17 @@ fun Button(
)
}
object ElementButtonDefaults {
val ContentPadding = PaddingValues(horizontal = 24.dp, vertical = 14.dp)
val shape: Shape @Composable get() = ButtonDefaults.shape
@Composable
fun buttonElevation(): ButtonElevation = ButtonDefaults.buttonElevation()
@Composable
fun buttonColors(): ButtonColors = ButtonDefaults.buttonColors()
}
@Preview
@Composable
internal fun ButtonsLightPreview() = ElementPreviewLight { ContentToPreview() }

View file

@ -29,8 +29,17 @@ import androidx.compose.material3.TextFieldColors
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusDirection
import androidx.compose.ui.focus.FocusManager
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.KeyEventType
import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.onPreviewKeyEvent
import androidx.compose.ui.input.key.type
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.tooling.preview.Preview
@ -40,7 +49,7 @@ import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.utils.allBooleans
import io.element.android.libraries.designsystem.utils.asInt
@OptIn(ExperimentalMaterial3Api::class)
@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class)
@Composable
fun OutlinedTextField(
value: String,
@ -88,6 +97,16 @@ fun OutlinedTextField(
)
}
@OptIn(ExperimentalComposeUiApi::class)
fun Modifier.onTabOrEnterKeyFocusNext(focusManager: FocusManager): Modifier = onPreviewKeyEvent { event ->
if (event.type == KeyEventType.KeyUp && (event.key == Key.Tab || event.key == Key.Enter)) {
focusManager.moveFocus(FocusDirection.Down)
true
} else {
false
}
}
@Preview
@Composable
internal fun OutlinedTextFieldsLightPreview() = ElementPreviewLight { ContentToPreview() }

View file

@ -0,0 +1,37 @@
/*
* 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.
*/
plugins {
id("io.element.android-library")
}
android {
namespace = "io.element.android.libraries.encrypteddb"
buildTypes {
release {
isMinifyEnabled = true
consumerProguardFiles("consumer-proguard-rules.pro")
}
}
}
dependencies {
implementation(libs.sqldelight.driver.android)
implementation(libs.sqlcipher)
implementation(libs.sqlite)
implementation(libs.androidx.security.crypto)
}

View file

@ -0,0 +1,25 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
# Prevent ProGuard from renaming internal SQLCipher classes, which breaks the library.
# From https://github.com/sqlcipher/android-database-sqlcipher#proguard
-keep class net.sqlcipher.** { *; }

View file

@ -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.encrypteddb
import android.content.Context
import com.squareup.sqldelight.android.AndroidSqliteDriver
import com.squareup.sqldelight.db.SqlDriver
import io.element.encrypteddb.passphrase.PassphraseProvider
import net.sqlcipher.database.SupportFactory
/**
* Creates an encrypted version of the [SqlDriver] using SQLCipher's [SupportFactory].
* @param passphraseProvider Provides the passphrase needed to use the SQLite database with SQLCipher.
*/
class SqlCipherDriverFactory(
private val passphraseProvider: PassphraseProvider,
) {
/**
* Returns a valid [SqlDriver] with SQLCipher support.
* @param schema The SQLite DB schema.
* @param name The name of the database to create.
* @param context Android [Context], used to instantiate the driver.
*/
fun create(schema: SqlDriver.Schema, name: String, context: Context): SqlDriver {
val passphrase = passphraseProvider.getPassphrase()
val factory = SupportFactory(passphrase)
return AndroidSqliteDriver(schema = schema, context = context, name = name, factory = factory)
}
}

View file

@ -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.encrypteddb.passphrase
/**
* An abstraction to implement secure providers for SQLCipher passphrases.
*/
interface PassphraseProvider {
/**
* Returns a passphrase for SQLCipher in [ByteArray] format.
*/
fun getPassphrase(): ByteArray
}

View file

@ -0,0 +1,59 @@
/*
* 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.encrypteddb.passphrase
import android.content.Context
import androidx.security.crypto.EncryptedFile
import java.io.File
import java.security.SecureRandom
/**
* Provides a secure passphrase for SQLCipher by generating a random secret and storing it into an [EncryptedFile].
* @param context Android [Context], used by [EncryptedFile] for cryptographic operations.
* @param file Destination file where the key will be stored.
* @param alias Alias of the key used to encrypt & decrypt the [EncryptedFile]'s contents.
* @param secretSize Length of the generated secret.
*/
class RandomSecretPassphraseProvider(
private val context: Context,
private val file: File,
private val alias: String,
private val secretSize: Int = 256,
) : PassphraseProvider {
override fun getPassphrase(): ByteArray {
val encryptedFile = EncryptedFile.Builder(
file,
context,
alias,
EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build()
return if (!file.exists()) {
val secret = generateSecret()
encryptedFile.openFileOutput().use { it.write(secret) }
secret
} else {
encryptedFile.openFileInput().use { it.readBytes() }
}
}
private fun generateSecret(): ByteArray {
val buffer = ByteArray(size = secretSize)
SecureRandom().nextBytes(buffer)
return buffer
}
}

View file

@ -0,0 +1,44 @@
/*
* 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")
id("kotlin-parcelize")
alias(libs.plugins.anvil)
kotlin("plugin.serialization") version "1.8.10"
}
android {
namespace = "io.element.android.libraries.matrix.api"
}
anvil {
generateDaggerFactories.set(true)
}
dependencies {
// api(projects.libraries.rustsdk)
api(libs.matrix.sdk)
implementation(projects.libraries.di)
implementation(libs.dagger)
implementation(projects.libraries.core)
implementation("net.java.dev.jna:jna:5.13.0@aar")
implementation(libs.serialization.json)
api(projects.libraries.sessionStorage.api)
implementation(libs.coroutines.core)
}

View file

@ -14,14 +14,13 @@
* limitations under the License.
*/
package io.element.android.libraries.matrix
package io.element.android.libraries.matrix.api
import io.element.android.libraries.matrix.core.RoomId
import io.element.android.libraries.matrix.core.SessionId
import io.element.android.libraries.matrix.core.UserId
import io.element.android.libraries.matrix.media.MediaResolver
import io.element.android.libraries.matrix.room.MatrixRoom
import io.element.android.libraries.matrix.room.RoomSummaryDataSource
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.media.MediaResolver
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource
import org.matrix.rustcomponents.sdk.MediaSource
import java.io.Closeable
@ -33,7 +32,6 @@ interface MatrixClient : Closeable {
fun roomSummaryDataSource(): RoomSummaryDataSource
fun mediaResolver(): MediaResolver
suspend fun logout()
fun userId(): UserId
suspend fun loadUserDisplayName(): Result<String>
suspend fun loadUserAvatarURLString(): Result<String>
suspend fun loadMediaContentForSource(source: MediaSource): Result<ByteArray>

View file

@ -14,18 +14,18 @@
* limitations under the License.
*/
package io.element.android.libraries.matrix.auth
package io.element.android.libraries.matrix.api.auth
import io.element.android.libraries.matrix.MatrixClient
import io.element.android.libraries.matrix.core.SessionId
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.SessionId
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
interface MatrixAuthenticationService {
fun isLoggedIn(): Flow<Boolean>
suspend fun getLatestSessionId(): SessionId?
suspend fun restoreSession(sessionId: SessionId): MatrixClient?
fun getHomeserver(): String?
fun getHomeserverOrDefault(): String
fun getHomeserverDetails(): StateFlow<MatrixHomeServerDetails?>
suspend fun setHomeserver(homeserver: String)
suspend fun login(username: String, password: String): SessionId
}

View file

@ -0,0 +1,34 @@
/*
* 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.auth
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
import org.matrix.rustcomponents.sdk.HomeserverLoginDetails
@Parcelize
data class MatrixHomeServerDetails(
val url: String,
val supportsPasswordLogin: Boolean,
val authenticationIssuer: String?
): Parcelable {
constructor(homeserverLoginDetails: HomeserverLoginDetails) : this(
homeserverLoginDetails.url(),
homeserverLoginDetails.supportsPasswordLogin(),
homeserverLoginDetails.authenticationIssuer()
)
}

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.libraries.matrix.core
package io.element.android.libraries.matrix.api.core
import java.io.Serializable

View file

@ -14,9 +14,9 @@
* limitations under the License.
*/
package io.element.android.libraries.matrix.core
package io.element.android.libraries.matrix.api.core
import io.element.android.libraries.matrix.BuildConfig
import io.element.android.libraries.matrix.api.BuildConfig
import timber.log.Timber
/**

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.libraries.matrix.core
package io.element.android.libraries.matrix.api.core
import java.io.Serializable

View file

@ -14,9 +14,6 @@
* limitations under the License.
*/
package io.element.android.libraries.matrix.core
package io.element.android.libraries.matrix.api.core
import java.io.Serializable
@JvmInline
value class SessionId(val value: String) : Serializable
typealias SessionId = UserId

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.libraries.matrix.core
package io.element.android.libraries.matrix.api.core
import java.io.Serializable

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.libraries.matrix.media
package io.element.android.libraries.matrix.api.media
import org.matrix.rustcomponents.sdk.MediaSource

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.libraries.matrix.permalink
package io.element.android.libraries.matrix.api.permalink
import android.net.Uri

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.libraries.matrix.permalink
package io.element.android.libraries.matrix.api.permalink
import android.net.Uri

View file

@ -14,11 +14,11 @@
* limitations under the License.
*/
package io.element.android.libraries.matrix.permalink
package io.element.android.libraries.matrix.api.permalink
import android.net.Uri
import android.net.UrlQuerySanitizer
import io.element.android.libraries.matrix.core.MatrixPatterns
import io.element.android.libraries.matrix.api.core.MatrixPatterns
import timber.log.Timber
import java.net.URLDecoder

View file

@ -14,11 +14,11 @@
* limitations under the License.
*/
package io.element.android.libraries.matrix.room
package io.element.android.libraries.matrix.api.room
import io.element.android.libraries.matrix.core.EventId
import io.element.android.libraries.matrix.core.RoomId
import io.element.android.libraries.matrix.timeline.MatrixTimeline
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.timeline.MatrixTimeline
import kotlinx.coroutines.flow.Flow
interface MatrixRoom {
@ -46,5 +46,4 @@ interface MatrixRoom {
suspend fun replyMessage(eventId: EventId, message: String): Result<Unit>
suspend fun redactEvent(eventId: EventId, reason: String? = null): Result<Unit>
}

View file

@ -14,9 +14,9 @@
* limitations under the License.
*/
package io.element.android.libraries.matrix.room
package io.element.android.libraries.matrix.api.room
import io.element.android.libraries.matrix.core.RoomId
import io.element.android.libraries.matrix.api.core.RoomId
sealed interface RoomSummary {
data class Empty(val identifier: String) : RoomSummary

View file

@ -0,0 +1,24 @@
/*
* 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
import kotlinx.coroutines.flow.StateFlow
interface RoomSummaryDataSource {
fun roomSummaries(): StateFlow<List<RoomSummary>>
fun setSlidingSyncRange(range: IntRange)
}

View file

@ -14,10 +14,10 @@
* limitations under the License.
*/
package io.element.android.libraries.matrix.room.message
package io.element.android.libraries.matrix.api.room.message
import io.element.android.libraries.matrix.core.EventId
import io.element.android.libraries.matrix.core.UserId
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.UserId
data class RoomMessage(
val eventId: EventId,

View file

@ -14,9 +14,9 @@
* limitations under the License.
*/
package io.element.android.libraries.matrix.timeline
package io.element.android.libraries.matrix.api.timeline
import io.element.android.libraries.matrix.core.EventId
import io.element.android.libraries.matrix.api.core.EventId
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow

View file

@ -14,11 +14,10 @@
* limitations under the License.
*/
package io.element.android.libraries.matrix.timeline
package io.element.android.libraries.matrix.api.timeline
import io.element.android.libraries.matrix.core.EventId
import io.element.android.libraries.matrix.api.core.EventId
import org.matrix.rustcomponents.sdk.EventTimelineItem
import org.matrix.rustcomponents.sdk.TimelineItem
import org.matrix.rustcomponents.sdk.VirtualTimelineItem
sealed interface MatrixTimelineItem {
@ -31,14 +30,3 @@ sealed interface MatrixTimelineItem {
object Other : MatrixTimelineItem
}
fun TimelineItem.asMatrixTimelineItem(): MatrixTimelineItem {
val asEvent = asEvent()
if (asEvent != null) {
return MatrixTimelineItem.Event(asEvent)
}
val asVirtual = asVirtual()
if (asVirtual != null) {
return MatrixTimelineItem.Virtual(asVirtual)
}
return MatrixTimelineItem.Other
}

View file

@ -14,9 +14,7 @@
* limitations under the License.
*/
package io.element.android.libraries.matrix.tracing
import timber.log.Timber
package io.element.android.libraries.matrix.api.tracing
data class TracingConfiguration(
val overrides: Map<Target, LogLevel> = emptyMap()
@ -74,12 +72,6 @@ sealed class LogLevel(val filter: String) {
object Error : LogLevel("error")
}
fun setupTracing(tracingConfiguration: TracingConfiguration) {
val filter = tracingConfiguration.filter
Timber.v("Tracing config filter = $filter")
org.matrix.rustcomponents.sdk.setupTracing(filter)
}
object TracingConfigurations {
val release = TracingConfiguration(overrides = mapOf(Target.Common to LogLevel.Info))
val debug = TracingConfiguration(overrides = mapOf(Target.Common to LogLevel.Info))

View file

@ -23,7 +23,7 @@ plugins {
}
android {
namespace = "io.element.android.libraries.matrix"
namespace = "io.element.android.libraries.matrix.impl"
}
anvil {
@ -34,6 +34,7 @@ dependencies {
// api(projects.libraries.rustsdk)
api(libs.matrix.sdk)
implementation(projects.libraries.di)
implementation(projects.libraries.matrix.api)
implementation(libs.dagger)
implementation(projects.libraries.core)
implementation("net.java.dev.jna:jna:5.13.0@aar")

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
</manifest>

View file

@ -14,21 +14,20 @@
* limitations under the License.
*/
package io.element.android.libraries.matrix
package io.element.android.libraries.matrix.impl
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.matrix.core.RoomId
import io.element.android.libraries.matrix.core.SessionId
import io.element.android.libraries.matrix.core.UserId
import io.element.android.libraries.matrix.media.MediaResolver
import io.element.android.libraries.matrix.media.RustMediaResolver
import io.element.android.libraries.matrix.room.MatrixRoom
import io.element.android.libraries.matrix.room.RoomSummaryDataSource
import io.element.android.libraries.matrix.room.RustMatrixRoom
import io.element.android.libraries.matrix.room.RustRoomSummaryDataSource
import io.element.android.libraries.matrix.session.SessionStore
import io.element.android.libraries.matrix.session.sessionId
import io.element.android.libraries.matrix.sync.SlidingSyncObserverProxy
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.impl.media.RustMediaResolver
import io.element.android.libraries.matrix.impl.room.RustMatrixRoom
import io.element.android.libraries.matrix.impl.room.RustRoomSummaryDataSource
import io.element.android.libraries.matrix.impl.sync.SlidingSyncObserverProxy
import io.element.android.libraries.sessionstorage.api.SessionStore
import io.element.android.libraries.matrix.api.media.MediaResolver
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.withContext
import org.matrix.rustcomponents.sdk.Client
@ -51,7 +50,7 @@ class RustMatrixClient constructor(
private val baseDirectory: File,
) : MatrixClient {
override val sessionId: SessionId = client.session().sessionId()
override val sessionId: UserId = UserId(client.userId())
private val clientDelegate = object : ClientDelegate {
override fun didReceiveAuthError(isSoftLogout: Boolean) {
@ -174,11 +173,9 @@ class RustMatrixClient constructor(
Timber.e(failure, "Fail to call logout on HS. Still delete local files.")
}
baseDirectory.deleteSessionDirectory(userID = client.userId())
sessionStore.reset()
sessionStore.removeSession(client.userId())
}
override fun userId(): UserId = UserId(client.userId())
override suspend fun loadUserDisplayName(): Result<String> = withContext(dispatchers.io) {
runCatching {
client.displayName()

View file

@ -14,28 +14,36 @@
* limitations under the License.
*/
package io.element.android.libraries.matrix.auth
package io.element.android.libraries.matrix.impl.auth
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.matrix.MatrixClient
import io.element.android.libraries.matrix.RustMatrixClient
import io.element.android.libraries.matrix.core.SessionId
import io.element.android.libraries.matrix.session.SessionStore
import io.element.android.libraries.matrix.session.sessionId
import io.element.android.libraries.matrix.util.logError
import io.element.android.libraries.di.SingleIn
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.impl.RustMatrixClient
import io.element.android.libraries.matrix.impl.util.logError
import io.element.android.libraries.sessionstorage.api.SessionData
import io.element.android.libraries.sessionstorage.api.SessionStore
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.withContext
import org.matrix.rustcomponents.sdk.AuthenticationService
import org.matrix.rustcomponents.sdk.Client
import org.matrix.rustcomponents.sdk.ClientBuilder
import org.matrix.rustcomponents.sdk.Session
import timber.log.Timber
import java.io.File
import javax.inject.Inject
@ContributesBinding(AppScope::class)
@SingleIn(AppScope::class)
class RustMatrixAuthenticationService @Inject constructor(
private val baseDirectory: File,
private val coroutineScope: CoroutineScope,
@ -44,23 +52,25 @@ class RustMatrixAuthenticationService @Inject constructor(
private val authService: AuthenticationService,
) : MatrixAuthenticationService {
private var currentHomeserver = MutableStateFlow<MatrixHomeServerDetails?>(null)
override fun isLoggedIn(): Flow<Boolean> {
return sessionStore.isLoggedIn()
}
override suspend fun getLatestSessionId(): SessionId? = withContext(coroutineDispatchers.io) {
sessionStore.getLatestSession()?.sessionId()
sessionStore.getLatestSession()?.userId?.let { UserId(it) }
}
override suspend fun restoreSession(sessionId: SessionId) = withContext(coroutineDispatchers.io) {
sessionStore.getSession(sessionId)
?.let { session ->
sessionStore.getSession(sessionId.value)
?.let { sessionData ->
try {
ClientBuilder()
.basePath(baseDirectory.absolutePath)
.username(session.userId)
.username(sessionData.userId)
.build().apply {
restoreSession(session)
restoreSession(sessionData.toSession())
}
} catch (throwable: Throwable) {
logError(throwable)
@ -71,13 +81,15 @@ class RustMatrixAuthenticationService @Inject constructor(
}
}
override fun getHomeserver(): String? = authService.homeserverDetails()?.url()
override fun getHomeserverOrDefault(): String = getHomeserver() ?: "matrix.org"
override fun getHomeserverDetails(): StateFlow<MatrixHomeServerDetails?> = currentHomeserver
override suspend fun setHomeserver(homeserver: String) {
withContext(coroutineDispatchers.io) {
authService.configureHomeserver(homeserver)
val homeServerDetails = authService.homeserverDetails()?.use { MatrixHomeServerDetails(it) }
if (homeServerDetails != null) {
currentHomeserver.value = homeServerDetails.copy(url = homeserver)
}
}
}
@ -90,8 +102,8 @@ class RustMatrixAuthenticationService @Inject constructor(
throw failure
}
val session = client.session()
sessionStore.storeData(session)
session.sessionId()
sessionStore.storeData(session.toSessionData())
SessionId(session.userId)
}
private fun createMatrixClient(client: Client): MatrixClient {
@ -104,3 +116,23 @@ class RustMatrixAuthenticationService @Inject constructor(
)
}
}
private fun SessionData.toSession() = Session(
accessToken = accessToken,
refreshToken = refreshToken,
userId = userId,
deviceId = deviceId,
homeserverUrl = homeserverUrl,
isSoftLogout = isSoftLogout,
slidingSyncProxy = slidingSyncProxy,
)
private fun Session.toSessionData() = SessionData(
userId = userId,
deviceId = deviceId,
accessToken = accessToken,
refreshToken = refreshToken,
homeserverUrl = homeserverUrl,
isSoftLogout = isSoftLogout,
slidingSyncProxy = slidingSyncProxy,
)

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.libraries.matrix.di
package io.element.android.libraries.matrix.impl.di
import com.squareup.anvil.annotations.ContributesTo
import dagger.Module

View file

@ -14,9 +14,10 @@
* limitations under the License.
*/
package io.element.android.libraries.matrix.media
package io.element.android.libraries.matrix.impl.media
import io.element.android.libraries.matrix.MatrixClient
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.media.MediaResolver
import org.matrix.rustcomponents.sdk.mediaSourceFromUrl
internal class RustMediaResolver(private val client: MatrixClient) : MediaResolver {
@ -28,13 +29,14 @@ internal class RustMediaResolver(private val client: MatrixClient) : MediaResolv
}
override suspend fun resolve(meta: MediaResolver.Meta): ByteArray? {
if (meta.source == null) return null
return when (meta.kind) {
is MediaResolver.Kind.Content -> client.loadMediaContentForSource(meta.source)
val source = meta.source ?: return null
val kind = meta.kind
return when (kind) {
is MediaResolver.Kind.Content -> client.loadMediaContentForSource(source)
is MediaResolver.Kind.Thumbnail -> client.loadMediaThumbnailForSource(
meta.source,
meta.kind.width.toLong(),
meta.kind.height.toLong()
source,
kind.width.toLong(),
kind.height.toLong()
)
}.getOrNull()
}

View file

@ -14,10 +14,11 @@
* limitations under the License.
*/
package io.element.android.libraries.matrix.room
package io.element.android.libraries.matrix.impl.room
import io.element.android.libraries.matrix.core.RoomId
import io.element.android.libraries.matrix.room.message.RoomMessageFactory
import io.element.android.libraries.matrix.impl.room.message.RoomMessageFactory
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.room.RoomSummaryDetails
import org.matrix.rustcomponents.sdk.Room
import org.matrix.rustcomponents.sdk.SlidingSyncRoom

View file

@ -14,13 +14,14 @@
* limitations under the License.
*/
package io.element.android.libraries.matrix.room
package io.element.android.libraries.matrix.impl.room
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.matrix.core.EventId
import io.element.android.libraries.matrix.core.RoomId
import io.element.android.libraries.matrix.timeline.MatrixTimeline
import io.element.android.libraries.matrix.timeline.RustMatrixTimeline
import io.element.android.libraries.matrix.impl.timeline.RustMatrixTimeline
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.timeline.MatrixTimeline
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter
@ -32,7 +33,6 @@ import org.matrix.rustcomponents.sdk.SlidingSyncRoom
import org.matrix.rustcomponents.sdk.UpdateSummary
import org.matrix.rustcomponents.sdk.genTransactionId
import org.matrix.rustcomponents.sdk.messageEventContentFromMarkdown
import timber.log.Timber
class RustMatrixRoom(
private val slidingSyncUpdateFlow: Flow<UpdateSummary>,

View file

@ -14,11 +14,13 @@
* limitations under the License.
*/
package io.element.android.libraries.matrix.room
package io.element.android.libraries.matrix.impl.room
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.matrix.sync.roomListDiff
import io.element.android.libraries.matrix.sync.state
import io.element.android.libraries.matrix.impl.sync.roomListDiff
import io.element.android.libraries.matrix.impl.sync.state
import io.element.android.libraries.matrix.api.room.RoomSummary
import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
@ -39,11 +41,6 @@ import timber.log.Timber
import java.io.Closeable
import java.util.UUID
interface RoomSummaryDataSource {
fun roomSummaries(): StateFlow<List<RoomSummary>>
fun setSlidingSyncRange(range: IntRange)
}
internal class RustRoomSummaryDataSource(
private val slidingSyncUpdateFlow: Flow<UpdateSummary>,
private val slidingSync: SlidingSync,

View file

@ -14,10 +14,11 @@
* limitations under the License.
*/
package io.element.android.libraries.matrix.room.message
package io.element.android.libraries.matrix.impl.room.message
import io.element.android.libraries.matrix.core.EventId
import io.element.android.libraries.matrix.core.UserId
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.message.RoomMessage
import org.matrix.rustcomponents.sdk.EventTimelineItem
class RoomMessageFactory {

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.libraries.matrix.sync
package io.element.android.libraries.matrix.impl.sync
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableSharedFlow

View file

@ -14,9 +14,9 @@
* limitations under the License.
*/
package io.element.android.libraries.matrix.sync
package io.element.android.libraries.matrix.impl.sync
import io.element.android.libraries.matrix.util.mxCallbackFlow
import io.element.android.libraries.matrix.impl.util.mxCallbackFlow
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch

View file

@ -14,8 +14,10 @@
* limitations under the License.
*/
package io.element.android.libraries.matrix.timeline
package io.element.android.libraries.matrix.impl.timeline
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow

View file

@ -0,0 +1,32 @@
/*
* 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.
*/
package io.element.android.libraries.matrix.impl.timeline
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
import org.matrix.rustcomponents.sdk.TimelineItem
fun TimelineItem.asMatrixTimelineItem(): MatrixTimelineItem {
val asEvent = asEvent()
if (asEvent != null) {
return MatrixTimelineItem.Event(asEvent)
}
val asVirtual = asVirtual()
if (asVirtual != null) {
return MatrixTimelineItem.Virtual(asVirtual)
}
return MatrixTimelineItem.Other
}

View file

@ -14,12 +14,14 @@
* limitations under the License.
*/
package io.element.android.libraries.matrix.timeline
package io.element.android.libraries.matrix.impl.timeline
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.matrix.core.EventId
import io.element.android.libraries.matrix.room.MatrixRoom
import io.element.android.libraries.matrix.util.TaskHandleBag
import io.element.android.libraries.matrix.impl.util.TaskHandleBag
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.Flow

View file

@ -0,0 +1,26 @@
/*
* 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.
*/
package io.element.android.libraries.matrix.impl.tracing
import io.element.android.libraries.matrix.api.tracing.TracingConfiguration
import timber.log.Timber
fun setupTracing(tracingConfiguration: TracingConfiguration) {
val filter = tracingConfiguration.filter
Timber.v("Tracing config filter = $filter")
org.matrix.rustcomponents.sdk.setupTracing(filter)
}

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.libraries.matrix.util
package io.element.android.libraries.matrix.impl.util
import kotlinx.coroutines.channels.ProducerScope
import kotlinx.coroutines.channels.awaitClose

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.libraries.matrix.util
package io.element.android.libraries.matrix.impl.util
import org.matrix.rustcomponents.sdk.ClientException
import timber.log.Timber

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.libraries.matrix.util
package io.element.android.libraries.matrix.impl.util
import org.matrix.rustcomponents.sdk.TaskHandle
import java.util.concurrent.CopyOnWriteArraySet

View file

@ -1,109 +0,0 @@
/*
* 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.
*/
package io.element.android.libraries.matrix.session
import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.di.ApplicationContext
import io.element.android.libraries.di.SingleIn
import io.element.android.libraries.matrix.core.SessionId
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.map
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.matrix.rustcomponents.sdk.Session
import javax.inject.Inject
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "elementx_sessions")
// TODO It contains the access token, so it has to be stored in a more secured storage.
private val sessionKey = stringPreferencesKey("session")
@SingleIn(AppScope::class)
@ContributesBinding(AppScope::class)
class PreferencesSessionStore @Inject constructor(
@ApplicationContext context: Context
) : SessionStore {
@Serializable
data class SessionData(
val accessToken: String,
val deviceId: String,
val homeserverUrl: String,
val isSoftLogout: Boolean,
val refreshToken: String?,
val userId: String,
val slidingSyncProxy: String?
)
private val store = context.dataStore
override fun isLoggedIn(): Flow<Boolean> {
return store.data.map { prefs ->
prefs[sessionKey] != null
}
}
override suspend fun storeData(session: Session) {
store.edit { prefs ->
val sessionData = SessionData(
accessToken = session.accessToken,
deviceId = session.deviceId,
homeserverUrl = session.homeserverUrl,
isSoftLogout = session.isSoftLogout,
refreshToken = session.refreshToken,
userId = session.userId,
slidingSyncProxy = session.slidingSyncProxy
)
val encodedSession = Json.encodeToString(sessionData)
prefs[sessionKey] = encodedSession
}
}
override suspend fun getLatestSession(): Session? {
return store.data.firstOrNull()?.let { prefs ->
val encodedSession = prefs[sessionKey] ?: return@let null
val sessionData = Json.decodeFromString<SessionData>(encodedSession)
Session(
accessToken = sessionData.accessToken,
deviceId = sessionData.deviceId,
homeserverUrl = sessionData.homeserverUrl,
isSoftLogout = sessionData.isSoftLogout,
refreshToken = sessionData.refreshToken,
userId = sessionData.userId,
slidingSyncProxy = sessionData.slidingSyncProxy
)
}
}
override suspend fun getSession(sessionId: SessionId): Session? {
//TODO we should have a proper session management
return getLatestSession()
}
override suspend fun reset() {
store.edit { it.clear() }
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2023 New Vector Ltd
* 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.
@ -25,6 +25,6 @@ android {
}
dependencies {
api(projects.libraries.matrix)
api(projects.libraries.matrix.api)
api(libs.coroutines.core)
}

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
</manifest>

View file

@ -14,23 +14,22 @@
* limitations under the License.
*/
package io.element.android.libraries.matrixtest
package io.element.android.libraries.matrix.test
import io.element.android.libraries.matrix.MatrixClient
import io.element.android.libraries.matrix.core.RoomId
import io.element.android.libraries.matrix.core.SessionId
import io.element.android.libraries.matrix.core.UserId
import io.element.android.libraries.matrix.media.MediaResolver
import io.element.android.libraries.matrix.room.MatrixRoom
import io.element.android.libraries.matrix.room.RoomSummaryDataSource
import io.element.android.libraries.matrixtest.media.FakeMediaResolver
import io.element.android.libraries.matrixtest.room.FakeMatrixRoom
import io.element.android.libraries.matrixtest.room.FakeRoomSummaryDataSource
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.media.MediaResolver
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource
import io.element.android.libraries.matrix.test.media.FakeMediaResolver
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import io.element.android.libraries.matrix.test.room.FakeRoomSummaryDataSource
import kotlinx.coroutines.delay
import org.matrix.rustcomponents.sdk.MediaSource
class FakeMatrixClient(
override val sessionId: SessionId = SessionId(A_SESSION_ID),
override val sessionId: SessionId = A_SESSION_ID,
private val userDisplayName: Result<String> = Result.success(A_USER_NAME),
private val userAvatarURLString: Result<String> = Result.success(AN_AVATAR_URL),
val roomSummaryDataSource: RoomSummaryDataSource = FakeRoomSummaryDataSource()
@ -63,8 +62,6 @@ class FakeMatrixClient(
logoutFailure?.let { throw it }
}
override fun userId(): UserId = A_USER_ID
override suspend fun loadUserDisplayName(): Result<String> {
return userDisplayName
}

View file

@ -14,16 +14,19 @@
* limitations under the License.
*/
package io.element.android.libraries.matrixtest
package io.element.android.libraries.matrix.test
import io.element.android.libraries.matrix.core.EventId
import io.element.android.libraries.matrix.core.RoomId
import io.element.android.libraries.matrix.core.UserId
import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails
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.SessionId
import io.element.android.libraries.matrix.api.core.UserId
const val A_USER_NAME = "alice"
const val A_PASSWORD = "password"
val A_USER_ID = UserId("@alice:server.org")
val A_SESSION_ID = SessionId(A_USER_ID.value)
val A_ROOM_ID = RoomId("!aRoomId")
val AN_EVENT_ID = EventId("\$anEventId")
@ -32,9 +35,10 @@ const val A_MESSAGE = "Hello world!"
const val A_REPLY = "OK, I'll be there!"
const val ANOTHER_MESSAGE = "Hello universe!"
const val A_HOMESERVER = "matrix.org"
const val A_HOMESERVER_2 = "matrix-client.org"
const val A_SESSION_ID = "sessionId"
const val A_HOMESERVER_URL = "matrix.org"
const val A_HOMESERVER_URL_2 = "matrix-client.org"
val A_HOMESERVER = MatrixHomeServerDetails(A_HOMESERVER_URL, true, null)
const val AN_AVATAR_URL = "mxc://data"

View file

@ -14,20 +14,24 @@
* limitations under the License.
*/
package io.element.android.libraries.matrixtest.auth
package io.element.android.libraries.matrix.test.auth
import io.element.android.libraries.matrix.MatrixClient
import io.element.android.libraries.matrix.auth.MatrixAuthenticationService
import io.element.android.libraries.matrix.core.SessionId
import io.element.android.libraries.matrixtest.A_HOMESERVER
import io.element.android.libraries.matrixtest.A_SESSION_ID
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.test.A_HOMESERVER
import io.element.android.libraries.matrix.test.A_USER_ID
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flowOf
class FakeAuthenticationService : MatrixAuthenticationService {
private var homeserver: String = A_HOMESERVER
private var homeserver = MutableStateFlow<MatrixHomeServerDetails?>(null)
private var loginError: Throwable? = null
private var changeServerError: Throwable? = null
override fun isLoggedIn(): Flow<Boolean> {
return flowOf(false)
@ -41,29 +45,30 @@ class FakeAuthenticationService : MatrixAuthenticationService {
return null
}
override fun getHomeserver(): String? {
return null
}
fun givenHomeserver(homeserver: String) {
this.homeserver = homeserver
}
override fun getHomeserverOrDefault(): String {
override fun getHomeserverDetails(): StateFlow<MatrixHomeServerDetails?> {
return homeserver
}
fun givenHomeserver(homeserver: MatrixHomeServerDetails) {
this.homeserver.value = homeserver
}
override suspend fun setHomeserver(homeserver: String) {
changeServerError?.let { throw it }
delay(100)
}
override suspend fun login(username: String, password: String): SessionId {
delay(100)
loginError?.let { throw it }
return SessionId(A_SESSION_ID)
return A_USER_ID
}
fun givenLoginError(throwable: Throwable?) {
loginError = throwable
}
fun givenChangeServerError(throwable: Throwable?) {
changeServerError = throwable
}
}

View file

@ -14,9 +14,9 @@
* limitations under the License.
*/
package io.element.android.libraries.matrixtest.media
package io.element.android.libraries.matrix.test.media
import io.element.android.libraries.matrix.media.MediaResolver
import io.element.android.libraries.matrix.api.media.MediaResolver
class FakeMediaResolver : MediaResolver {
override suspend fun resolve(url: String?, kind: MediaResolver.Kind): ByteArray? {

View file

@ -14,14 +14,14 @@
* limitations under the License.
*/
package io.element.android.libraries.matrixtest.room
package io.element.android.libraries.matrix.test.room
import io.element.android.libraries.matrix.core.EventId
import io.element.android.libraries.matrix.core.RoomId
import io.element.android.libraries.matrix.room.MatrixRoom
import io.element.android.libraries.matrix.timeline.MatrixTimeline
import io.element.android.libraries.matrixtest.A_ROOM_ID
import io.element.android.libraries.matrixtest.timeline.FakeMatrixTimeline
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.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.timeline.FakeMatrixTimeline
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow

View file

@ -14,10 +14,10 @@
* limitations under the License.
*/
package io.element.android.libraries.matrixtest.room
package io.element.android.libraries.matrix.test.room
import io.element.android.libraries.matrix.room.RoomSummary
import io.element.android.libraries.matrix.room.RoomSummaryDataSource
import io.element.android.libraries.matrix.api.room.RoomSummary
import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow

View file

@ -14,14 +14,14 @@
* limitations under the License.
*/
package io.element.android.libraries.matrixtest.room
package io.element.android.libraries.matrix.test.room
import io.element.android.libraries.matrix.core.RoomId
import io.element.android.libraries.matrix.room.RoomSummary
import io.element.android.libraries.matrix.room.RoomSummaryDetails
import io.element.android.libraries.matrixtest.A_MESSAGE
import io.element.android.libraries.matrixtest.A_ROOM_ID
import io.element.android.libraries.matrixtest.A_ROOM_NAME
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.room.RoomSummary
import io.element.android.libraries.matrix.api.room.RoomSummaryDetails
import io.element.android.libraries.matrix.test.A_MESSAGE
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_ROOM_NAME
fun aRoomSummaryFilled(
roomId: RoomId = A_ROOM_ID,

View file

@ -14,11 +14,11 @@
* limitations under the License.
*/
package io.element.android.libraries.matrixtest.timeline
package io.element.android.libraries.matrix.test.timeline
import io.element.android.libraries.matrix.core.EventId
import io.element.android.libraries.matrix.timeline.MatrixTimeline
import io.element.android.libraries.matrix.timeline.MatrixTimelineItem
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow

View file

@ -35,7 +35,7 @@ dependencies {
anvil(projects.anvilcodegen)
implementation(projects.libraries.di)
implementation(projects.libraries.architecture)
implementation(projects.libraries.matrix)
implementation(projects.libraries.matrix.api)
implementation(projects.libraries.designsystem)
implementation(projects.libraries.core)
implementation(libs.coil.compose)

View file

@ -18,7 +18,7 @@ package io.element.android.libraries.matrix.ui
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.matrix.MatrixClient
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.ui.model.MatrixUser
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.Flow
@ -38,13 +38,13 @@ class MatrixItemHelper @Inject constructor(
val userDisplayName = client.loadUserDisplayName().getOrNull()
val avatarData =
AvatarData(
client.userId().value,
client.sessionId.value,
userDisplayName,
userAvatarUrl,
avatarSize
)
MatrixUser(
id = client.userId(),
id = client.sessionId,
username = userDisplayName,
avatarData = avatarData,
)

View file

@ -18,7 +18,7 @@ package io.element.android.libraries.matrix.ui.components
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.libraries.designsystem.components.avatar.anAvatarData
import io.element.android.libraries.matrix.core.UserId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.ui.model.MatrixUser
open class MatrixUserProvider : PreviewParameterProvider<MatrixUser> {

View file

@ -17,7 +17,7 @@
package io.element.android.libraries.matrix.ui.media
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.matrix.media.MediaResolver
import io.element.android.libraries.matrix.api.media.MediaResolver
import org.matrix.rustcomponents.sdk.mediaSourceFromUrl
fun AvatarData.toMetadata(): MediaResolver.Meta {

View file

@ -20,7 +20,7 @@ import android.content.Context
import coil.ImageLoader
import coil.ImageLoaderFactory
import io.element.android.libraries.di.ApplicationContext
import io.element.android.libraries.matrix.MatrixClient
import io.element.android.libraries.matrix.api.MatrixClient
import javax.inject.Inject
class LoggedInImageLoaderFactory @Inject constructor(

View file

@ -21,8 +21,8 @@ import coil.fetch.FetchResult
import coil.fetch.Fetcher
import coil.request.Options
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.matrix.MatrixClient
import io.element.android.libraries.matrix.media.MediaResolver
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.media.MediaResolver
import java.nio.ByteBuffer
internal class MediaFetcher(

View file

@ -19,7 +19,7 @@ package io.element.android.libraries.matrix.ui.media
import coil.key.Keyer
import coil.request.Options
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.matrix.media.MediaResolver
import io.element.android.libraries.matrix.api.media.MediaResolver
internal class AvatarKeyer : Keyer<AvatarData> {
override fun key(data: AvatarData, options: Options): String? {

Some files were not shown because too many files have changed in this diff Show more