Merge branch 'develop' into renovate/io.nlopez.compose.rules-detekt-0.x
This commit is contained in:
commit
683f7d4748
587 changed files with 9299 additions and 2438 deletions
|
|
@ -28,6 +28,7 @@ import android.widget.Toast
|
|||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.annotation.ChecksSdkIntAtLeast
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.content.pm.PackageInfoCompat
|
||||
import io.element.android.libraries.androidutils.R
|
||||
import io.element.android.libraries.androidutils.compat.getApplicationInfoCompat
|
||||
import io.element.android.libraries.core.mimetype.MimeTypes
|
||||
|
|
@ -47,6 +48,19 @@ fun Context.getApplicationLabel(packageName: String): String {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the versionCode from the Manifest.
|
||||
* The value is more accurate than BuildConfig.VERSION_CODE, as it is correct according to the
|
||||
* computation in the `androidComponents` block of the app build.gradle.kts file.
|
||||
* In other words, the last digit (for the architecture) will be set, whereas BuildConfig.VERSION_CODE
|
||||
* last digit will always be 0.
|
||||
*/
|
||||
fun Context.getVersionCodeFromManifest(): Long {
|
||||
return PackageInfoCompat.getLongVersionCode(
|
||||
packageManager.getPackageInfo(packageName, 0)
|
||||
)
|
||||
}
|
||||
|
||||
// ==============================================================================================================
|
||||
// Clipboard helper
|
||||
// ==============================================================================================================
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="error_no_compatible_app_found">"თავსებადი აპლიკაცია ვერ მოიძებნა ამ მოქმედების შესასრულებლად."</string>
|
||||
</resources>
|
||||
|
|
@ -86,6 +86,8 @@ sealed interface AsyncAction<out T> {
|
|||
fun isFailure(): Boolean = this is Failure
|
||||
|
||||
fun isSuccess(): Boolean = this is Success
|
||||
|
||||
fun isReady() = isSuccess() || isFailure()
|
||||
}
|
||||
|
||||
suspend inline fun <T> MutableState<AsyncAction<T>>.runCatchingUpdatingState(
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ data class BuildMeta(
|
|||
val applicationId: String,
|
||||
val lowPrivacyLoggingEnabled: Boolean,
|
||||
val versionName: String,
|
||||
val versionCode: Int,
|
||||
val versionCode: Long,
|
||||
val gitRevision: String,
|
||||
val gitBranchName: String,
|
||||
val flavorDescription: String,
|
||||
|
|
|
|||
|
|
@ -19,22 +19,19 @@ package io.element.android.libraries.designsystem.components.preferences
|
|||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewGroup
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.theme.components.ListSectionHeader
|
||||
|
||||
@Composable
|
||||
fun PreferenceCategory(
|
||||
modifier: Modifier = Modifier,
|
||||
title: String? = null,
|
||||
showDivider: Boolean = true,
|
||||
showTopDivider: Boolean = true,
|
||||
content: @Composable ColumnScope.() -> Unit,
|
||||
) {
|
||||
Column(
|
||||
|
|
@ -42,30 +39,17 @@ fun PreferenceCategory(
|
|||
.fillMaxWidth()
|
||||
) {
|
||||
if (title != null) {
|
||||
PreferenceCategoryTitle(title = title)
|
||||
}
|
||||
content()
|
||||
if (showDivider) {
|
||||
ListSectionHeader(
|
||||
title = title,
|
||||
hasDivider = showTopDivider,
|
||||
)
|
||||
} else if (showTopDivider) {
|
||||
PreferenceDivider()
|
||||
}
|
||||
content()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PreferenceCategoryTitle(title: String) {
|
||||
Text(
|
||||
modifier = Modifier.padding(
|
||||
top = 20.dp,
|
||||
bottom = 8.dp,
|
||||
start = preferencePaddingHorizontal,
|
||||
end = preferencePaddingHorizontal,
|
||||
),
|
||||
style = ElementTheme.typography.fontBodyLgMedium,
|
||||
color = ElementTheme.materialColors.primary,
|
||||
text = title,
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(group = PreviewGroup.Preferences)
|
||||
@Composable
|
||||
internal fun PreferenceCategoryPreview() = ElementThemedPreview {
|
||||
|
|
|
|||
|
|
@ -17,25 +17,18 @@
|
|||
package io.element.android.libraries.designsystem.components.preferences
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
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.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
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.libraries.designsystem.components.preferences.components.PreferenceIcon
|
||||
import io.element.android.libraries.designsystem.components.list.ListItemContent
|
||||
import io.element.android.libraries.designsystem.components.preferences.components.preferenceIcon
|
||||
import io.element.android.libraries.designsystem.icons.CompoundDrawables
|
||||
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewGroup
|
||||
import io.element.android.libraries.designsystem.theme.components.Checkbox
|
||||
import io.element.android.libraries.designsystem.theme.components.ListItem
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.toEnabledColor
|
||||
import io.element.android.libraries.designsystem.toSecondaryEnabledColor
|
||||
|
|
@ -52,45 +45,36 @@ fun PreferenceCheckbox(
|
|||
@DrawableRes iconResourceId: Int? = null,
|
||||
showIconAreaIfNoIcon: Boolean = false,
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.defaultMinSize(minHeight = preferenceMinHeight)
|
||||
.clickable { onCheckedChange(!isChecked) }
|
||||
.padding(vertical = 4.dp, horizontal = preferencePaddingHorizontal),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
PreferenceIcon(
|
||||
ListItem(
|
||||
modifier = modifier,
|
||||
onClick = onCheckedChange.takeIf { enabled }?.let { { onCheckedChange(!isChecked) } },
|
||||
leadingContent = preferenceIcon(
|
||||
icon = icon,
|
||||
iconResourceId = iconResourceId,
|
||||
enabled = enabled,
|
||||
isVisible = showIconAreaIfNoIcon
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier.weight(1f),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
showIconAreaIfNoIcon = showIconAreaIfNoIcon,
|
||||
),
|
||||
headlineContent = {
|
||||
Text(
|
||||
style = ElementTheme.typography.fontBodyLgRegular,
|
||||
text = title,
|
||||
color = enabled.toEnabledColor(),
|
||||
)
|
||||
if (supportingText != null) {
|
||||
},
|
||||
supportingContent = supportingText?.let {
|
||||
{
|
||||
Text(
|
||||
style = ElementTheme.typography.fontBodyMdRegular,
|
||||
text = supportingText,
|
||||
text = it,
|
||||
color = enabled.toSecondaryEnabledColor(),
|
||||
)
|
||||
}
|
||||
}
|
||||
Checkbox(
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterVertically),
|
||||
},
|
||||
trailingContent = ListItemContent.Checkbox(
|
||||
checked = isChecked,
|
||||
enabled = enabled,
|
||||
onCheckedChange = onCheckedChange
|
||||
)
|
||||
}
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(group = PreviewGroup.Preferences)
|
||||
|
|
@ -112,5 +96,31 @@ internal fun PreferenceCheckboxPreview() = ElementThemedPreview {
|
|||
isChecked = true,
|
||||
onCheckedChange = {},
|
||||
)
|
||||
PreferenceCheckbox(
|
||||
title = "Checkbox with supporting text",
|
||||
supportingText = "Supporting text",
|
||||
iconResourceId = CompoundDrawables.ic_compound_threads,
|
||||
enabled = false,
|
||||
isChecked = true,
|
||||
onCheckedChange = {},
|
||||
)
|
||||
PreferenceCheckbox(
|
||||
title = "Checkbox with supporting text",
|
||||
supportingText = "Supporting text",
|
||||
iconResourceId = null,
|
||||
showIconAreaIfNoIcon = true,
|
||||
enabled = true,
|
||||
isChecked = true,
|
||||
onCheckedChange = {},
|
||||
)
|
||||
PreferenceCheckbox(
|
||||
title = "Checkbox with supporting text",
|
||||
supportingText = "Supporting text",
|
||||
iconResourceId = null,
|
||||
showIconAreaIfNoIcon = false,
|
||||
enabled = true,
|
||||
isChecked = true,
|
||||
onCheckedChange = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ package io.element.android.libraries.designsystem.components.preferences
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewGroup
|
||||
import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
|
||||
|
|
@ -28,10 +27,7 @@ import io.element.android.libraries.designsystem.theme.components.HorizontalDivi
|
|||
fun PreferenceDivider(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
HorizontalDivider(
|
||||
modifier = modifier,
|
||||
color = ElementTheme.colors.borderDisabled,
|
||||
)
|
||||
HorizontalDivider(modifier = modifier)
|
||||
}
|
||||
|
||||
@Preview(group = PreviewGroup.Preferences)
|
||||
|
|
|
|||
|
|
@ -19,14 +19,13 @@ package io.element.android.libraries.designsystem.components.preferences
|
|||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewGroup
|
||||
import io.element.android.libraries.designsystem.theme.components.ListItem
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
|
||||
/**
|
||||
|
|
@ -37,15 +36,17 @@ fun PreferenceRow(
|
|||
modifier: Modifier = Modifier,
|
||||
content: @Composable RowScope.() -> Unit,
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.padding(horizontal = preferencePaddingHorizontal)
|
||||
.heightIn(min = preferenceMinHeight)
|
||||
.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
content()
|
||||
}
|
||||
ListItem(
|
||||
modifier = modifier,
|
||||
headlineContent = {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(group = PreviewGroup.Preferences)
|
||||
|
|
|
|||
|
|
@ -19,23 +19,18 @@ package io.element.android.libraries.designsystem.components.preferences
|
|||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.FloatRange
|
||||
import androidx.compose.foundation.layout.Column
|
||||
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.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.libraries.designsystem.components.preferences.components.PreferenceIcon
|
||||
import io.element.android.libraries.designsystem.components.preferences.components.preferenceIcon
|
||||
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewGroup
|
||||
import io.element.android.libraries.designsystem.theme.components.ListItem
|
||||
import io.element.android.libraries.designsystem.theme.components.Slider
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.toEnabledColor
|
||||
|
||||
@Composable
|
||||
fun PreferenceSlide(
|
||||
|
|
@ -51,51 +46,57 @@ fun PreferenceSlide(
|
|||
summary: String? = null,
|
||||
steps: Int = 0,
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.defaultMinSize(minHeight = preferenceMinHeight)
|
||||
.padding(vertical = 4.dp, horizontal = preferencePaddingHorizontal),
|
||||
) {
|
||||
PreferenceIcon(
|
||||
ListItem(
|
||||
modifier = modifier,
|
||||
enabled = enabled,
|
||||
leadingContent = preferenceIcon(
|
||||
icon = icon,
|
||||
iconResourceId = iconResourceId,
|
||||
isVisible = showIconAreaIfNoIcon,
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f),
|
||||
) {
|
||||
Text(
|
||||
style = ElementTheme.typography.fontBodyLgRegular,
|
||||
text = title,
|
||||
color = enabled.toEnabledColor(),
|
||||
)
|
||||
summary?.let {
|
||||
enabled = enabled,
|
||||
showIconAreaIfNoIcon = showIconAreaIfNoIcon,
|
||||
),
|
||||
headlineContent = {
|
||||
Column {
|
||||
Text(
|
||||
style = ElementTheme.typography.fontBodyMdRegular,
|
||||
text = summary,
|
||||
color = enabled.toEnabledColor(),
|
||||
style = ElementTheme.typography.fontBodyLgRegular,
|
||||
text = title,
|
||||
)
|
||||
summary?.let {
|
||||
Text(
|
||||
style = ElementTheme.typography.fontBodyMdRegular,
|
||||
text = summary,
|
||||
)
|
||||
}
|
||||
Slider(
|
||||
value = value,
|
||||
steps = steps,
|
||||
onValueChange = onValueChange,
|
||||
enabled = enabled,
|
||||
)
|
||||
}
|
||||
Slider(
|
||||
value = value,
|
||||
steps = steps,
|
||||
onValueChange = onValueChange,
|
||||
enabled = enabled,
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(group = PreviewGroup.Preferences)
|
||||
@Composable
|
||||
internal fun PreferenceSlidePreview() = ElementThemedPreview {
|
||||
PreferenceSlide(
|
||||
icon = CompoundIcons.UserProfile(),
|
||||
title = "Slide",
|
||||
summary = "Summary",
|
||||
value = 0.75F,
|
||||
onValueChange = {},
|
||||
)
|
||||
Column {
|
||||
PreferenceSlide(
|
||||
icon = CompoundIcons.UserProfile(),
|
||||
title = "Slide",
|
||||
summary = "Summary",
|
||||
enabled = true,
|
||||
value = 0.75F,
|
||||
onValueChange = {},
|
||||
)
|
||||
PreferenceSlide(
|
||||
icon = CompoundIcons.UserProfile(),
|
||||
title = "Slide",
|
||||
summary = "Summary",
|
||||
enabled = false,
|
||||
value = 0.75F,
|
||||
onValueChange = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,30 +17,19 @@
|
|||
package io.element.android.libraries.designsystem.components.preferences
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.clickable
|
||||
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.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
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.libraries.designsystem.components.preferences.components.PreferenceIcon
|
||||
import io.element.android.libraries.designsystem.components.list.ListItemContent
|
||||
import io.element.android.libraries.designsystem.components.preferences.components.preferenceIcon
|
||||
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewGroup
|
||||
import io.element.android.libraries.designsystem.theme.components.Switch
|
||||
import io.element.android.libraries.designsystem.theme.components.ListItem
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.toEnabledColor
|
||||
import io.element.android.libraries.designsystem.toSecondaryEnabledColor
|
||||
|
||||
@Composable
|
||||
fun PreferenceSwitch(
|
||||
|
|
@ -53,62 +42,65 @@ fun PreferenceSwitch(
|
|||
icon: ImageVector? = null,
|
||||
@DrawableRes iconResourceId: Int? = null,
|
||||
showIconAreaIfNoIcon: Boolean = false,
|
||||
switchAlignment: Alignment.Vertical = Alignment.CenterVertically
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.defaultMinSize(minHeight = preferenceMinHeight)
|
||||
.clickable { onCheckedChange(!isChecked) }
|
||||
.padding(vertical = 4.dp, horizontal = preferencePaddingHorizontal),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
PreferenceIcon(
|
||||
ListItem(
|
||||
modifier = modifier,
|
||||
enabled = enabled,
|
||||
onClick = onCheckedChange.takeIf { enabled }?.let { { onCheckedChange(!isChecked) } },
|
||||
leadingContent = preferenceIcon(
|
||||
icon = icon,
|
||||
iconResourceId = iconResourceId,
|
||||
enabled = enabled,
|
||||
isVisible = showIconAreaIfNoIcon
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.align(Alignment.CenterVertically)
|
||||
) {
|
||||
showIconAreaIfNoIcon = showIconAreaIfNoIcon,
|
||||
),
|
||||
headlineContent = {
|
||||
Text(
|
||||
style = ElementTheme.typography.fontBodyLgRegular,
|
||||
text = title,
|
||||
color = enabled.toEnabledColor(),
|
||||
)
|
||||
if (subtitle != null) {
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
},
|
||||
supportingContent = subtitle?.let {
|
||||
{
|
||||
Text(
|
||||
style = ElementTheme.typography.fontBodyMdRegular,
|
||||
text = subtitle,
|
||||
color = enabled.toSecondaryEnabledColor(),
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
// TODO Create a wrapper for Switch
|
||||
Switch(
|
||||
modifier = Modifier
|
||||
.align(switchAlignment),
|
||||
},
|
||||
trailingContent = ListItemContent.Switch(
|
||||
checked = isChecked,
|
||||
enabled = enabled,
|
||||
onCheckedChange = onCheckedChange
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(group = PreviewGroup.Preferences)
|
||||
@Composable
|
||||
internal fun PreferenceSwitchPreview() = ElementThemedPreview {
|
||||
PreferenceSwitch(
|
||||
title = "Switch",
|
||||
subtitle = "Subtitle Switch",
|
||||
icon = CompoundIcons.Threads(),
|
||||
enabled = true,
|
||||
isChecked = true,
|
||||
onCheckedChange = {},
|
||||
)
|
||||
Column {
|
||||
PreferenceSwitch(
|
||||
title = "Switch",
|
||||
subtitle = "Subtitle Switch",
|
||||
icon = CompoundIcons.Threads(),
|
||||
enabled = true,
|
||||
isChecked = true,
|
||||
onCheckedChange = {},
|
||||
)
|
||||
PreferenceSwitch(
|
||||
title = "Switch",
|
||||
subtitle = "Subtitle Switch",
|
||||
icon = CompoundIcons.Threads(),
|
||||
enabled = false,
|
||||
isChecked = true,
|
||||
onCheckedChange = {},
|
||||
)
|
||||
PreferenceSwitch(
|
||||
title = "Switch no subtitle",
|
||||
subtitle = null,
|
||||
icon = CompoundIcons.Threads(),
|
||||
enabled = false,
|
||||
isChecked = true,
|
||||
onCheckedChange = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,12 +17,9 @@
|
|||
package io.element.android.libraries.designsystem.components.preferences
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
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.foundation.layout.size
|
||||
import androidx.compose.foundation.progressSemantics
|
||||
|
|
@ -38,18 +35,17 @@ import io.element.android.compound.theme.ElementTheme
|
|||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage
|
||||
import io.element.android.libraries.designsystem.atomic.atoms.RedIndicatorAtom
|
||||
import io.element.android.libraries.designsystem.components.preferences.components.PreferenceIcon
|
||||
import io.element.android.libraries.designsystem.components.list.ListItemContent
|
||||
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
|
||||
import io.element.android.libraries.designsystem.preview.PreviewGroup
|
||||
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
|
||||
import io.element.android.libraries.designsystem.theme.components.ListItem
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.toEnabledColor
|
||||
import io.element.android.libraries.designsystem.toSecondaryEnabledColor
|
||||
|
||||
/**
|
||||
* Tried to use ListItem, but it cannot really match the design. Keep custom Layout for now.
|
||||
*/
|
||||
@Composable
|
||||
fun PreferenceText(
|
||||
title: String,
|
||||
|
|
@ -67,76 +63,76 @@ fun PreferenceText(
|
|||
tintColor: Color? = null,
|
||||
onClick: () -> Unit = {},
|
||||
) {
|
||||
val minHeight = if (subtitle == null && subtitleAnnotated == null) preferenceMinHeightOnlyTitle else preferenceMinHeight
|
||||
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.defaultMinSize(minHeight = minHeight)
|
||||
.clickable { onClick() }
|
||||
.padding(horizontal = preferencePaddingHorizontal, vertical = 4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
PreferenceIcon(
|
||||
ListItem(
|
||||
modifier = modifier,
|
||||
enabled = enabled,
|
||||
onClick = onClick,
|
||||
leadingContent = preferenceIcon(
|
||||
icon = icon,
|
||||
iconResourceId = iconResourceId,
|
||||
showIconBadge = showIconBadge,
|
||||
enabled = enabled,
|
||||
isVisible = showIconAreaIfNoIcon,
|
||||
tintColor = tintColor ?: enabled.toSecondaryEnabledColor(),
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.align(Alignment.CenterVertically)
|
||||
) {
|
||||
showIconAreaIfNoIcon = showIconAreaIfNoIcon,
|
||||
tintColor = tintColor,
|
||||
),
|
||||
headlineContent = {
|
||||
Text(
|
||||
style = ElementTheme.typography.fontBodyLgRegular,
|
||||
text = title,
|
||||
color = tintColor ?: enabled.toEnabledColor(),
|
||||
)
|
||||
if (subtitle != null) {
|
||||
},
|
||||
supportingContent = if (subtitle != null) {
|
||||
{
|
||||
Text(
|
||||
style = ElementTheme.typography.fontBodyMdRegular,
|
||||
text = subtitle,
|
||||
color = tintColor ?: enabled.toSecondaryEnabledColor(),
|
||||
)
|
||||
} else if (subtitleAnnotated != null) {
|
||||
Text(
|
||||
style = ElementTheme.typography.fontBodyMdRegular,
|
||||
text = subtitleAnnotated,
|
||||
color = tintColor ?: enabled.toSecondaryEnabledColor(),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
subtitleAnnotated?.let {
|
||||
{
|
||||
Text(
|
||||
style = ElementTheme.typography.fontBodyMdRegular,
|
||||
text = it,
|
||||
color = tintColor ?: enabled.toSecondaryEnabledColor(),
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
trailingContent = if (currentValue != null || loadingCurrentValue || showEndBadge) {
|
||||
ListItemContent.Custom {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
if (currentValue != null) {
|
||||
Text(
|
||||
text = currentValue,
|
||||
style = ElementTheme.typography.fontBodyXsMedium,
|
||||
color = enabled.toSecondaryEnabledColor(),
|
||||
)
|
||||
} else if (loadingCurrentValue) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier
|
||||
.progressSemantics()
|
||||
.size(20.dp),
|
||||
strokeWidth = 2.dp
|
||||
)
|
||||
}
|
||||
if (showEndBadge) {
|
||||
val endBadgeStartPadding = if (currentValue != null || loadingCurrentValue) 16.dp else 0.dp
|
||||
RedIndicatorAtom(
|
||||
modifier = Modifier
|
||||
.padding(start = endBadgeStartPadding)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
if (currentValue != null) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterVertically)
|
||||
.padding(start = 16.dp, end = 8.dp),
|
||||
text = currentValue,
|
||||
style = ElementTheme.typography.fontBodyXsMedium,
|
||||
color = enabled.toSecondaryEnabledColor(),
|
||||
)
|
||||
} else if (loadingCurrentValue) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier
|
||||
.progressSemantics()
|
||||
.padding(start = 16.dp, end = 8.dp)
|
||||
.size(20.dp)
|
||||
.align(Alignment.CenterVertically),
|
||||
strokeWidth = 2.dp
|
||||
)
|
||||
}
|
||||
if (showEndBadge) {
|
||||
val endBadgeStartPadding = if (currentValue != null || loadingCurrentValue) 8.dp else 16.dp
|
||||
RedIndicatorAtom(
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterVertically)
|
||||
.padding(start = endBadgeStartPadding)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(group = PreviewGroup.Preferences)
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ package io.element.android.libraries.designsystem.components.preferences.compone
|
|||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -31,13 +30,39 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.libraries.designsystem.atomic.atoms.RedIndicatorAtom
|
||||
import io.element.android.libraries.designsystem.components.list.ListItemContent
|
||||
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewGroup
|
||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
import io.element.android.libraries.designsystem.toSecondaryEnabledColor
|
||||
|
||||
@Composable
|
||||
fun PreferenceIcon(
|
||||
fun preferenceIcon(
|
||||
icon: ImageVector? = null,
|
||||
@DrawableRes iconResourceId: Int? = null,
|
||||
showIconBadge: Boolean = false,
|
||||
tintColor: Color? = null,
|
||||
enabled: Boolean = true,
|
||||
showIconAreaIfNoIcon: Boolean = false,
|
||||
): ListItemContent.Custom? {
|
||||
return if (icon != null || iconResourceId != null || showIconAreaIfNoIcon) {
|
||||
ListItemContent.Custom {
|
||||
PreferenceIcon(
|
||||
icon = icon,
|
||||
iconResourceId = iconResourceId,
|
||||
showIconBadge = showIconBadge,
|
||||
enabled = enabled,
|
||||
isVisible = showIconAreaIfNoIcon,
|
||||
tintColor = tintColor,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PreferenceIcon(
|
||||
modifier: Modifier = Modifier,
|
||||
icon: ImageVector? = null,
|
||||
@DrawableRes iconResourceId: Int? = null,
|
||||
|
|
@ -54,19 +79,17 @@ fun PreferenceIcon(
|
|||
contentDescription = null,
|
||||
tint = tintColor ?: enabled.toSecondaryEnabledColor(),
|
||||
modifier = Modifier
|
||||
.padding(end = 16.dp)
|
||||
.size(24.dp),
|
||||
)
|
||||
if (showIconBadge) {
|
||||
RedIndicatorAtom(
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopEnd)
|
||||
.padding(end = 16.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
} else if (isVisible) {
|
||||
Spacer(modifier = modifier.width(40.dp))
|
||||
Spacer(modifier = modifier.width(24.dp))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ class RoomMembershipContentFormatter @Inject constructor(
|
|||
): CharSequence? {
|
||||
val userId = membershipContent.userId
|
||||
val memberIsYou = matrixClient.isMe(userId)
|
||||
val userDisplayNameOrId = membershipContent.userDisplayName ?: userId.value
|
||||
return when (membershipContent.change) {
|
||||
MembershipChange.JOINED -> if (memberIsYou) {
|
||||
sp.getString(R.string.state_event_room_join_by_you)
|
||||
|
|
@ -46,41 +47,41 @@ class RoomMembershipContentFormatter @Inject constructor(
|
|||
sp.getString(R.string.state_event_room_leave, senderDisambiguatedDisplayName)
|
||||
}
|
||||
MembershipChange.BANNED, MembershipChange.KICKED_AND_BANNED -> if (senderIsYou) {
|
||||
sp.getString(R.string.state_event_room_ban_by_you, userId.value)
|
||||
sp.getString(R.string.state_event_room_ban_by_you, userDisplayNameOrId)
|
||||
} else {
|
||||
sp.getString(R.string.state_event_room_ban, senderDisambiguatedDisplayName, userId.value)
|
||||
sp.getString(R.string.state_event_room_ban, senderDisambiguatedDisplayName, userDisplayNameOrId)
|
||||
}
|
||||
MembershipChange.UNBANNED -> if (senderIsYou) {
|
||||
sp.getString(R.string.state_event_room_unban_by_you, userId.value)
|
||||
sp.getString(R.string.state_event_room_unban_by_you, userDisplayNameOrId)
|
||||
} else {
|
||||
sp.getString(R.string.state_event_room_unban, senderDisambiguatedDisplayName, userId.value)
|
||||
sp.getString(R.string.state_event_room_unban, senderDisambiguatedDisplayName, userDisplayNameOrId)
|
||||
}
|
||||
MembershipChange.KICKED -> if (senderIsYou) {
|
||||
sp.getString(R.string.state_event_room_remove_by_you, userId.value)
|
||||
sp.getString(R.string.state_event_room_remove_by_you, userDisplayNameOrId)
|
||||
} else {
|
||||
sp.getString(R.string.state_event_room_remove, senderDisambiguatedDisplayName, userId.value)
|
||||
sp.getString(R.string.state_event_room_remove, senderDisambiguatedDisplayName, userDisplayNameOrId)
|
||||
}
|
||||
MembershipChange.INVITED -> if (senderIsYou) {
|
||||
sp.getString(R.string.state_event_room_invite_by_you, userId.value)
|
||||
sp.getString(R.string.state_event_room_invite_by_you, userDisplayNameOrId)
|
||||
} else if (memberIsYou) {
|
||||
sp.getString(R.string.state_event_room_invite_you, senderDisambiguatedDisplayName)
|
||||
} else {
|
||||
sp.getString(R.string.state_event_room_invite, senderDisambiguatedDisplayName, userId.value)
|
||||
sp.getString(R.string.state_event_room_invite, senderDisambiguatedDisplayName, userDisplayNameOrId)
|
||||
}
|
||||
MembershipChange.INVITATION_ACCEPTED -> if (memberIsYou) {
|
||||
sp.getString(R.string.state_event_room_invite_accepted_by_you)
|
||||
} else {
|
||||
sp.getString(R.string.state_event_room_invite_accepted, userId.value)
|
||||
sp.getString(R.string.state_event_room_invite_accepted, userDisplayNameOrId)
|
||||
}
|
||||
MembershipChange.INVITATION_REJECTED -> if (memberIsYou) {
|
||||
sp.getString(R.string.state_event_room_reject_by_you)
|
||||
} else {
|
||||
sp.getString(R.string.state_event_room_reject, userId.value)
|
||||
sp.getString(R.string.state_event_room_reject, userDisplayNameOrId)
|
||||
}
|
||||
MembershipChange.INVITATION_REVOKED -> if (senderIsYou) {
|
||||
sp.getString(R.string.state_event_room_third_party_revoked_invite_by_you, userId.value)
|
||||
sp.getString(R.string.state_event_room_third_party_revoked_invite_by_you, userDisplayNameOrId)
|
||||
} else {
|
||||
sp.getString(R.string.state_event_room_third_party_revoked_invite, senderDisambiguatedDisplayName, userId.value)
|
||||
sp.getString(R.string.state_event_room_third_party_revoked_invite, senderDisambiguatedDisplayName, userDisplayNameOrId)
|
||||
}
|
||||
MembershipChange.KNOCKED -> if (memberIsYou) {
|
||||
sp.getString(R.string.state_event_room_knock_by_you)
|
||||
|
|
@ -88,9 +89,9 @@ class RoomMembershipContentFormatter @Inject constructor(
|
|||
sp.getString(R.string.state_event_room_knock, senderDisambiguatedDisplayName)
|
||||
}
|
||||
MembershipChange.KNOCK_ACCEPTED -> if (senderIsYou) {
|
||||
sp.getString(R.string.state_event_room_knock_accepted_by_you, userId.value)
|
||||
sp.getString(R.string.state_event_room_knock_accepted_by_you, userDisplayNameOrId)
|
||||
} else {
|
||||
sp.getString(R.string.state_event_room_knock_accepted, senderDisambiguatedDisplayName, userId.value)
|
||||
sp.getString(R.string.state_event_room_knock_accepted, senderDisambiguatedDisplayName, userDisplayNameOrId)
|
||||
}
|
||||
MembershipChange.KNOCK_RETRACTED -> if (memberIsYou) {
|
||||
sp.getString(R.string.state_event_room_knock_retracted_by_you)
|
||||
|
|
@ -98,11 +99,11 @@ class RoomMembershipContentFormatter @Inject constructor(
|
|||
sp.getString(R.string.state_event_room_knock_retracted, senderDisambiguatedDisplayName)
|
||||
}
|
||||
MembershipChange.KNOCK_DENIED -> if (senderIsYou) {
|
||||
sp.getString(R.string.state_event_room_knock_denied_by_you, userId.value)
|
||||
sp.getString(R.string.state_event_room_knock_denied_by_you, userDisplayNameOrId)
|
||||
} else if (memberIsYou) {
|
||||
sp.getString(R.string.state_event_room_knock_denied_you, senderDisambiguatedDisplayName)
|
||||
} else {
|
||||
sp.getString(R.string.state_event_room_knock_denied, senderDisambiguatedDisplayName, userId.value)
|
||||
sp.getString(R.string.state_event_room_knock_denied, senderDisambiguatedDisplayName, userDisplayNameOrId)
|
||||
}
|
||||
MembershipChange.NONE -> if (senderIsYou) {
|
||||
sp.getString(R.string.state_event_room_none_by_you)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="state_event_avatar_changed_too">"(ფოტოც შეიცვალა)"</string>
|
||||
<string name="state_event_avatar_url_changed">"%1$s პროფილის ფოტო შეცვალა"</string>
|
||||
<string name="state_event_avatar_url_changed_by_you">"თქვენ შეცვალეთ პროფილის ფოტო"</string>
|
||||
<string name="state_event_display_name_changed_from">"%1$s თავისი ნაჩვენები სახელი შეცვალა %2$s დან %3$s ზე"</string>
|
||||
<string name="state_event_display_name_changed_from_by_you">"თქვენ შეცვალეთ თქვენი ნაჩვენები სახელი %1$s -დან %2$s -ზე"</string>
|
||||
<string name="state_event_display_name_removed">"%1$s წაშალა თავისი ნაჩვენები სახელი (იყო %2$s)"</string>
|
||||
<string name="state_event_display_name_removed_by_you">"თქვენ წაშალეთ ნაჩვენები სახელი (იყო %1$s)"</string>
|
||||
<string name="state_event_display_name_set">"%1$s თავისი ნაჩვენები სახელი შეცვალა %2$s"</string>
|
||||
<string name="state_event_display_name_set_by_you">"თქვენი ახალი ნაჩვენები სახელი - %1$s"</string>
|
||||
<string name="state_event_room_avatar_changed">"%1$s ოთახის ფოტო შეცვალა"</string>
|
||||
<string name="state_event_room_avatar_changed_by_you">"თქვენ შეცვალეთ ოთახის ფოტო"</string>
|
||||
<string name="state_event_room_avatar_removed">"%1$s წაშალა ოთახის ფოტო"</string>
|
||||
<string name="state_event_room_avatar_removed_by_you">"თქვენ წაშალეთ ოთახის ფოტო"</string>
|
||||
<string name="state_event_room_ban">"%1$s დაბლოკა %2$s"</string>
|
||||
<string name="state_event_room_ban_by_you">"თქვენ დაბლოკეთ %1$s"</string>
|
||||
<string name="state_event_room_created">"%1$s შექმნა ოთახი"</string>
|
||||
<string name="state_event_room_created_by_you">"თქვენ შექმენით ოთახი"</string>
|
||||
<string name="state_event_room_invite">"%1$s მოიწვია %2$s"</string>
|
||||
<string name="state_event_room_invite_accepted">"%1$s მიიღო მოწვევა"</string>
|
||||
<string name="state_event_room_invite_accepted_by_you">"თქვენ მიიღეთ მოწვევა"</string>
|
||||
<string name="state_event_room_invite_by_you">"თქვენ მოიწვიეთ %1$s"</string>
|
||||
<string name="state_event_room_invite_you">"%1$s მოგიწვიათ"</string>
|
||||
<string name="state_event_room_join">"%1$s გაწევრიანდა ოთახში"</string>
|
||||
<string name="state_event_room_join_by_you">"თქვენ გაწევრიანდით ოთახში"</string>
|
||||
<string name="state_event_room_knock">"%1$s გაწევრიანება მოითხოვა"</string>
|
||||
<string name="state_event_room_knock_accepted">"%1$s გაწევრიანების უფლება მისცა %2$s"</string>
|
||||
<string name="state_event_room_knock_accepted_by_you">"თქვენ %1$s გაწევრიანების უფლება მიეცით"</string>
|
||||
<string name="state_event_room_knock_by_you">"თქვენ მოითხოვეთ გაწევრიანება"</string>
|
||||
<string name="state_event_room_knock_denied">"%1$s უარი თქვა %2$s-ს გაწევრიანების მოთხოვნაზე"</string>
|
||||
<string name="state_event_room_knock_denied_by_you">"თქვენ უარი თქვით %1$s გაწევრიანების თხოვნაზე"</string>
|
||||
<string name="state_event_room_knock_denied_you">"%1$s უარი თქვა თქვენს მოთხოვნაზე გაწევრიანების შესახებ"</string>
|
||||
<string name="state_event_room_knock_retracted">"%1$s აღარ არის დაინტერესებული გაწევრიანებით"</string>
|
||||
<string name="state_event_room_knock_retracted_by_you">"თქვენ გააუქმეთ გაწევრიანების მოთხოვნა"</string>
|
||||
<string name="state_event_room_leave">"%1$s დატოვა ოთახი"</string>
|
||||
<string name="state_event_room_leave_by_you">"თქვენ დატოვეთ ოთახი"</string>
|
||||
<string name="state_event_room_name_changed">"%1$s შეცვალა ოთახის სახელი: %2$s"</string>
|
||||
<string name="state_event_room_name_changed_by_you">"თქვენ შეცვალეთ ოთახის სახელი: %1$s"</string>
|
||||
<string name="state_event_room_name_removed">"%1$s წაშალა ოთახის სახელი"</string>
|
||||
<string name="state_event_room_name_removed_by_you">"თქვენ წაშალეთ ოთახის სახელი"</string>
|
||||
<string name="state_event_room_reject">"%1$s მოწვევაზე უარი თქვა"</string>
|
||||
<string name="state_event_room_reject_by_you">"თქვენ უარი თქვით მოწვევაზე"</string>
|
||||
<string name="state_event_room_remove">"%1$s გააგდო %2$s"</string>
|
||||
<string name="state_event_room_remove_by_you">"თქვენ გააგდეთ %1$s"</string>
|
||||
<string name="state_event_room_third_party_invite">"%1$s მოიწვია %2$s ოთახში"</string>
|
||||
<string name="state_event_room_third_party_invite_by_you">"თქვენ მოიწვიეთ %1$s ოთახში"</string>
|
||||
<string name="state_event_room_third_party_revoked_invite">"%1$s გააუქმო %2$s-ს ოთახში მოწვევა"</string>
|
||||
<string name="state_event_room_third_party_revoked_invite_by_you">"თქვენ %1$s-ს ოთახში მოწვევა გააუქმეთ"</string>
|
||||
<string name="state_event_room_topic_changed">"%1$s შეცვალა თემა: %2$s"</string>
|
||||
<string name="state_event_room_topic_changed_by_you">"თქვენ შეცვალეთ თემა: %1$s"</string>
|
||||
<string name="state_event_room_topic_removed">"%1$s წაშალა ოთახის თემა"</string>
|
||||
<string name="state_event_room_topic_removed_by_you">"თქვენ წაშალეთ ოთახის თემა"</string>
|
||||
<string name="state_event_room_unban">"%1$s განბლოკა %2$s"</string>
|
||||
<string name="state_event_room_unban_by_you">"თქვენ განბლოკეთ %1$s"</string>
|
||||
<string name="state_event_room_unknown_membership_change">"%1$s უცნობი ცვლილება შეიტანა თავის წევრობაში"</string>
|
||||
</resources>
|
||||
|
|
@ -254,9 +254,9 @@ class DefaultRoomLastMessageFormatterTest {
|
|||
@Test
|
||||
@Config(qualifiers = "en")
|
||||
fun `Membership change - joined`() {
|
||||
val otherName = "Someone"
|
||||
val youContent = RoomMembershipContent(A_USER_ID, MembershipChange.JOINED)
|
||||
val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), MembershipChange.JOINED)
|
||||
val otherName = "Other"
|
||||
val youContent = RoomMembershipContent(A_USER_ID, null, MembershipChange.JOINED)
|
||||
val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), otherName, MembershipChange.JOINED)
|
||||
|
||||
val youJoinedRoomEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent)
|
||||
val youJoinedRoom = formatter.format(youJoinedRoomEvent, false)
|
||||
|
|
@ -270,9 +270,9 @@ class DefaultRoomLastMessageFormatterTest {
|
|||
@Test
|
||||
@Config(qualifiers = "en")
|
||||
fun `Membership change - left`() {
|
||||
val otherName = "Someone"
|
||||
val youContent = RoomMembershipContent(A_USER_ID, MembershipChange.LEFT)
|
||||
val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), MembershipChange.LEFT)
|
||||
val otherName = "Other"
|
||||
val youContent = RoomMembershipContent(A_USER_ID, null, MembershipChange.LEFT)
|
||||
val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), otherName, MembershipChange.LEFT)
|
||||
|
||||
val youLeftRoomEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent)
|
||||
val youLeftRoom = formatter.format(youLeftRoomEvent, false)
|
||||
|
|
@ -286,67 +286,71 @@ class DefaultRoomLastMessageFormatterTest {
|
|||
@Test
|
||||
@Config(qualifiers = "en")
|
||||
fun `Membership change - banned`() {
|
||||
val otherName = "Someone"
|
||||
val youContent = RoomMembershipContent(UserId("@someone_else:domain"), MembershipChange.BANNED)
|
||||
val youKickedContent = RoomMembershipContent(UserId("@someone_else:domain"), MembershipChange.KICKED_AND_BANNED)
|
||||
val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), MembershipChange.BANNED)
|
||||
val someoneKickedContent = RoomMembershipContent(UserId("@someone_else:domain"), MembershipChange.KICKED_AND_BANNED)
|
||||
val otherName = "Other"
|
||||
val third = "Someone"
|
||||
val youContent = RoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.BANNED)
|
||||
val youKickedContent = RoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.KICKED_AND_BANNED)
|
||||
val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.BANNED)
|
||||
val someoneKickedContent = RoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.KICKED_AND_BANNED)
|
||||
|
||||
val youBannedEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent)
|
||||
val youBanned = formatter.format(youBannedEvent, false)
|
||||
assertThat(youBanned).isEqualTo("You banned ${youContent.userId}")
|
||||
assertThat(youBanned).isEqualTo("You banned $third")
|
||||
|
||||
val youKickBannedEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youKickedContent)
|
||||
val youKickedBanned = formatter.format(youKickBannedEvent, false)
|
||||
assertThat(youKickedBanned).isEqualTo("You banned ${youContent.userId}")
|
||||
assertThat(youKickedBanned).isEqualTo("You banned $third")
|
||||
|
||||
val someoneBannedEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent)
|
||||
val someoneBanned = formatter.format(someoneBannedEvent, false)
|
||||
assertThat(someoneBanned).isEqualTo("$otherName banned ${someoneContent.userId}")
|
||||
assertThat(someoneBanned).isEqualTo("$otherName banned $third")
|
||||
|
||||
val someoneKickBannedEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = someoneKickedContent)
|
||||
val someoneKickBanned = formatter.format(someoneKickBannedEvent, false)
|
||||
assertThat(someoneKickBanned).isEqualTo("$otherName banned ${someoneContent.userId}")
|
||||
assertThat(someoneKickBanned).isEqualTo("$otherName banned $third")
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(qualifiers = "en")
|
||||
fun `Membership change - unban`() {
|
||||
val otherName = "Someone"
|
||||
val youContent = RoomMembershipContent(UserId("@someone_else:domain"), MembershipChange.UNBANNED)
|
||||
val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), MembershipChange.UNBANNED)
|
||||
val otherName = "Other"
|
||||
val third = "Someone"
|
||||
val youContent = RoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.UNBANNED)
|
||||
val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.UNBANNED)
|
||||
|
||||
val youUnbannedEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent)
|
||||
val youUnbanned = formatter.format(youUnbannedEvent, false)
|
||||
assertThat(youUnbanned).isEqualTo("You unbanned ${youContent.userId}")
|
||||
assertThat(youUnbanned).isEqualTo("You unbanned $third")
|
||||
|
||||
val someoneUnbannedEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent)
|
||||
val someoneUnbanned = formatter.format(someoneUnbannedEvent, false)
|
||||
assertThat(someoneUnbanned).isEqualTo("$otherName unbanned ${someoneContent.userId}")
|
||||
assertThat(someoneUnbanned).isEqualTo("$otherName unbanned $third")
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(qualifiers = "en")
|
||||
fun `Membership change - kicked`() {
|
||||
val otherName = "Someone"
|
||||
val youContent = RoomMembershipContent(UserId("@someone_else:domain"), MembershipChange.KICKED)
|
||||
val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), MembershipChange.KICKED)
|
||||
val otherName = "Other"
|
||||
val third = "Someone"
|
||||
val youContent = RoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.KICKED)
|
||||
val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.KICKED)
|
||||
|
||||
val youKickedEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent)
|
||||
val youKicked = formatter.format(youKickedEvent, false)
|
||||
assertThat(youKicked).isEqualTo("You removed ${youContent.userId}")
|
||||
assertThat(youKicked).isEqualTo("You removed $third")
|
||||
|
||||
val someoneKickedEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent)
|
||||
val someoneKicked = formatter.format(someoneKickedEvent, false)
|
||||
assertThat(someoneKicked).isEqualTo("$otherName removed ${someoneContent.userId}")
|
||||
assertThat(someoneKicked).isEqualTo("$otherName removed $third")
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(qualifiers = "en")
|
||||
fun `Membership change - invited`() {
|
||||
val otherName = "Someone"
|
||||
val youContent = RoomMembershipContent(A_USER_ID, MembershipChange.INVITED)
|
||||
val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), MembershipChange.INVITED)
|
||||
val otherName = "Other"
|
||||
val third = "Someone"
|
||||
val youContent = RoomMembershipContent(A_USER_ID, null, MembershipChange.INVITED)
|
||||
val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.INVITED)
|
||||
|
||||
val youWereInvitedEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = youContent)
|
||||
val youWereInvited = formatter.format(youWereInvitedEvent, false)
|
||||
|
|
@ -354,19 +358,19 @@ class DefaultRoomLastMessageFormatterTest {
|
|||
|
||||
val youInvitedEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = someoneContent)
|
||||
val youInvited = formatter.format(youInvitedEvent, false)
|
||||
assertThat(youInvited).isEqualTo("You invited ${someoneContent.userId}")
|
||||
assertThat(youInvited).isEqualTo("You invited $third")
|
||||
|
||||
val someoneInvitedEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent)
|
||||
val someoneInvited = formatter.format(someoneInvitedEvent, false)
|
||||
assertThat(someoneInvited).isEqualTo("$otherName invited ${someoneContent.userId}")
|
||||
assertThat(someoneInvited).isEqualTo("$otherName invited $third")
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(qualifiers = "en")
|
||||
fun `Membership change - invitation accepted`() {
|
||||
val otherName = "Someone"
|
||||
val youContent = RoomMembershipContent(A_USER_ID, MembershipChange.INVITATION_ACCEPTED)
|
||||
val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), MembershipChange.INVITATION_ACCEPTED)
|
||||
val otherName = "Other"
|
||||
val youContent = RoomMembershipContent(A_USER_ID, null, MembershipChange.INVITATION_ACCEPTED)
|
||||
val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), otherName, MembershipChange.INVITATION_ACCEPTED)
|
||||
|
||||
val youAcceptedInviteEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent)
|
||||
val youAcceptedInvite = formatter.format(youAcceptedInviteEvent, false)
|
||||
|
|
@ -374,15 +378,15 @@ class DefaultRoomLastMessageFormatterTest {
|
|||
|
||||
val someoneAcceptedInviteEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent)
|
||||
val someoneAcceptedInvite = formatter.format(someoneAcceptedInviteEvent, false)
|
||||
assertThat(someoneAcceptedInvite).isEqualTo("${someoneContent.userId} accepted the invite")
|
||||
assertThat(someoneAcceptedInvite).isEqualTo("$otherName accepted the invite")
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(qualifiers = "en")
|
||||
fun `Membership change - invitation rejected`() {
|
||||
val otherName = "Someone"
|
||||
val youContent = RoomMembershipContent(A_USER_ID, MembershipChange.INVITATION_REJECTED)
|
||||
val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), MembershipChange.INVITATION_REJECTED)
|
||||
val otherName = "Other"
|
||||
val youContent = RoomMembershipContent(A_USER_ID, null, MembershipChange.INVITATION_REJECTED)
|
||||
val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), otherName, MembershipChange.INVITATION_REJECTED)
|
||||
|
||||
val youRejectedInviteEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent)
|
||||
val youRejectedInvite = formatter.format(youRejectedInviteEvent, false)
|
||||
|
|
@ -390,30 +394,31 @@ class DefaultRoomLastMessageFormatterTest {
|
|||
|
||||
val someoneRejectedInviteEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent)
|
||||
val someoneRejectedInvite = formatter.format(someoneRejectedInviteEvent, false)
|
||||
assertThat(someoneRejectedInvite).isEqualTo("${someoneContent.userId} rejected the invitation")
|
||||
assertThat(someoneRejectedInvite).isEqualTo("$otherName rejected the invitation")
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(qualifiers = "en")
|
||||
fun `Membership change - invitation revoked`() {
|
||||
val otherName = "Someone"
|
||||
val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), MembershipChange.INVITATION_REVOKED)
|
||||
val otherName = "Other"
|
||||
val third = "Someone"
|
||||
val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.INVITATION_REVOKED)
|
||||
|
||||
val youRevokedInviteEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = someoneContent)
|
||||
val youRevokedInvite = formatter.format(youRevokedInviteEvent, false)
|
||||
assertThat(youRevokedInvite).isEqualTo("You revoked the invitation for ${someoneContent.userId} to join the room")
|
||||
assertThat(youRevokedInvite).isEqualTo("You revoked the invitation for $third to join the room")
|
||||
|
||||
val someoneRevokedInviteEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent)
|
||||
val someoneRevokedInvite = formatter.format(someoneRevokedInviteEvent, false)
|
||||
assertThat(someoneRevokedInvite).isEqualTo("$otherName revoked the invitation for ${someoneContent.userId} to join the room")
|
||||
assertThat(someoneRevokedInvite).isEqualTo("$otherName revoked the invitation for $third to join the room")
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(qualifiers = "en")
|
||||
fun `Membership change - knocked`() {
|
||||
val otherName = "Someone"
|
||||
val youContent = RoomMembershipContent(A_USER_ID, MembershipChange.KNOCKED)
|
||||
val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), MembershipChange.KNOCKED)
|
||||
val otherName = "Other"
|
||||
val youContent = RoomMembershipContent(A_USER_ID, null, MembershipChange.KNOCKED)
|
||||
val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), otherName, MembershipChange.KNOCKED)
|
||||
|
||||
val youKnockedEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent)
|
||||
val youKnocked = formatter.format(youKnockedEvent, false)
|
||||
|
|
@ -427,24 +432,25 @@ class DefaultRoomLastMessageFormatterTest {
|
|||
@Test
|
||||
@Config(qualifiers = "en")
|
||||
fun `Membership change - knock accepted`() {
|
||||
val otherName = "Someone"
|
||||
val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), MembershipChange.KNOCK_ACCEPTED)
|
||||
val otherName = "Other"
|
||||
val third = "Someone"
|
||||
val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.KNOCK_ACCEPTED)
|
||||
|
||||
val youAcceptedKnockEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = someoneContent)
|
||||
val youAcceptedKnock = formatter.format(youAcceptedKnockEvent, false)
|
||||
assertThat(youAcceptedKnock).isEqualTo("You allowed ${someoneContent.userId} to join")
|
||||
assertThat(youAcceptedKnock).isEqualTo("You allowed $third to join")
|
||||
|
||||
val someoneAcceptedKnockEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent)
|
||||
val someoneAcceptedKnock = formatter.format(someoneAcceptedKnockEvent, false)
|
||||
assertThat(someoneAcceptedKnock).isEqualTo("$otherName allowed ${someoneContent.userId} to join")
|
||||
assertThat(someoneAcceptedKnock).isEqualTo("$otherName allowed $third to join")
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(qualifiers = "en")
|
||||
fun `Membership change - knock retracted`() {
|
||||
val otherName = "Someone"
|
||||
val youContent = RoomMembershipContent(A_USER_ID, MembershipChange.KNOCK_RETRACTED)
|
||||
val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), MembershipChange.KNOCK_RETRACTED)
|
||||
val otherName = "Other"
|
||||
val youContent = RoomMembershipContent(A_USER_ID, null, MembershipChange.KNOCK_RETRACTED)
|
||||
val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), null, MembershipChange.KNOCK_RETRACTED)
|
||||
|
||||
val youRetractedKnockEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent)
|
||||
val youRetractedKnock = formatter.format(youRetractedKnockEvent, false)
|
||||
|
|
@ -458,17 +464,18 @@ class DefaultRoomLastMessageFormatterTest {
|
|||
@Test
|
||||
@Config(qualifiers = "en")
|
||||
fun `Membership change - knock denied`() {
|
||||
val otherName = "Someone"
|
||||
val youContent = RoomMembershipContent(A_USER_ID, MembershipChange.KNOCK_DENIED)
|
||||
val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), MembershipChange.KNOCK_DENIED)
|
||||
val otherName = "Other"
|
||||
val third = "Someone"
|
||||
val youContent = RoomMembershipContent(A_USER_ID, third, MembershipChange.KNOCK_DENIED)
|
||||
val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.KNOCK_DENIED)
|
||||
|
||||
val youDeniedKnockEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = someoneContent)
|
||||
val youDeniedKnock = formatter.format(youDeniedKnockEvent, false)
|
||||
assertThat(youDeniedKnock).isEqualTo("You rejected ${someoneContent.userId}'s request to join")
|
||||
assertThat(youDeniedKnock).isEqualTo("You rejected $third's request to join")
|
||||
|
||||
val someoneDeniedKnockEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent)
|
||||
val someoneDeniedKnock = formatter.format(someoneDeniedKnockEvent, false)
|
||||
assertThat(someoneDeniedKnock).isEqualTo("$otherName rejected ${someoneContent.userId}'s request to join")
|
||||
assertThat(someoneDeniedKnock).isEqualTo("$otherName rejected $third's request to join")
|
||||
|
||||
val someoneDeniedYourKnockEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = youContent)
|
||||
val someoneDeniedYourKnock = formatter.format(someoneDeniedYourKnockEvent, false)
|
||||
|
|
@ -478,9 +485,9 @@ class DefaultRoomLastMessageFormatterTest {
|
|||
@Test
|
||||
@Config(qualifiers = "en")
|
||||
fun `Membership change - None`() {
|
||||
val otherName = "Someone"
|
||||
val youContent = RoomMembershipContent(A_USER_ID, MembershipChange.NONE)
|
||||
val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), MembershipChange.NONE)
|
||||
val otherName = "Other"
|
||||
val youContent = RoomMembershipContent(A_USER_ID, null, MembershipChange.NONE)
|
||||
val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), otherName, MembershipChange.NONE)
|
||||
|
||||
val youNoneRoomEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent)
|
||||
val youNoneRoom = formatter.format(youNoneRoomEvent, false)
|
||||
|
|
@ -497,7 +504,7 @@ class DefaultRoomLastMessageFormatterTest {
|
|||
val otherChanges = arrayOf(MembershipChange.ERROR, MembershipChange.NOT_IMPLEMENTED, null)
|
||||
|
||||
val results = otherChanges.map { change ->
|
||||
val content = RoomMembershipContent(A_USER_ID, change)
|
||||
val content = RoomMembershipContent(A_USER_ID, null, change)
|
||||
val event = createRoomEvent(sentByYou = false, senderDisplayName = "Someone", content = content)
|
||||
val result = formatter.format(event, false)
|
||||
change to result
|
||||
|
|
@ -513,7 +520,7 @@ class DefaultRoomLastMessageFormatterTest {
|
|||
@Test
|
||||
@Config(qualifiers = "en")
|
||||
fun `Room state change - avatar`() {
|
||||
val otherName = "Someone"
|
||||
val otherName = "Other"
|
||||
val changedContent = StateContent("", OtherState.RoomAvatar("new_avatar"))
|
||||
val removedContent = StateContent("", OtherState.RoomAvatar(null))
|
||||
|
||||
|
|
@ -537,7 +544,7 @@ class DefaultRoomLastMessageFormatterTest {
|
|||
@Test
|
||||
@Config(qualifiers = "en")
|
||||
fun `Room state change - create`() {
|
||||
val otherName = "Someone"
|
||||
val otherName = "Other"
|
||||
val content = StateContent("", OtherState.RoomCreate)
|
||||
|
||||
val youCreatedRoomMessage = createRoomEvent(sentByYou = true, senderDisplayName = null, content = content)
|
||||
|
|
@ -552,7 +559,7 @@ class DefaultRoomLastMessageFormatterTest {
|
|||
@Test
|
||||
@Config(qualifiers = "en")
|
||||
fun `Room state change - encryption`() {
|
||||
val otherName = "Someone"
|
||||
val otherName = "Other"
|
||||
val content = StateContent("", OtherState.RoomEncryption)
|
||||
|
||||
val youCreatedRoomMessage = createRoomEvent(sentByYou = true, senderDisplayName = null, content = content)
|
||||
|
|
@ -567,7 +574,7 @@ class DefaultRoomLastMessageFormatterTest {
|
|||
@Test
|
||||
@Config(qualifiers = "en")
|
||||
fun `Room state change - room name`() {
|
||||
val otherName = "Someone"
|
||||
val otherName = "Other"
|
||||
val newName = "New name"
|
||||
val changedContent = StateContent("", OtherState.RoomName(newName))
|
||||
val removedContent = StateContent("", OtherState.RoomName(null))
|
||||
|
|
@ -592,7 +599,7 @@ class DefaultRoomLastMessageFormatterTest {
|
|||
@Test
|
||||
@Config(qualifiers = "en")
|
||||
fun `Room state change - third party invite`() {
|
||||
val otherName = "Someone"
|
||||
val otherName = "Other"
|
||||
val inviteeName = "Alice"
|
||||
val changedContent = StateContent("", OtherState.RoomThirdPartyInvite(inviteeName))
|
||||
val removedContent = StateContent("", OtherState.RoomThirdPartyInvite(null))
|
||||
|
|
@ -617,7 +624,7 @@ class DefaultRoomLastMessageFormatterTest {
|
|||
@Test
|
||||
@Config(qualifiers = "en")
|
||||
fun `Room state change - room topic`() {
|
||||
val otherName = "Someone"
|
||||
val otherName = "Other"
|
||||
val roomTopic = "New topic"
|
||||
val changedContent = StateContent("", OtherState.RoomTopic(roomTopic))
|
||||
val removedContent = StateContent("", OtherState.RoomTopic(null))
|
||||
|
|
@ -677,7 +684,7 @@ class DefaultRoomLastMessageFormatterTest {
|
|||
@Test
|
||||
@Config(qualifiers = "en")
|
||||
fun `Profile change - avatar`() {
|
||||
val otherName = "Someone"
|
||||
val otherName = "Other"
|
||||
val changedContent = aProfileChangeMessageContent(avatarUrl = "new_avatar_url", prevAvatarUrl = "old_avatar_url")
|
||||
val setContent = aProfileChangeMessageContent(avatarUrl = "new_avatar_url", prevAvatarUrl = null)
|
||||
val removedContent = aProfileChangeMessageContent(avatarUrl = null, prevAvatarUrl = "old_avatar_url")
|
||||
|
|
@ -722,7 +729,7 @@ class DefaultRoomLastMessageFormatterTest {
|
|||
fun `Profile change - display name`() {
|
||||
val newDisplayName = "New"
|
||||
val oldDisplayName = "Old"
|
||||
val otherName = "Someone"
|
||||
val otherName = "Other"
|
||||
val changedContent = aProfileChangeMessageContent(displayName = newDisplayName, prevDisplayName = oldDisplayName)
|
||||
val setContent = aProfileChangeMessageContent(displayName = newDisplayName, prevDisplayName = null)
|
||||
val removedContent = aProfileChangeMessageContent(displayName = null, prevDisplayName = oldDisplayName)
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ class DefaultIndicatorService @Inject constructor(
|
|||
) : IndicatorService {
|
||||
@Composable
|
||||
override fun showRoomListTopBarIndicator(): State<Boolean> {
|
||||
val canVerifySession by sessionVerificationService.canVerifySessionFlow.collectAsState(initial = false)
|
||||
val canVerifySession by sessionVerificationService.needsSessionVerification.collectAsState(initial = false)
|
||||
val settingChatBackupIndicator = showSettingChatBackupIndicator()
|
||||
|
||||
return remember {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ package io.element.android.libraries.matrix.api
|
|||
import io.element.android.libraries.matrix.api.core.ProgressCallback
|
||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters
|
||||
|
|
@ -105,5 +104,5 @@ interface MatrixClient : Closeable {
|
|||
suspend fun trackRecentlyVisitedRoom(roomId: RoomId): Result<Unit>
|
||||
suspend fun getRecentlyVisitedRooms(): Result<List<RoomId>>
|
||||
suspend fun resolveRoomAlias(roomAlias: RoomAlias): Result<ResolvedRoomAlias>
|
||||
suspend fun getRoomPreview(roomIdOrAlias: RoomIdOrAlias): Result<RoomPreview>
|
||||
suspend fun getRoomPreviewFromRoomId(roomId: RoomId, serverNames: List<String>): Result<RoomPreview>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,4 +50,16 @@ interface EncryptionService {
|
|||
* Wait for backup upload steady state.
|
||||
*/
|
||||
fun waitForBackupUploadSteadyState(): Flow<BackupUploadState>
|
||||
|
||||
/**
|
||||
* Get the public curve25519 key of our own device in base64. This is usually what is
|
||||
* called the identity key of the device.
|
||||
*/
|
||||
suspend fun deviceCurve25519(): String?
|
||||
|
||||
/**
|
||||
* Get the public ed25519 key of our own device. This is usually what is
|
||||
* called the fingerprint of the device.
|
||||
*/
|
||||
suspend fun deviceEd25519(): String?
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,5 +18,5 @@ package io.element.android.libraries.matrix.api.pusher
|
|||
|
||||
interface PushersService {
|
||||
suspend fun setHttpPusher(setHttpPusherData: SetHttpPusherData): Result<Unit>
|
||||
suspend fun unsetHttpPusher(): Result<Unit>
|
||||
suspend fun unsetHttpPusher(unsetHttpPusherData: UnsetHttpPusherData): Result<Unit>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
* Copyright (c) 2024 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.
|
||||
|
|
@ -14,10 +14,9 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.designsystem.components.preferences
|
||||
package io.element.android.libraries.matrix.api.pusher
|
||||
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
internal val preferenceMinHeightOnlyTitle = 56.dp
|
||||
internal val preferenceMinHeight = 56.dp
|
||||
internal val preferencePaddingHorizontal = 16.dp
|
||||
data class UnsetHttpPusherData(
|
||||
val pushKey: String,
|
||||
val appId: String,
|
||||
)
|
||||
|
|
@ -73,6 +73,7 @@ data class UnableToDecryptContent(
|
|||
|
||||
data class RoomMembershipContent(
|
||||
val userId: UserId,
|
||||
val userDisplayName: String?,
|
||||
val change: MembershipChange?
|
||||
) : EventContent
|
||||
|
||||
|
|
|
|||
|
|
@ -26,12 +26,6 @@ interface SessionVerificationService {
|
|||
*/
|
||||
val verificationFlowState: StateFlow<VerificationFlowState>
|
||||
|
||||
/**
|
||||
* The internal service that checks verification can only run after the initial sync.
|
||||
* This [StateFlow] will notify consumers when the service is ready to be used.
|
||||
*/
|
||||
val isReady: StateFlow<Boolean>
|
||||
|
||||
/**
|
||||
* Returns whether the current verification status is either: [SessionVerifiedStatus.Unknown], [SessionVerifiedStatus.NotVerified]
|
||||
* or [SessionVerifiedStatus.Verified].
|
||||
|
|
@ -39,9 +33,9 @@ interface SessionVerificationService {
|
|||
val sessionVerifiedStatus: StateFlow<SessionVerifiedStatus>
|
||||
|
||||
/**
|
||||
* Returns whether the current session needs to be verified and the SDK is ready to start the verification.
|
||||
* Returns whether the current session needs to be verified.
|
||||
*/
|
||||
val canVerifySessionFlow: Flow<Boolean>
|
||||
val needsSessionVerification: Flow<Boolean>
|
||||
|
||||
/**
|
||||
* Request verification of the current session.
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ import io.element.android.libraries.matrix.api.MatrixClient
|
|||
import io.element.android.libraries.matrix.api.core.ProgressCallback
|
||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters
|
||||
import io.element.android.libraries.matrix.api.createroom.RoomPreset
|
||||
|
|
@ -487,9 +486,12 @@ class RustMatrixClient(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun getRoomPreview(roomIdOrAlias: RoomIdOrAlias): Result<RoomPreview> = withContext(sessionDispatcher) {
|
||||
override suspend fun getRoomPreviewFromRoomId(roomId: RoomId, serverNames: List<String>): Result<RoomPreview> = withContext(sessionDispatcher) {
|
||||
runCatching {
|
||||
client.getRoomPreview(roomIdOrAlias.identifier).let(RoomPreviewMapper::map)
|
||||
client.getRoomPreviewFromRoomId(
|
||||
roomId = roomId.value,
|
||||
viaServers = serverNames,
|
||||
).let(RoomPreviewMapper::map)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -190,4 +190,12 @@ internal class RustEncryptionService(
|
|||
it.mapRecoveryException()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun deviceCurve25519(): String? {
|
||||
return service.curve25519Key()
|
||||
}
|
||||
|
||||
override suspend fun deviceEd25519(): String? {
|
||||
return service.ed25519Key()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package io.element.android.libraries.matrix.impl.pushers
|
|||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.matrix.api.pusher.PushersService
|
||||
import io.element.android.libraries.matrix.api.pusher.SetHttpPusherData
|
||||
import io.element.android.libraries.matrix.api.pusher.UnsetHttpPusherData
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.matrix.rustcomponents.sdk.Client
|
||||
import org.matrix.rustcomponents.sdk.HttpPusherData
|
||||
|
|
@ -54,8 +55,16 @@ class RustPushersService(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun unsetHttpPusher(): Result<Unit> {
|
||||
// TODO Missing client API. We need to set the pusher with Kind == null, but we do not have access to this field from the SDK.
|
||||
return Result.success(Unit)
|
||||
override suspend fun unsetHttpPusher(unsetHttpPusherData: UnsetHttpPusherData): Result<Unit> {
|
||||
return withContext(dispatchers.io) {
|
||||
runCatching {
|
||||
client.deletePusher(
|
||||
identifiers = PusherIdentifiers(
|
||||
pushkey = unsetHttpPusherData.pushKey,
|
||||
appId = unsetHttpPusherData.appId
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,8 +88,9 @@ class TimelineEventContentMapper(private val eventMessageMapper: EventMessageMap
|
|||
}
|
||||
is TimelineItemContentKind.RoomMembership -> {
|
||||
RoomMembershipContent(
|
||||
UserId(kind.userId),
|
||||
kind.change?.map()
|
||||
userId = UserId(kind.userId),
|
||||
userDisplayName = kind.userDisplayName,
|
||||
change = kind.change?.map()
|
||||
)
|
||||
}
|
||||
is TimelineItemContentKind.State -> {
|
||||
|
|
|
|||
|
|
@ -30,8 +30,8 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -80,10 +80,14 @@ class RustSessionVerificationService(
|
|||
private val _sessionVerifiedStatus = MutableStateFlow<SessionVerifiedStatus>(SessionVerifiedStatus.Unknown)
|
||||
override val sessionVerifiedStatus: StateFlow<SessionVerifiedStatus> = _sessionVerifiedStatus.asStateFlow()
|
||||
|
||||
override val isReady = isSyncServiceReady.stateIn(sessionCoroutineScope, SharingStarted.Eagerly, false)
|
||||
/**
|
||||
* The internal service that checks verification can only run after the initial sync.
|
||||
* This [StateFlow] will notify consumers when the service is ready to be used.
|
||||
*/
|
||||
private val isReady = isSyncServiceReady.stateIn(sessionCoroutineScope, SharingStarted.Eagerly, false)
|
||||
|
||||
override val canVerifySessionFlow = combine(sessionVerifiedStatus, isReady) { verificationStatus, isReady ->
|
||||
isReady && verificationStatus == SessionVerifiedStatus.NotVerified
|
||||
override val needsSessionVerification = sessionVerifiedStatus.map { verificationStatus ->
|
||||
verificationStatus == SessionVerifiedStatus.NotVerified
|
||||
}
|
||||
|
||||
init {
|
||||
|
|
|
|||
|
|
@ -57,9 +57,9 @@ class DefaultJoinRoomTest {
|
|||
.isNeverCalled()
|
||||
joinRoomLambda
|
||||
.assertions()
|
||||
.isCalledExactly(1)
|
||||
.withSequence(
|
||||
listOf(value(A_ROOM_ID))
|
||||
.isCalledOnce()
|
||||
.with(
|
||||
value(A_ROOM_ID)
|
||||
)
|
||||
assertThat(analyticsService.capturedEvents).containsExactly(
|
||||
roomResult.toAnalyticsJoinedRoom(aTrigger)
|
||||
|
|
@ -88,9 +88,10 @@ class DefaultJoinRoomTest {
|
|||
sut.invoke(A_ROOM_ID, A_SERVER_LIST, aTrigger)
|
||||
joinRoomByIdOrAliasLambda
|
||||
.assertions()
|
||||
.isCalledExactly(1)
|
||||
.withSequence(
|
||||
listOf(value(A_ROOM_ID), value(A_SERVER_LIST))
|
||||
.isCalledOnce()
|
||||
.with(
|
||||
value(A_ROOM_ID),
|
||||
value(A_SERVER_LIST)
|
||||
)
|
||||
joinRoomLambda
|
||||
.assertions()
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ class RoomBeginningPostProcessorTest {
|
|||
fun `processor removes room creation event and self-join event from DM timeline`() {
|
||||
val timelineItems = listOf(
|
||||
MatrixTimelineItem.Event("m.room.create", anEventTimelineItem(sender = A_USER_ID, content = StateContent("", OtherState.RoomCreate))),
|
||||
MatrixTimelineItem.Event("m.room.member", anEventTimelineItem(content = RoomMembershipContent(A_USER_ID, MembershipChange.JOINED))),
|
||||
MatrixTimelineItem.Event("m.room.member", anEventTimelineItem(content = RoomMembershipContent(A_USER_ID, null, MembershipChange.JOINED))),
|
||||
)
|
||||
val processor = RoomBeginningPostProcessor()
|
||||
val processedItems = processor.process(timelineItems, isDm = true, hasMoreToLoadBackwards = false)
|
||||
|
|
@ -44,13 +44,13 @@ class RoomBeginningPostProcessorTest {
|
|||
@Test
|
||||
fun `processor removes room creation event and self-join event from DM timeline even if they're not the first items`() {
|
||||
val timelineItems = listOf(
|
||||
MatrixTimelineItem.Event("m.room.member_other", anEventTimelineItem(content = RoomMembershipContent(A_USER_ID_2, MembershipChange.JOINED))),
|
||||
MatrixTimelineItem.Event("m.room.member_other", anEventTimelineItem(content = RoomMembershipContent(A_USER_ID_2, null, MembershipChange.JOINED))),
|
||||
MatrixTimelineItem.Event("m.room.create", anEventTimelineItem(sender = A_USER_ID, content = StateContent("", OtherState.RoomCreate))),
|
||||
MatrixTimelineItem.Event("m.room.message", anEventTimelineItem(content = aMessageContent("hi"))),
|
||||
MatrixTimelineItem.Event("m.room.member", anEventTimelineItem(content = RoomMembershipContent(A_USER_ID, MembershipChange.JOINED))),
|
||||
MatrixTimelineItem.Event("m.room.member", anEventTimelineItem(content = RoomMembershipContent(A_USER_ID, null, MembershipChange.JOINED))),
|
||||
)
|
||||
val expected = listOf(
|
||||
MatrixTimelineItem.Event("m.room.member_other", anEventTimelineItem(content = RoomMembershipContent(A_USER_ID_2, MembershipChange.JOINED))),
|
||||
MatrixTimelineItem.Event("m.room.member_other", anEventTimelineItem(content = RoomMembershipContent(A_USER_ID_2, null, MembershipChange.JOINED))),
|
||||
MatrixTimelineItem.Event("m.room.message", anEventTimelineItem(content = aMessageContent("hi"))),
|
||||
)
|
||||
val processor = RoomBeginningPostProcessor()
|
||||
|
|
@ -62,7 +62,7 @@ class RoomBeginningPostProcessorTest {
|
|||
fun `processor will add beginning of room item if it's not a DM`() {
|
||||
val timelineItems = listOf(
|
||||
MatrixTimelineItem.Event("m.room.create", anEventTimelineItem(sender = A_USER_ID, content = StateContent("", OtherState.RoomCreate))),
|
||||
MatrixTimelineItem.Event("m.room.member", anEventTimelineItem(content = RoomMembershipContent(A_USER_ID, MembershipChange.JOINED))),
|
||||
MatrixTimelineItem.Event("m.room.member", anEventTimelineItem(content = RoomMembershipContent(A_USER_ID, null, MembershipChange.JOINED))),
|
||||
)
|
||||
val processor = RoomBeginningPostProcessor()
|
||||
val processedItems = processor.process(timelineItems, isDm = false, hasMoreToLoadBackwards = false)
|
||||
|
|
@ -85,7 +85,7 @@ class RoomBeginningPostProcessorTest {
|
|||
fun `processor won't remove items if it's not at the start of the timeline`() {
|
||||
val timelineItems = listOf(
|
||||
MatrixTimelineItem.Event("m.room.create", anEventTimelineItem(sender = A_USER_ID, content = StateContent("", OtherState.RoomCreate))),
|
||||
MatrixTimelineItem.Event("m.room.member", anEventTimelineItem(content = RoomMembershipContent(A_USER_ID, MembershipChange.JOINED))),
|
||||
MatrixTimelineItem.Event("m.room.member", anEventTimelineItem(content = RoomMembershipContent(A_USER_ID, null, MembershipChange.JOINED))),
|
||||
)
|
||||
val processor = RoomBeginningPostProcessor()
|
||||
val processedItems = processor.process(timelineItems, isDm = true, hasMoreToLoadBackwards = true)
|
||||
|
|
@ -95,7 +95,7 @@ class RoomBeginningPostProcessorTest {
|
|||
@Test
|
||||
fun `processor won't remove the first member join event if it can't find the room creation event`() {
|
||||
val timelineItems = listOf(
|
||||
MatrixTimelineItem.Event("m.room.member", anEventTimelineItem(content = RoomMembershipContent(A_USER_ID, MembershipChange.JOINED))),
|
||||
MatrixTimelineItem.Event("m.room.member", anEventTimelineItem(content = RoomMembershipContent(A_USER_ID, null, MembershipChange.JOINED))),
|
||||
)
|
||||
val processor = RoomBeginningPostProcessor()
|
||||
val processedItems = processor.process(timelineItems, isDm = true, hasMoreToLoadBackwards = true)
|
||||
|
|
@ -106,7 +106,7 @@ class RoomBeginningPostProcessorTest {
|
|||
fun `processor won't remove the first member join event if it's not from the room creator`() {
|
||||
val timelineItems = listOf(
|
||||
MatrixTimelineItem.Event("m.room.create", anEventTimelineItem(sender = A_USER_ID, content = StateContent("", OtherState.RoomCreate))),
|
||||
MatrixTimelineItem.Event("m.room.member", anEventTimelineItem(content = RoomMembershipContent(A_USER_ID_2, MembershipChange.JOINED))),
|
||||
MatrixTimelineItem.Event("m.room.member", anEventTimelineItem(content = RoomMembershipContent(A_USER_ID_2, null, MembershipChange.JOINED))),
|
||||
)
|
||||
val processor = RoomBeginningPostProcessor()
|
||||
val processedItems = processor.process(timelineItems, isDm = true, hasMoreToLoadBackwards = true)
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ import io.element.android.libraries.matrix.api.MatrixClient
|
|||
import io.element.android.libraries.matrix.api.core.ProgressCallback
|
||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters
|
||||
|
|
@ -41,7 +40,7 @@ import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults
|
|||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
||||
import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService
|
||||
import io.element.android.libraries.matrix.test.media.FakeMediaLoader
|
||||
import io.element.android.libraries.matrix.test.media.FakeMatrixMediaLoader
|
||||
import io.element.android.libraries.matrix.test.notification.FakeNotificationService
|
||||
import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService
|
||||
import io.element.android.libraries.matrix.test.pushers.FakePushersService
|
||||
|
|
@ -54,7 +53,6 @@ import kotlinx.collections.immutable.ImmutableList
|
|||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
|
|
@ -68,7 +66,7 @@ class FakeMatrixClient(
|
|||
private val userDisplayName: String? = A_USER_NAME,
|
||||
private val userAvatarUrl: String? = AN_AVATAR_URL,
|
||||
override val roomListService: RoomListService = FakeRoomListService(),
|
||||
override val mediaLoader: MatrixMediaLoader = FakeMediaLoader(),
|
||||
override val mediaLoader: MatrixMediaLoader = FakeMatrixMediaLoader(),
|
||||
private val sessionVerificationService: FakeSessionVerificationService = FakeSessionVerificationService(),
|
||||
private val pushersService: FakePushersService = FakePushersService(),
|
||||
private val notificationService: FakeNotificationService = FakeNotificationService(),
|
||||
|
|
@ -78,7 +76,7 @@ class FakeMatrixClient(
|
|||
private val roomDirectoryService: RoomDirectoryService = FakeRoomDirectoryService(),
|
||||
private val accountManagementUrlString: Result<String?> = Result.success(null),
|
||||
private val resolveRoomAliasResult: (RoomAlias) -> Result<ResolvedRoomAlias> = { Result.success(ResolvedRoomAlias(A_ROOM_ID, emptyList())) },
|
||||
private val getRoomPreviewResult: (RoomIdOrAlias) -> Result<RoomPreview> = { Result.failure(AN_EXCEPTION) },
|
||||
private val getRoomPreviewFromRoomIdResult: (RoomId, List<String>) -> Result<RoomPreview> = { _, _ -> Result.failure(AN_EXCEPTION) },
|
||||
) : MatrixClient {
|
||||
var setDisplayNameCalled: Boolean = false
|
||||
private set
|
||||
|
|
@ -96,7 +94,6 @@ class FakeMatrixClient(
|
|||
private var createRoomResult: Result<RoomId> = Result.success(A_ROOM_ID)
|
||||
private var createDmResult: Result<RoomId> = Result.success(A_ROOM_ID)
|
||||
private var findDmResult: RoomId? = A_ROOM_ID
|
||||
private var logoutFailure: Throwable? = null
|
||||
private val getRoomResults = mutableMapOf<RoomId, MatrixRoom>()
|
||||
private val searchUserResults = mutableMapOf<String, Result<MatrixSearchUserResults>>()
|
||||
private val getProfileResults = mutableMapOf<UserId, Result<MatrixUser>>()
|
||||
|
|
@ -116,6 +113,9 @@ class FakeMatrixClient(
|
|||
var getRoomInfoFlowLambda = { _: RoomId ->
|
||||
flowOf<Optional<MatrixRoomInfo>>(Optional.empty())
|
||||
}
|
||||
var logoutLambda: (Boolean) -> String? = {
|
||||
null
|
||||
}
|
||||
|
||||
override suspend fun getRoom(roomId: RoomId): MatrixRoom? {
|
||||
return getRoomResults[roomId]
|
||||
|
|
@ -160,12 +160,8 @@ class FakeMatrixClient(
|
|||
override suspend fun clearCache() {
|
||||
}
|
||||
|
||||
override suspend fun logout(ignoreSdkError: Boolean): String? {
|
||||
delay(100)
|
||||
if (ignoreSdkError.not()) {
|
||||
logoutFailure?.let { throw it }
|
||||
}
|
||||
return null
|
||||
override suspend fun logout(ignoreSdkError: Boolean): String? = simulateLongTask {
|
||||
return logoutLambda(ignoreSdkError)
|
||||
}
|
||||
|
||||
override fun close() = Unit
|
||||
|
|
@ -229,10 +225,6 @@ class FakeMatrixClient(
|
|||
|
||||
// Mocks
|
||||
|
||||
fun givenLogoutError(failure: Throwable?) {
|
||||
logoutFailure = failure
|
||||
}
|
||||
|
||||
fun givenCreateRoomResult(result: Result<RoomId>) {
|
||||
createRoomResult = result
|
||||
}
|
||||
|
|
@ -297,8 +289,8 @@ class FakeMatrixClient(
|
|||
resolveRoomAliasResult(roomAlias)
|
||||
}
|
||||
|
||||
override suspend fun getRoomPreview(roomIdOrAlias: RoomIdOrAlias): Result<RoomPreview> = simulateLongTask {
|
||||
getRoomPreviewResult(roomIdOrAlias)
|
||||
override suspend fun getRoomPreviewFromRoomId(roomId: RoomId, serverNames: List<String>) = simulateLongTask {
|
||||
getRoomPreviewFromRoomIdResult(roomId, serverNames)
|
||||
}
|
||||
|
||||
override suspend fun getRecentlyVisitedRooms(): Result<List<RoomId>> {
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import io.element.android.libraries.matrix.api.room.RoomNotificationSettings
|
|||
|
||||
const val A_USER_NAME = "alice"
|
||||
const val A_PASSWORD = "password"
|
||||
const val A_SECRET = "secret"
|
||||
|
||||
val A_USER_ID = UserId("@alice:server.org")
|
||||
val A_USER_ID_2 = UserId("@bob:server.org")
|
||||
|
|
|
|||
|
|
@ -31,7 +31,9 @@ import kotlinx.coroutines.flow.flowOf
|
|||
|
||||
val A_OIDC_DATA = OidcDetails(url = "a-url")
|
||||
|
||||
class FakeAuthenticationService : MatrixAuthenticationService {
|
||||
class FakeMatrixAuthenticationService(
|
||||
private val matrixClientResult: ((SessionId) -> Result<MatrixClient>)? = null
|
||||
) : MatrixAuthenticationService {
|
||||
private val homeserver = MutableStateFlow<MatrixHomeServerDetails?>(null)
|
||||
private var oidcError: Throwable? = null
|
||||
private var oidcCancelError: Throwable? = null
|
||||
|
|
@ -39,15 +41,18 @@ class FakeAuthenticationService : MatrixAuthenticationService {
|
|||
private var changeServerError: Throwable? = null
|
||||
private var matrixClient: MatrixClient? = null
|
||||
|
||||
var getLatestSessionIdLambda: (() -> SessionId?) = { null }
|
||||
|
||||
override fun loggedInStateFlow(): Flow<LoggedInState> {
|
||||
return flowOf(LoggedInState.NotLoggedIn)
|
||||
}
|
||||
|
||||
override suspend fun getLatestSessionId(): SessionId? {
|
||||
return null
|
||||
}
|
||||
override suspend fun getLatestSessionId(): SessionId? = getLatestSessionIdLambda()
|
||||
|
||||
override suspend fun restoreSession(sessionId: SessionId): Result<MatrixClient> {
|
||||
if (matrixClientResult != null) {
|
||||
return matrixClientResult.invoke(sessionId)
|
||||
}
|
||||
return if (matrixClient != null) {
|
||||
Result.success(matrixClient!!)
|
||||
} else {
|
||||
|
|
@ -28,7 +28,7 @@ fun aBuildMeta(
|
|||
applicationId: String = "",
|
||||
lowPrivacyLoggingEnabled: Boolean = true,
|
||||
versionName: String = "",
|
||||
versionCode: Int = 0,
|
||||
versionCode: Long = 0,
|
||||
gitRevision: String = "",
|
||||
gitBranchName: String = "",
|
||||
flavorDescription: String = "",
|
||||
|
|
|
|||
|
|
@ -39,6 +39,9 @@ class FakeEncryptionService : EncryptionService {
|
|||
|
||||
private var enableBackupsFailure: Exception? = null
|
||||
|
||||
private var curve25519: String? = null
|
||||
private var ed25519: String? = null
|
||||
|
||||
fun givenEnableBackupsFailure(exception: Exception?) {
|
||||
enableBackupsFailure = exception
|
||||
}
|
||||
|
|
@ -94,6 +97,15 @@ class FakeEncryptionService : EncryptionService {
|
|||
return waitForBackupUploadSteadyStateFlow
|
||||
}
|
||||
|
||||
fun givenDeviceKeys(curve25519: String?, ed25519: String?) {
|
||||
this.curve25519 = curve25519
|
||||
this.ed25519 = ed25519
|
||||
}
|
||||
|
||||
override suspend fun deviceCurve25519(): String? = curve25519
|
||||
|
||||
override suspend fun deviceEd25519(): String? = ed25519
|
||||
|
||||
suspend fun emitBackupState(state: BackupState) {
|
||||
backupStateStateFlow.emit(state)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import io.element.android.libraries.matrix.api.media.MediaFile
|
|||
import io.element.android.libraries.matrix.api.media.MediaSource
|
||||
import io.element.android.tests.testutils.simulateLongTask
|
||||
|
||||
class FakeMediaLoader : MatrixMediaLoader {
|
||||
class FakeMatrixMediaLoader : MatrixMediaLoader {
|
||||
var shouldFail = false
|
||||
var path: String = ""
|
||||
|
||||
|
|
@ -20,9 +20,9 @@ import io.element.android.libraries.matrix.api.core.UserId
|
|||
import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder
|
||||
|
||||
class FakePermalinkBuilder(
|
||||
private val result: () -> Result<String> = { Result.failure(Exception("Not implemented")) }
|
||||
private val result: (UserId) -> Result<String> = { Result.failure(Exception("Not implemented")) }
|
||||
) : PermalinkBuilder {
|
||||
override fun permalinkForUser(userId: UserId): Result<String> {
|
||||
return result()
|
||||
return result(userId)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,9 +18,10 @@ package io.element.android.libraries.matrix.test.permalink
|
|||
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkData
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkParser
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
|
||||
class FakePermalinkParser(
|
||||
private var result: () -> PermalinkData = { TODO("Not implemented") }
|
||||
private var result: () -> PermalinkData = { lambdaError() }
|
||||
) : PermalinkParser {
|
||||
fun givenResult(result: PermalinkData) {
|
||||
this.result = { result }
|
||||
|
|
|
|||
|
|
@ -18,8 +18,13 @@ package io.element.android.libraries.matrix.test.pushers
|
|||
|
||||
import io.element.android.libraries.matrix.api.pusher.PushersService
|
||||
import io.element.android.libraries.matrix.api.pusher.SetHttpPusherData
|
||||
import io.element.android.libraries.matrix.api.pusher.UnsetHttpPusherData
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
|
||||
class FakePushersService : PushersService {
|
||||
override suspend fun setHttpPusher(setHttpPusherData: SetHttpPusherData) = Result.success(Unit)
|
||||
override suspend fun unsetHttpPusher(): Result<Unit> = Result.success(Unit)
|
||||
class FakePushersService(
|
||||
private val setHttpPusherResult: (SetHttpPusherData) -> Result<Unit> = { lambdaError() },
|
||||
private val unsetHttpPusherResult: (UnsetHttpPusherData) -> Result<Unit> = { lambdaError() },
|
||||
) : PushersService {
|
||||
override suspend fun setHttpPusher(setHttpPusherData: SetHttpPusherData) = setHttpPusherResult(setHttpPusherData)
|
||||
override suspend fun unsetHttpPusher(unsetHttpPusherData: UnsetHttpPusherData): Result<Unit> = unsetHttpPusherResult(unsetHttpPusherData)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ import io.element.android.libraries.matrix.test.A_SESSION_ID
|
|||
import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler
|
||||
import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService
|
||||
import io.element.android.libraries.matrix.test.timeline.FakeTimeline
|
||||
import io.element.android.libraries.matrix.test.widget.FakeWidgetDriver
|
||||
import io.element.android.libraries.matrix.test.widget.FakeMatrixWidgetDriver
|
||||
import io.element.android.tests.testutils.simulateLongTask
|
||||
import kotlinx.collections.immutable.ImmutableMap
|
||||
import kotlinx.collections.immutable.persistentMapOf
|
||||
|
|
@ -125,7 +125,7 @@ class FakeMatrixRoom(
|
|||
private var endPollResult = Result.success(Unit)
|
||||
private var progressCallbackValues = emptyList<Pair<Long, Long>>()
|
||||
private var generateWidgetWebViewUrlResult = Result.success("https://call.element.io")
|
||||
private var getWidgetDriverResult: Result<MatrixWidgetDriver> = Result.success(FakeWidgetDriver())
|
||||
private var getWidgetDriverResult: Result<MatrixWidgetDriver> = Result.success(FakeMatrixWidgetDriver())
|
||||
private var canUserTriggerRoomNotificationResult: Result<Boolean> = Result.success(true)
|
||||
private var canUserJoinCallResult: Result<Boolean> = Result.success(true)
|
||||
private var setIsFavoriteResult = Result.success(Unit)
|
||||
|
|
|
|||
|
|
@ -25,17 +25,14 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
class FakeSessionVerificationService : SessionVerificationService {
|
||||
private val _isReady = MutableStateFlow(false)
|
||||
private val _sessionVerifiedStatus = MutableStateFlow<SessionVerifiedStatus>(SessionVerifiedStatus.Unknown)
|
||||
private var _verificationFlowState = MutableStateFlow<VerificationFlowState>(VerificationFlowState.Initial)
|
||||
private var _canVerifySessionFlow = MutableStateFlow(true)
|
||||
private var _needsSessionVerification = MutableStateFlow(true)
|
||||
var shouldFail = false
|
||||
|
||||
override val verificationFlowState: StateFlow<VerificationFlowState> = _verificationFlowState
|
||||
override val sessionVerifiedStatus: StateFlow<SessionVerifiedStatus> = _sessionVerifiedStatus
|
||||
override val canVerifySessionFlow: Flow<Boolean> = _canVerifySessionFlow
|
||||
|
||||
override val isReady: StateFlow<Boolean> = _isReady
|
||||
override val needsSessionVerification: Flow<Boolean> = _needsSessionVerification
|
||||
|
||||
override suspend fun requestVerification() {
|
||||
if (!shouldFail) {
|
||||
|
|
@ -85,12 +82,8 @@ class FakeSessionVerificationService : SessionVerificationService {
|
|||
_verificationFlowState.value = state
|
||||
}
|
||||
|
||||
fun givenCanVerifySession(canVerify: Boolean) {
|
||||
_canVerifySessionFlow.value = canVerify
|
||||
}
|
||||
|
||||
fun givenIsReady(value: Boolean) {
|
||||
_isReady.value = value
|
||||
fun givenNeedsSessionVerification(needsVerification: Boolean) {
|
||||
_needsSessionVerification.value = needsVerification
|
||||
}
|
||||
|
||||
override suspend fun reset() {
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import io.element.android.libraries.matrix.api.widget.MatrixWidgetDriver
|
|||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import java.util.UUID
|
||||
|
||||
class FakeWidgetDriver(
|
||||
class FakeMatrixWidgetDriver(
|
||||
override val id: String = UUID.randomUUID().toString(),
|
||||
) : MatrixWidgetDriver {
|
||||
private val _sentMessages = mutableListOf<String>()
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_invites_invited_you">"%1$s (%2$s) მოგიწვიათ"</string>
|
||||
</resources>
|
||||
|
|
@ -25,7 +25,7 @@ import app.cash.turbine.test
|
|||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
||||
import io.element.android.libraries.matrix.test.media.FakeMediaLoader
|
||||
import io.element.android.libraries.matrix.test.media.FakeMatrixMediaLoader
|
||||
import io.element.android.libraries.matrix.test.media.aMediaSource
|
||||
import io.element.android.libraries.mediaviewer.api.local.anApkMediaInfo
|
||||
import io.element.android.libraries.mediaviewer.api.viewer.MediaViewerEvents
|
||||
|
|
@ -51,9 +51,9 @@ class MediaViewerPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - download media success scenario`() = runTest {
|
||||
val mediaLoader = FakeMediaLoader()
|
||||
val matrixMediaLoader = FakeMatrixMediaLoader()
|
||||
val mediaActions = FakeLocalMediaActions()
|
||||
val presenter = createMediaViewerPresenter(mediaLoader, mediaActions)
|
||||
val presenter = createMediaViewerPresenter(matrixMediaLoader, mediaActions)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
|
|
@ -71,10 +71,10 @@ class MediaViewerPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - check all actions `() = runTest {
|
||||
val mediaLoader = FakeMediaLoader()
|
||||
val matrixMediaLoader = FakeMatrixMediaLoader()
|
||||
val mediaActions = FakeLocalMediaActions()
|
||||
val snackbarDispatcher = SnackbarDispatcher()
|
||||
val presenter = createMediaViewerPresenter(mediaLoader, mediaActions, snackbarDispatcher)
|
||||
val presenter = createMediaViewerPresenter(matrixMediaLoader, mediaActions, snackbarDispatcher)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
|
|
@ -118,13 +118,13 @@ class MediaViewerPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - download media failure then retry with success scenario`() = runTest {
|
||||
val mediaLoader = FakeMediaLoader()
|
||||
val matrixMediaLoader = FakeMatrixMediaLoader()
|
||||
val mediaActions = FakeLocalMediaActions()
|
||||
val presenter = createMediaViewerPresenter(mediaLoader, mediaActions)
|
||||
val presenter = createMediaViewerPresenter(matrixMediaLoader, mediaActions)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
mediaLoader.shouldFail = true
|
||||
matrixMediaLoader.shouldFail = true
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.downloadedMedia).isEqualTo(AsyncData.Uninitialized)
|
||||
assertThat(initialState.mediaInfo).isEqualTo(TESTED_MEDIA_INFO)
|
||||
|
|
@ -132,7 +132,7 @@ class MediaViewerPresenterTest {
|
|||
assertThat(loadingState.downloadedMedia).isInstanceOf(AsyncData.Loading::class.java)
|
||||
val failureState = awaitItem()
|
||||
assertThat(failureState.downloadedMedia).isInstanceOf(AsyncData.Failure::class.java)
|
||||
mediaLoader.shouldFail = false
|
||||
matrixMediaLoader.shouldFail = false
|
||||
failureState.eventSink(MediaViewerEvents.RetryLoading)
|
||||
// There is one recomposition because of the retry mechanism
|
||||
skipItems(1)
|
||||
|
|
@ -146,7 +146,7 @@ class MediaViewerPresenterTest {
|
|||
}
|
||||
|
||||
private fun createMediaViewerPresenter(
|
||||
mediaLoader: FakeMediaLoader,
|
||||
matrixMediaLoader: FakeMatrixMediaLoader,
|
||||
localMediaActions: FakeLocalMediaActions,
|
||||
snackbarDispatcher: SnackbarDispatcher = SnackbarDispatcher(),
|
||||
canShare: Boolean = true,
|
||||
|
|
@ -161,7 +161,7 @@ class MediaViewerPresenterTest {
|
|||
canDownload = canDownload,
|
||||
),
|
||||
localMediaFactory = localMediaFactory,
|
||||
mediaLoader = mediaLoader,
|
||||
mediaLoader = matrixMediaLoader,
|
||||
localMediaActions = localMediaActions,
|
||||
snackbarDispatcher = snackbarDispatcher,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="dialog_permission_camera">"იმისათვის, რომ აპლიკაციამ გამოიყენოს კამერა, გთხოვთ, მიანიჭოთ ნებართვა სისტემის პარამეტრებში."</string>
|
||||
<string name="dialog_permission_generic">"გთხოვთ, მიანიჭოთ ნებართვა სისტემის პარამეტრებში."</string>
|
||||
<string name="dialog_permission_microphone">"იმისათვის, რომ აპლიკაციამ მიკროფონი გამოიყენოს, გთხოვთ, მიანიჭოთ ნებართვა სისტემის პარამეტრებში."</string>
|
||||
<string name="dialog_permission_notification">"იმისათვის, რომ აპლიკაციამ გამოაჩინოს შეტყობინებები, გთხოვთ, მიანიჭოთ ნებართვა სისტემის პარამეტრებში."</string>
|
||||
</resources>
|
||||
|
|
@ -19,9 +19,6 @@ package io.element.android.features.preferences.api.store
|
|||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface AppPreferencesStore {
|
||||
suspend fun setRichTextEditorEnabled(enabled: Boolean)
|
||||
fun isRichTextEditorEnabledFlow(): Flow<Boolean>
|
||||
|
||||
suspend fun setDeveloperModeEnabled(enabled: Boolean)
|
||||
fun isDeveloperModeEnabledFlow(): Flow<Boolean>
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ import androidx.datastore.preferences.core.stringPreferencesKey
|
|||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.features.preferences.api.store.AppPreferencesStore
|
||||
import io.element.android.libraries.core.bool.orTrue
|
||||
import io.element.android.libraries.core.meta.BuildMeta
|
||||
import io.element.android.libraries.core.meta.BuildType
|
||||
import io.element.android.libraries.di.AppScope
|
||||
|
|
@ -36,7 +35,6 @@ import javax.inject.Inject
|
|||
|
||||
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "elementx_preferences")
|
||||
|
||||
private val richTextEditorKey = booleanPreferencesKey("richTextEditor")
|
||||
private val developerModeKey = booleanPreferencesKey("developerMode")
|
||||
private val customElementCallBaseUrlKey = stringPreferencesKey("elementCallBaseUrl")
|
||||
private val themeKey = stringPreferencesKey("theme")
|
||||
|
|
@ -48,19 +46,6 @@ class DefaultAppPreferencesStore @Inject constructor(
|
|||
) : AppPreferencesStore {
|
||||
private val store = context.dataStore
|
||||
|
||||
override suspend fun setRichTextEditorEnabled(enabled: Boolean) {
|
||||
store.edit { prefs ->
|
||||
prefs[richTextEditorKey] = enabled
|
||||
}
|
||||
}
|
||||
|
||||
override fun isRichTextEditorEnabledFlow(): Flow<Boolean> {
|
||||
return store.data.map { prefs ->
|
||||
// enabled by default
|
||||
prefs[richTextEditorKey].orTrue()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun setDeveloperModeEnabled(enabled: Boolean) {
|
||||
store.edit { prefs ->
|
||||
prefs[developerModeKey] = enabled
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import io.element.android.tests.testutils.lambda.LambdaTwoParamsRecorder
|
|||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
||||
class FakeSessionPreferenceStoreFactory(
|
||||
class FakeSessionPreferencesStoreFactory(
|
||||
var getLambda: LambdaTwoParamsRecorder<SessionId, CoroutineScope, SessionPreferencesStore> = lambdaRecorder { _, _ -> throw NotImplementedError() },
|
||||
var removeLambda: LambdaOneParamRecorder<SessionId, Unit> = lambdaRecorder { _ -> },
|
||||
) : SessionPreferencesStoreFactory {
|
||||
|
|
@ -21,24 +21,14 @@ import kotlinx.coroutines.flow.Flow
|
|||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
class InMemoryAppPreferencesStore(
|
||||
isRichTextEditorEnabled: Boolean = false,
|
||||
isDeveloperModeEnabled: Boolean = false,
|
||||
customElementCallBaseUrl: String? = null,
|
||||
theme: String? = null,
|
||||
) : AppPreferencesStore {
|
||||
private val isRichTextEditorEnabled = MutableStateFlow(isRichTextEditorEnabled)
|
||||
private val isDeveloperModeEnabled = MutableStateFlow(isDeveloperModeEnabled)
|
||||
private val customElementCallBaseUrl = MutableStateFlow(customElementCallBaseUrl)
|
||||
private val theme = MutableStateFlow(theme)
|
||||
|
||||
override suspend fun setRichTextEditorEnabled(enabled: Boolean) {
|
||||
isRichTextEditorEnabled.value = enabled
|
||||
}
|
||||
|
||||
override fun isRichTextEditorEnabledFlow(): Flow<Boolean> {
|
||||
return isRichTextEditorEnabled
|
||||
}
|
||||
|
||||
override suspend fun setDeveloperModeEnabled(enabled: Boolean) {
|
||||
isDeveloperModeEnabled.value = enabled
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,8 +21,10 @@ import io.element.android.libraries.pushproviders.api.Distributor
|
|||
import io.element.android.libraries.pushproviders.api.PushProvider
|
||||
|
||||
interface PushService {
|
||||
// TODO Move away
|
||||
fun notificationStyleChanged()
|
||||
/**
|
||||
* Return the current push provider, or null if none.
|
||||
*/
|
||||
suspend fun getCurrentPushProvider(): PushProvider?
|
||||
|
||||
/**
|
||||
* Return the list of push providers, available at compile time, and
|
||||
|
|
@ -35,7 +37,11 @@ interface PushService {
|
|||
*
|
||||
* The method has effect only if the [PushProvider] is different than the current one.
|
||||
*/
|
||||
suspend fun registerWith(matrixClient: MatrixClient, pushProvider: PushProvider, distributor: Distributor)
|
||||
suspend fun registerWith(
|
||||
matrixClient: MatrixClient,
|
||||
pushProvider: PushProvider,
|
||||
distributor: Distributor,
|
||||
): Result<Unit>
|
||||
|
||||
/**
|
||||
* Return false in case of early error.
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ dependencies {
|
|||
testImplementation(projects.libraries.matrix.test)
|
||||
testImplementation(projects.libraries.push.test)
|
||||
testImplementation(projects.libraries.pushproviders.test)
|
||||
testImplementation(projects.libraries.pushstore.test)
|
||||
testImplementation(projects.tests.testutils)
|
||||
testImplementation(projects.services.appnavstate.test)
|
||||
testImplementation(projects.services.toolbox.impl)
|
||||
|
|
|
|||
|
|
@ -21,22 +21,23 @@ import io.element.android.libraries.di.AppScope
|
|||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.push.api.GetCurrentPushProvider
|
||||
import io.element.android.libraries.push.api.PushService
|
||||
import io.element.android.libraries.push.impl.notifications.DefaultNotificationDrawerManager
|
||||
import io.element.android.libraries.push.impl.test.TestPush
|
||||
import io.element.android.libraries.pushproviders.api.Distributor
|
||||
import io.element.android.libraries.pushproviders.api.PushProvider
|
||||
import io.element.android.libraries.pushstore.api.UserPushStoreFactory
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultPushService @Inject constructor(
|
||||
private val defaultNotificationDrawerManager: DefaultNotificationDrawerManager,
|
||||
private val pushersManager: PushersManager,
|
||||
private val testPush: TestPush,
|
||||
private val userPushStoreFactory: UserPushStoreFactory,
|
||||
private val pushProviders: Set<@JvmSuppressWildcards PushProvider>,
|
||||
private val getCurrentPushProvider: GetCurrentPushProvider,
|
||||
) : PushService {
|
||||
override fun notificationStyleChanged() {
|
||||
defaultNotificationDrawerManager.notificationStyleChanged()
|
||||
override suspend fun getCurrentPushProvider(): PushProvider? {
|
||||
val currentPushProvider = getCurrentPushProvider.getCurrentPushProvider()
|
||||
return pushProviders.find { it.name == currentPushProvider }
|
||||
}
|
||||
|
||||
override fun getAvailablePushProviders(): List<PushProvider> {
|
||||
|
|
@ -45,26 +46,36 @@ class DefaultPushService @Inject constructor(
|
|||
.sortedBy { it.index }
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current push provider, compare with provided one, then unregister and register if different, and store change.
|
||||
*/
|
||||
override suspend fun registerWith(matrixClient: MatrixClient, pushProvider: PushProvider, distributor: Distributor) {
|
||||
override suspend fun registerWith(
|
||||
matrixClient: MatrixClient,
|
||||
pushProvider: PushProvider,
|
||||
distributor: Distributor,
|
||||
): Result<Unit> {
|
||||
Timber.d("Registering with ${pushProvider.name}/${distributor.name}}")
|
||||
val userPushStore = userPushStoreFactory.getOrCreate(matrixClient.sessionId)
|
||||
val currentPushProviderName = userPushStore.getPushProviderName()
|
||||
if (currentPushProviderName != pushProvider.name) {
|
||||
val currentPushProvider = pushProviders.find { it.name == currentPushProviderName }
|
||||
val currentDistributorValue = currentPushProvider?.getCurrentDistributor(matrixClient)?.value
|
||||
if (currentPushProviderName != pushProvider.name || currentDistributorValue != distributor.value) {
|
||||
// Unregister previous one if any
|
||||
pushProviders.find { it.name == currentPushProviderName }?.unregister(matrixClient)
|
||||
currentPushProvider
|
||||
?.also { Timber.d("Unregistering previous push provider $currentPushProviderName/$currentDistributorValue") }
|
||||
?.unregister(matrixClient)
|
||||
?.onFailure {
|
||||
Timber.w(it, "Failed to unregister previous push provider")
|
||||
return Result.failure(it)
|
||||
}
|
||||
}
|
||||
pushProvider.registerWith(matrixClient, distributor)
|
||||
// Store new value
|
||||
userPushStore.setPushProviderName(pushProvider.name)
|
||||
// Then try to register
|
||||
return pushProvider.registerWith(matrixClient, distributor)
|
||||
}
|
||||
|
||||
override suspend fun testPush(): Boolean {
|
||||
val currentPushProvider = getCurrentPushProvider.getCurrentPushProvider()
|
||||
val pushProvider = pushProviders.find { it.name == currentPushProvider } ?: return false
|
||||
val pushProvider = getCurrentPushProvider() ?: return false
|
||||
val config = pushProvider.getCurrentUserPushConfig() ?: return false
|
||||
pushersManager.testPush(config)
|
||||
testPush.execute(config)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,12 +22,9 @@ import io.element.android.libraries.core.log.logger.LoggerTag
|
|||
import io.element.android.libraries.core.meta.BuildMeta
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
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.pusher.SetHttpPusherData
|
||||
import io.element.android.libraries.push.impl.pushgateway.PushGatewayNotifyRequest
|
||||
import io.element.android.libraries.pushproviders.api.CurrentUserPushConfig
|
||||
import io.element.android.libraries.matrix.api.pusher.UnsetHttpPusherData
|
||||
import io.element.android.libraries.pushproviders.api.PusherSubscriber
|
||||
import io.element.android.libraries.pushstore.api.UserPushStoreFactory
|
||||
import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret
|
||||
|
|
@ -36,48 +33,37 @@ import javax.inject.Inject
|
|||
|
||||
internal const val DEFAULT_PUSHER_FILE_TAG = "mobile"
|
||||
|
||||
private val loggerTag = LoggerTag("PushersManager", LoggerTag.PushLoggerTag)
|
||||
private val loggerTag = LoggerTag("DefaultPusherSubscriber", LoggerTag.PushLoggerTag)
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class PushersManager @Inject constructor(
|
||||
// private val localeProvider: LocaleProvider,
|
||||
class DefaultPusherSubscriber @Inject constructor(
|
||||
private val buildMeta: BuildMeta,
|
||||
// private val getDeviceInfoUseCase: GetDeviceInfoUseCase,
|
||||
private val pushGatewayNotifyRequest: PushGatewayNotifyRequest,
|
||||
private val pushClientSecret: PushClientSecret,
|
||||
private val userPushStoreFactory: UserPushStoreFactory,
|
||||
) : PusherSubscriber {
|
||||
suspend fun testPush(config: CurrentUserPushConfig) {
|
||||
pushGatewayNotifyRequest.execute(
|
||||
PushGatewayNotifyRequest.Params(
|
||||
url = config.url,
|
||||
appId = PushConfig.PUSHER_APP_ID,
|
||||
pushKey = config.pushKey,
|
||||
eventId = TEST_EVENT_ID,
|
||||
roomId = TEST_ROOM_ID,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a pusher to the server if not done yet.
|
||||
*/
|
||||
override suspend fun registerPusher(matrixClient: MatrixClient, pushKey: String, gateway: String) {
|
||||
override suspend fun registerPusher(
|
||||
matrixClient: MatrixClient,
|
||||
pushKey: String,
|
||||
gateway: String,
|
||||
): Result<Unit> {
|
||||
val userDataStore = userPushStoreFactory.getOrCreate(matrixClient.sessionId)
|
||||
if (userDataStore.getCurrentRegisteredPushKey() == pushKey) {
|
||||
Timber.tag(loggerTag.value)
|
||||
.d("Unnecessary to register again the same pusher, but do it in case the pusher has been removed from the server")
|
||||
}
|
||||
matrixClient.pushersService().setHttpPusher(
|
||||
createHttpPusher(pushKey, gateway, matrixClient.sessionId)
|
||||
).fold(
|
||||
{
|
||||
return matrixClient.pushersService()
|
||||
.setHttpPusher(
|
||||
createHttpPusher(pushKey, gateway, matrixClient.sessionId)
|
||||
)
|
||||
.onSuccess {
|
||||
userDataStore.setCurrentRegisteredPushKey(pushKey)
|
||||
},
|
||||
{ throwable ->
|
||||
}
|
||||
.onFailure { throwable ->
|
||||
Timber.tag(loggerTag.value).e(throwable, "Unable to register the pusher")
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun createHttpPusher(
|
||||
|
|
@ -106,12 +92,24 @@ class PushersManager @Inject constructor(
|
|||
return "{\"cs\":\"$secretForUser\"}"
|
||||
}
|
||||
|
||||
override suspend fun unregisterPusher(matrixClient: MatrixClient, pushKey: String, gateway: String) {
|
||||
matrixClient.pushersService().unsetHttpPusher()
|
||||
}
|
||||
|
||||
companion object {
|
||||
val TEST_EVENT_ID = EventId("\$THIS_IS_A_FAKE_EVENT_ID")
|
||||
val TEST_ROOM_ID = RoomId("!room:domain")
|
||||
override suspend fun unregisterPusher(
|
||||
matrixClient: MatrixClient,
|
||||
pushKey: String,
|
||||
gateway: String,
|
||||
): Result<Unit> {
|
||||
val userDataStore = userPushStoreFactory.getOrCreate(matrixClient.sessionId)
|
||||
return matrixClient.pushersService()
|
||||
.unsetHttpPusher(
|
||||
unsetHttpPusherData = UnsetHttpPusherData(
|
||||
pushKey = pushKey,
|
||||
appId = PushConfig.PUSHER_APP_ID
|
||||
)
|
||||
)
|
||||
.onSuccess {
|
||||
userDataStore.setCurrentRegisteredPushKey(null)
|
||||
}
|
||||
.onFailure { throwable ->
|
||||
Timber.tag(loggerTag.value).e(throwable, "Unable to unregister the pusher")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -19,7 +19,9 @@ package io.element.android.libraries.push.impl.notifications
|
|||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.core.content.FileProvider
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.core.log.logger.LoggerTag
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.di.ApplicationContext
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.MatrixClientProvider
|
||||
|
|
@ -55,7 +57,7 @@ import io.element.android.services.toolbox.api.systemclock.SystemClock
|
|||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
private val loggerTag = LoggerTag("NotifiableEventResolver", LoggerTag.NotificationLoggerTag)
|
||||
private val loggerTag = LoggerTag("DefaultNotifiableEventResolver", LoggerTag.NotificationLoggerTag)
|
||||
|
||||
/**
|
||||
* The notifiable event resolver is able to create a NotifiableEvent (view model for notifications) from an sdk Event.
|
||||
|
|
@ -63,15 +65,20 @@ private val loggerTag = LoggerTag("NotifiableEventResolver", LoggerTag.Notificat
|
|||
* The NotifiableEventResolver is the only aware of session/store, the NotificationDrawerManager has no knowledge of that,
|
||||
* this pattern allow decoupling between the object responsible of displaying notifications and the matrix sdk.
|
||||
*/
|
||||
class NotifiableEventResolver @Inject constructor(
|
||||
interface NotifiableEventResolver {
|
||||
suspend fun resolveEvent(sessionId: SessionId, roomId: RoomId, eventId: EventId): NotifiableEvent?
|
||||
}
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultNotifiableEventResolver @Inject constructor(
|
||||
private val stringProvider: StringProvider,
|
||||
private val clock: SystemClock,
|
||||
private val matrixClientProvider: MatrixClientProvider,
|
||||
private val notificationMediaRepoFactory: NotificationMediaRepo.Factory,
|
||||
@ApplicationContext private val context: Context,
|
||||
private val permalinkParser: PermalinkParser,
|
||||
) {
|
||||
suspend fun resolveEvent(sessionId: SessionId, roomId: RoomId, eventId: EventId): NotifiableEvent? {
|
||||
) : NotifiableEventResolver {
|
||||
override suspend fun resolveEvent(sessionId: SessionId, roomId: RoomId, eventId: EventId): NotifiableEvent? {
|
||||
// Restore session
|
||||
val client = matrixClientProvider.getOrRestore(sessionId).getOrNull() ?: return null
|
||||
val notificationService = client.notificationService()
|
||||
|
|
@ -221,18 +221,6 @@ class DefaultNotificationDrawerManager @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
// TODO EAx Must be per account
|
||||
fun notificationStyleChanged() {
|
||||
updateEvents(doRender = true) {
|
||||
val newSettings = true // pushDataStore.useCompleteNotificationFormat()
|
||||
if (newSettings != useCompleteNotificationFormat) {
|
||||
// Settings has changed, remove all current notifications
|
||||
notificationRenderer.cancelAllNotifications()
|
||||
useCompleteNotificationFormat = newSettings
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateEvents(
|
||||
doRender: Boolean,
|
||||
action: (NotificationEventQueue) -> Unit,
|
||||
|
|
|
|||
|
|
@ -16,27 +16,19 @@
|
|||
|
||||
package io.element.android.libraries.push.impl.push
|
||||
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.core.log.logger.LoggerTag
|
||||
import io.element.android.libraries.core.meta.BuildMeta
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
||||
import io.element.android.libraries.push.impl.PushersManager
|
||||
import io.element.android.libraries.push.impl.notifications.DefaultNotificationDrawerManager
|
||||
import io.element.android.libraries.push.impl.notifications.NotifiableEventResolver
|
||||
import io.element.android.libraries.push.impl.store.DefaultPushDataStore
|
||||
import io.element.android.libraries.push.impl.test.DefaultTestPush
|
||||
import io.element.android.libraries.push.impl.troubleshoot.DiagnosticPushHandler
|
||||
import io.element.android.libraries.pushproviders.api.PushData
|
||||
import io.element.android.libraries.pushproviders.api.PushHandler
|
||||
import io.element.android.libraries.pushstore.api.UserPushStoreFactory
|
||||
import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
@ -44,23 +36,15 @@ private val loggerTag = LoggerTag("PushHandler", LoggerTag.PushLoggerTag)
|
|||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultPushHandler @Inject constructor(
|
||||
private val defaultNotificationDrawerManager: DefaultNotificationDrawerManager,
|
||||
private val onNotifiableEventReceived: OnNotifiableEventReceived,
|
||||
private val notifiableEventResolver: NotifiableEventResolver,
|
||||
private val defaultPushDataStore: DefaultPushDataStore,
|
||||
private val incrementPushDataStore: IncrementPushDataStore,
|
||||
private val userPushStoreFactory: UserPushStoreFactory,
|
||||
private val pushClientSecret: PushClientSecret,
|
||||
// private val actionIds: NotificationActionIds,
|
||||
private val buildMeta: BuildMeta,
|
||||
private val matrixAuthenticationService: MatrixAuthenticationService,
|
||||
private val diagnosticPushHandler: DiagnosticPushHandler,
|
||||
) : PushHandler {
|
||||
private val coroutineScope = CoroutineScope(SupervisorJob())
|
||||
|
||||
// UI handler
|
||||
private val uiHandler by lazy {
|
||||
Handler(Looper.getMainLooper())
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when message is received.
|
||||
*
|
||||
|
|
@ -68,21 +52,15 @@ class DefaultPushHandler @Inject constructor(
|
|||
*/
|
||||
override suspend fun handle(pushData: PushData) {
|
||||
Timber.tag(loggerTag.value).d("## handling pushData: ${pushData.roomId}/${pushData.eventId}")
|
||||
|
||||
if (buildMeta.lowPrivacyLoggingEnabled) {
|
||||
Timber.tag(loggerTag.value).d("## pushData: $pushData")
|
||||
}
|
||||
|
||||
defaultPushDataStore.incrementPushCounter()
|
||||
|
||||
incrementPushDataStore.incrementPushCounter()
|
||||
// Diagnostic Push
|
||||
if (pushData.eventId == PushersManager.TEST_EVENT_ID) {
|
||||
if (pushData.eventId == DefaultTestPush.TEST_EVENT_ID) {
|
||||
diagnosticPushHandler.handlePush()
|
||||
return
|
||||
}
|
||||
|
||||
uiHandler.post {
|
||||
coroutineScope.launch(Dispatchers.IO) { handleInternal(pushData) }
|
||||
} else {
|
||||
handleInternal(pushData)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -98,7 +76,6 @@ class DefaultPushHandler @Inject constructor(
|
|||
} else {
|
||||
Timber.tag(loggerTag.value).d("## handleInternal()")
|
||||
}
|
||||
|
||||
val clientSecret = pushData.clientSecret
|
||||
// clientSecret should not be null. If this happens, restore default session
|
||||
val userId = clientSecret
|
||||
|
|
@ -109,27 +86,22 @@ class DefaultPushHandler @Inject constructor(
|
|||
?: run {
|
||||
matrixAuthenticationService.getLatestSessionId()
|
||||
}
|
||||
|
||||
if (userId == null) {
|
||||
Timber.w("Unable to get a session")
|
||||
return
|
||||
}
|
||||
|
||||
val notifiableEvent = notifiableEventResolver.resolveEvent(userId, pushData.roomId, pushData.eventId)
|
||||
|
||||
if (notifiableEvent == null) {
|
||||
Timber.w("Unable to get a notification data")
|
||||
return
|
||||
}
|
||||
|
||||
val userPushStore = userPushStoreFactory.getOrCreate(userId)
|
||||
if (!userPushStore.getNotificationEnabledForDevice().first()) {
|
||||
if (userPushStore.getNotificationEnabledForDevice().first()) {
|
||||
val notifiableEvent = notifiableEventResolver.resolveEvent(userId, pushData.roomId, pushData.eventId)
|
||||
if (notifiableEvent == null) {
|
||||
Timber.w("Unable to get a notification data")
|
||||
return
|
||||
}
|
||||
onNotifiableEventReceived.onNotifiableEventReceived(notifiableEvent)
|
||||
} else {
|
||||
// TODO We need to check if this is an incoming call
|
||||
Timber.tag(loggerTag.value).i("Notification are disabled for this device, ignore push.")
|
||||
return
|
||||
}
|
||||
|
||||
defaultNotificationDrawerManager.onNotifiableEventReceived(notifiableEvent)
|
||||
} catch (e: Exception) {
|
||||
Timber.tag(loggerTag.value).e(e, "## handleInternal() failed")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright (c) 2024 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.push.impl.push
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.push.impl.store.DefaultPushDataStore
|
||||
import javax.inject.Inject
|
||||
|
||||
interface IncrementPushDataStore {
|
||||
suspend fun incrementPushCounter()
|
||||
}
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultIncrementPushDataStore @Inject constructor(
|
||||
private val defaultPushDataStore: DefaultPushDataStore
|
||||
) : IncrementPushDataStore {
|
||||
override suspend fun incrementPushCounter() {
|
||||
defaultPushDataStore.incrementPushCounter()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright (c) 2024 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.push.impl.push
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.push.impl.notifications.DefaultNotificationDrawerManager
|
||||
import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent
|
||||
import javax.inject.Inject
|
||||
|
||||
interface OnNotifiableEventReceived {
|
||||
fun onNotifiableEventReceived(notifiableEvent: NotifiableEvent)
|
||||
}
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultOnNotifiableEventReceived @Inject constructor(
|
||||
private val defaultNotificationDrawerManager: DefaultNotificationDrawerManager,
|
||||
) : OnNotifiableEventReceived {
|
||||
override fun onNotifiableEventReceived(notifiableEvent: NotifiableEvent) {
|
||||
defaultNotificationDrawerManager.onNotifiableEventReceived(notifiableEvent)
|
||||
}
|
||||
}
|
||||
|
|
@ -18,7 +18,7 @@ package io.element.android.libraries.push.impl.pushgateway
|
|||
import retrofit2.http.Body
|
||||
import retrofit2.http.POST
|
||||
|
||||
internal interface PushGatewayAPI {
|
||||
interface PushGatewayAPI {
|
||||
/**
|
||||
* Ask the Push Gateway to send a push to the current device.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright (c) 2024 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.push.impl.pushgateway
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.network.RetrofitFactory
|
||||
import javax.inject.Inject
|
||||
|
||||
interface PushGatewayApiFactory {
|
||||
fun create(baseUrl: String): PushGatewayAPI
|
||||
}
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultPushGatewayApiFactory @Inject constructor(
|
||||
private val retrofitFactory: RetrofitFactory,
|
||||
) : PushGatewayApiFactory {
|
||||
override fun create(baseUrl: String): PushGatewayAPI {
|
||||
return retrofitFactory.create(baseUrl)
|
||||
.create(PushGatewayAPI::class.java)
|
||||
}
|
||||
}
|
||||
|
|
@ -20,7 +20,7 @@ import kotlinx.serialization.SerialName
|
|||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
internal data class PushGatewayDevice(
|
||||
data class PushGatewayDevice(
|
||||
/**
|
||||
* Required. The app_id given when the pusher was created.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import kotlinx.serialization.SerialName
|
|||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
internal data class PushGatewayNotification(
|
||||
data class PushGatewayNotification(
|
||||
@SerialName("event_id")
|
||||
val eventId: String,
|
||||
@SerialName("room_id")
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import kotlinx.serialization.SerialName
|
|||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
internal data class PushGatewayNotifyBody(
|
||||
data class PushGatewayNotifyBody(
|
||||
/**
|
||||
* Required. Information about the push notification
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -15,15 +15,14 @@
|
|||
*/
|
||||
package io.element.android.libraries.push.impl.pushgateway
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.network.RetrofitFactory
|
||||
import io.element.android.libraries.push.api.gateway.PushGatewayFailure
|
||||
import javax.inject.Inject
|
||||
|
||||
class PushGatewayNotifyRequest @Inject constructor(
|
||||
private val retrofitFactory: RetrofitFactory,
|
||||
) {
|
||||
interface PushGatewayNotifyRequest {
|
||||
data class Params(
|
||||
val url: String,
|
||||
val appId: String,
|
||||
|
|
@ -32,13 +31,18 @@ class PushGatewayNotifyRequest @Inject constructor(
|
|||
val roomId: RoomId,
|
||||
)
|
||||
|
||||
suspend fun execute(params: Params) {
|
||||
val sygnalApi = retrofitFactory.create(
|
||||
suspend fun execute(params: Params)
|
||||
}
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultPushGatewayNotifyRequest @Inject constructor(
|
||||
private val pushGatewayApiFactory: PushGatewayApiFactory,
|
||||
) : PushGatewayNotifyRequest {
|
||||
override suspend fun execute(params: PushGatewayNotifyRequest.Params) {
|
||||
val pushGatewayApi = pushGatewayApiFactory.create(
|
||||
params.url.substringBefore(PushGatewayConfig.URI_PUSH_GATEWAY_PREFIX_PATH)
|
||||
)
|
||||
.create(PushGatewayAPI::class.java)
|
||||
|
||||
val response = sygnalApi.notify(
|
||||
val response = pushGatewayApi.notify(
|
||||
PushGatewayNotifyBody(
|
||||
PushGatewayNotification(
|
||||
eventId = params.eventId.value,
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import kotlinx.serialization.SerialName
|
|||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
internal data class PushGatewayNotifyResponse(
|
||||
data class PushGatewayNotifyResponse(
|
||||
@SerialName("rejected")
|
||||
val rejectedPushKeys: List<String>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright (c) 2024 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.push.impl.test
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.appconfig.PushConfig
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.push.impl.pushgateway.PushGatewayNotifyRequest
|
||||
import io.element.android.libraries.pushproviders.api.CurrentUserPushConfig
|
||||
import javax.inject.Inject
|
||||
|
||||
interface TestPush {
|
||||
suspend fun execute(config: CurrentUserPushConfig)
|
||||
}
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultTestPush @Inject constructor(
|
||||
private val pushGatewayNotifyRequest: PushGatewayNotifyRequest,
|
||||
) : TestPush {
|
||||
override suspend fun execute(config: CurrentUserPushConfig) {
|
||||
pushGatewayNotifyRequest.execute(
|
||||
PushGatewayNotifyRequest.Params(
|
||||
url = config.url,
|
||||
appId = PushConfig.PUSHER_APP_ID,
|
||||
pushKey = config.pushKey,
|
||||
eventId = TEST_EVENT_ID,
|
||||
roomId = TEST_ROOM_ID,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val TEST_EVENT_ID = EventId("\$THIS_IS_A_FAKE_EVENT_ID")
|
||||
val TEST_ROOM_ID = RoomId("!room:domain")
|
||||
}
|
||||
}
|
||||
|
|
@ -52,7 +52,6 @@
|
|||
<item quantity="few">"%d пакоя"</item>
|
||||
<item quantity="many">"%d пакояў"</item>
|
||||
</plurals>
|
||||
<string name="push_choose_distributor_dialog_title_android">"Выберыце спосаб атрымання апавяшчэнняў"</string>
|
||||
<string name="push_distributor_background_sync_android">"Фонавая сінхранізацыя"</string>
|
||||
<string name="push_distributor_firebase_android">"Сэрвісы Google"</string>
|
||||
<string name="push_no_valid_google_play_services_apk_android">"Службы Google Play не знойдзены. Апавяшчэнні могуць не працаваць належным чынам."</string>
|
||||
|
|
|
|||
|
|
@ -52,7 +52,6 @@
|
|||
<item quantity="few">"%d místnosti"</item>
|
||||
<item quantity="other">"%d místností"</item>
|
||||
</plurals>
|
||||
<string name="push_choose_distributor_dialog_title_android">"Vyberte, jak chcete přijímat oznámení"</string>
|
||||
<string name="push_distributor_background_sync_android">"Synchronizace na pozadí"</string>
|
||||
<string name="push_distributor_firebase_android">"Služby Google"</string>
|
||||
<string name="push_no_valid_google_play_services_apk_android">"Nebyly nalezeny žádné funkční služby Google Play. Oznámení nemusí fungovat správně."</string>
|
||||
|
|
|
|||
|
|
@ -46,7 +46,6 @@
|
|||
<item quantity="one">"%d Raum"</item>
|
||||
<item quantity="other">"%d Räume"</item>
|
||||
</plurals>
|
||||
<string name="push_choose_distributor_dialog_title_android">"Wähle aus, wie du Benachrichtigungen erhalten möchtest"</string>
|
||||
<string name="push_distributor_background_sync_android">"Hintergrundsynchronisation"</string>
|
||||
<string name="push_distributor_firebase_android">"Google-Dienste"</string>
|
||||
<string name="push_no_valid_google_play_services_apk_android">"Keine gültigen Google Play-Dienste gefunden. Benachrichtigungen funktionieren möglicherweise nicht richtig."</string>
|
||||
|
|
|
|||
|
|
@ -46,7 +46,6 @@
|
|||
<item quantity="one">"%d sala"</item>
|
||||
<item quantity="other">"%d salas"</item>
|
||||
</plurals>
|
||||
<string name="push_choose_distributor_dialog_title_android">"Elige cómo recibir las notificaciones"</string>
|
||||
<string name="push_distributor_background_sync_android">"Sincronización en segundo plano"</string>
|
||||
<string name="push_distributor_firebase_android">"Servicios de Google"</string>
|
||||
<string name="push_no_valid_google_play_services_apk_android">"No se han encontrado Servicios de Google Play válidos. Es posible que las notificaciones no funcionen correctamente."</string>
|
||||
|
|
|
|||
|
|
@ -46,7 +46,6 @@
|
|||
<item quantity="one">"%d salon"</item>
|
||||
<item quantity="other">"%d salons"</item>
|
||||
</plurals>
|
||||
<string name="push_choose_distributor_dialog_title_android">"Choisissez le mode de réception des notifications"</string>
|
||||
<string name="push_distributor_background_sync_android">"Synchronisation en arrière-plan"</string>
|
||||
<string name="push_distributor_firebase_android">"Services Google"</string>
|
||||
<string name="push_no_valid_google_play_services_apk_android">"Aucun service Google Play valide n’a été trouvé. Les notifications peuvent ne pas fonctionner correctement."</string>
|
||||
|
|
|
|||
|
|
@ -46,7 +46,6 @@
|
|||
<item quantity="one">"%d szoba"</item>
|
||||
<item quantity="other">"%d szoba"</item>
|
||||
</plurals>
|
||||
<string name="push_choose_distributor_dialog_title_android">"Válassza ki az értesítések fogadási módját"</string>
|
||||
<string name="push_distributor_background_sync_android">"Háttérszinkronizálás"</string>
|
||||
<string name="push_distributor_firebase_android">"Google szolgáltatások"</string>
|
||||
<string name="push_no_valid_google_play_services_apk_android">"A Google Play szolgáltatások nem találhatók. Előfordulhat, hogy az értesítések nem működnek megfelelően."</string>
|
||||
|
|
|
|||
|
|
@ -40,7 +40,6 @@
|
|||
<plurals name="notification_unread_notified_messages_in_room_rooms">
|
||||
<item quantity="other">"%d ruangan"</item>
|
||||
</plurals>
|
||||
<string name="push_choose_distributor_dialog_title_android">"Pilih cara menerima notifikasi"</string>
|
||||
<string name="push_distributor_background_sync_android">"Sinkronisasi latar belakang"</string>
|
||||
<string name="push_distributor_firebase_android">"Layanan Google"</string>
|
||||
<string name="push_no_valid_google_play_services_apk_android">"Tidak ditemukan Layanan Google Play yang valid. Pemberitahuan mungkin tidak berfungsi dengan baik."</string>
|
||||
|
|
|
|||
|
|
@ -46,7 +46,6 @@
|
|||
<item quantity="one">"%d stanza"</item>
|
||||
<item quantity="other">"%d stanze"</item>
|
||||
</plurals>
|
||||
<string name="push_choose_distributor_dialog_title_android">"Scegli come ricevere le notifiche"</string>
|
||||
<string name="push_distributor_background_sync_android">"Sincronizzazione in background"</string>
|
||||
<string name="push_distributor_firebase_android">"Servizi Google"</string>
|
||||
<string name="push_no_valid_google_play_services_apk_android">"Google Play Services non trovato. Le notifiche non funzioneranno bene."</string>
|
||||
|
|
|
|||
50
libraries/push/impl/src/main/res/values-ka/translations.xml
Normal file
50
libraries/push/impl/src/main/res/values-ka/translations.xml
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="notification_channel_call">"ზარი"</string>
|
||||
<string name="notification_channel_listening_for_events">"მოვლენებისთვის მოსმენა"</string>
|
||||
<string name="notification_channel_noisy">"ხმაურიანი შეტყობინებები"</string>
|
||||
<string name="notification_channel_silent">"ჩუმი შეტყობინებები"</string>
|
||||
<plurals name="notification_compat_summary_line_for_room">
|
||||
<item quantity="one">"%1$s: %2$d შეტყობინება"</item>
|
||||
<item quantity="other">"%1$s: %2$d შეტყობინება"</item>
|
||||
</plurals>
|
||||
<plurals name="notification_compat_summary_title">
|
||||
<item quantity="one">"%d შეტყობინება"</item>
|
||||
<item quantity="other">"%d შეტყობინება"</item>
|
||||
</plurals>
|
||||
<string name="notification_fallback_content">"შეტყობინება"</string>
|
||||
<string name="notification_inline_reply_failed">"** გაგზავნა ვერ მოხერხდა - გთხოვთ, გახსნათ ოთახი"</string>
|
||||
<string name="notification_invitation_action_join">"გაწევრიანება"</string>
|
||||
<string name="notification_invitation_action_reject">"უარყოფა"</string>
|
||||
<plurals name="notification_invitations">
|
||||
<item quantity="one">"%d მოწვევა"</item>
|
||||
<item quantity="other">"%d მოწვევები"</item>
|
||||
</plurals>
|
||||
<string name="notification_invite_body">"მოგიწვიათ ჩატში"</string>
|
||||
<string name="notification_new_messages">"ახალი შეტყობინებები"</string>
|
||||
<plurals name="notification_new_messages_for_room">
|
||||
<item quantity="one">"%d ახალი მესიჯი"</item>
|
||||
<item quantity="other">"%d ახალი მესიჯი"</item>
|
||||
</plurals>
|
||||
<string name="notification_reaction_body">"რეაგირება მოხდა: %1$s"</string>
|
||||
<string name="notification_room_action_quick_reply">"Სწრაფი პასუხი"</string>
|
||||
<string name="notification_room_invite_body">"მოგიწვიათ ოთახში"</string>
|
||||
<string name="notification_sender_me">"მე"</string>
|
||||
<string name="notification_test_push_notification_content">"თქვენ ხედავთ შეტყობინებას! დამაწკაპუნეთ!"</string>
|
||||
<string name="notification_ticker_text_dm">"%1$s: %2$s"</string>
|
||||
<string name="notification_ticker_text_group">"%1$s: %2$s %3$s"</string>
|
||||
<plurals name="notification_unread_notified_messages">
|
||||
<item quantity="one">"%d წაუკითხავი შეტყობინება"</item>
|
||||
<item quantity="other">"%d წაუკითხავი შეტყობინება"</item>
|
||||
</plurals>
|
||||
<string name="notification_unread_notified_messages_and_invitation">"%1$s და %2$s"</string>
|
||||
<string name="notification_unread_notified_messages_in_room">"%1$s %2$s-ში"</string>
|
||||
<string name="notification_unread_notified_messages_in_room_and_invitation">"%1$s %2$s-ში და %3$s"</string>
|
||||
<plurals name="notification_unread_notified_messages_in_room_rooms">
|
||||
<item quantity="one">"%d ოთახი"</item>
|
||||
<item quantity="other">"%d ოთახი"</item>
|
||||
</plurals>
|
||||
<string name="push_distributor_background_sync_android">"ფონის სინქრონიზაცია"</string>
|
||||
<string name="push_distributor_firebase_android">"Google სერვისები"</string>
|
||||
<string name="push_no_valid_google_play_services_apk_android">"მოქმედი Google Play სერვისები ვერ მოიძებნა. შეტყობინებები შეიძლება ვერ იმუშაოს სწორად."</string>
|
||||
</resources>
|
||||
|
|
@ -46,7 +46,6 @@
|
|||
<item quantity="one">"%d sala"</item>
|
||||
<item quantity="other">"%d salas"</item>
|
||||
</plurals>
|
||||
<string name="push_choose_distributor_dialog_title_android">"Escolhe como receber notificações"</string>
|
||||
<string name="push_distributor_background_sync_android">"Sincronização em segundo plano"</string>
|
||||
<string name="push_distributor_firebase_android">"Serviços do Google Play"</string>
|
||||
<string name="push_no_valid_google_play_services_apk_android">"Nenhuns Serviços do Google Play válidos encontrados. As notificações poderão não funcionar devidamente."</string>
|
||||
|
|
@ -46,7 +46,6 @@
|
|||
<item quantity="one">"%d cameră"</item>
|
||||
<item quantity="other">"%d camere"</item>
|
||||
</plurals>
|
||||
<string name="push_choose_distributor_dialog_title_android">"Alegeți modul de primire a notificărilor"</string>
|
||||
<string name="push_distributor_background_sync_android">"Sincronizare în fundal"</string>
|
||||
<string name="push_distributor_firebase_android">"Servicii Google"</string>
|
||||
<string name="push_no_valid_google_play_services_apk_android">"Nu au fost găsite servicii Google Play valide. Este posibil ca notificările să nu funcționeze corect."</string>
|
||||
|
|
@ -58,6 +57,7 @@
|
|||
<string name="troubleshoot_notifications_test_detect_push_provider_failure">"Nu s-au găsit furnizori push."</string>
|
||||
<plurals name="troubleshoot_notifications_test_detect_push_provider_success">
|
||||
<item quantity="one">"S-a găsit %1$d furnizor push: %2$s"</item>
|
||||
<item quantity="few">"S-au găsit %1$d furnizori push: %2$s"</item>
|
||||
<item quantity="other">"S-au găsit %1$d furnizori push: %2$s"</item>
|
||||
</plurals>
|
||||
<string name="troubleshoot_notifications_test_detect_push_provider_title">"Detectați furnizorii push"</string>
|
||||
|
|
|
|||
|
|
@ -52,7 +52,6 @@
|
|||
<item quantity="few">"%d комнаты"</item>
|
||||
<item quantity="many">"%d комнат"</item>
|
||||
</plurals>
|
||||
<string name="push_choose_distributor_dialog_title_android">"Выберите способ получения уведомлений"</string>
|
||||
<string name="push_distributor_background_sync_android">"Фоновая синхронизация"</string>
|
||||
<string name="push_distributor_firebase_android">"Сервисы Google"</string>
|
||||
<string name="push_no_valid_google_play_services_apk_android">"Не найдены действующие службы Google Play. Уведомления могут работать некорректно."</string>
|
||||
|
|
|
|||
|
|
@ -52,7 +52,6 @@
|
|||
<item quantity="few">"%d miestnosti"</item>
|
||||
<item quantity="other">"%d miestností"</item>
|
||||
</plurals>
|
||||
<string name="push_choose_distributor_dialog_title_android">"Vyberte spôsob prijímania oznámení"</string>
|
||||
<string name="push_distributor_background_sync_android">"Synchronizácia na pozadí"</string>
|
||||
<string name="push_distributor_firebase_android">"Služby Google"</string>
|
||||
<string name="push_no_valid_google_play_services_apk_android">"Nenašli sa žiadne platné služby Google Play. Oznámenia nemusia fungovať správne."</string>
|
||||
|
|
|
|||
|
|
@ -45,7 +45,6 @@
|
|||
<item quantity="one">"%d rum"</item>
|
||||
<item quantity="other">"%d rum"</item>
|
||||
</plurals>
|
||||
<string name="push_choose_distributor_dialog_title_android">"Välj hur du vill ta emot aviseringar"</string>
|
||||
<string name="push_distributor_background_sync_android">"Bakgrundssynkronisering"</string>
|
||||
<string name="push_distributor_firebase_android">"Google-tjänster"</string>
|
||||
<string name="push_no_valid_google_play_services_apk_android">"Inga giltiga Google Play-tjänster hittades. Aviseringar kanske inte fungerar korrekt."</string>
|
||||
|
|
|
|||
|
|
@ -52,7 +52,6 @@
|
|||
<item quantity="few">"%d кімнати"</item>
|
||||
<item quantity="many">"%d кімнат"</item>
|
||||
</plurals>
|
||||
<string name="push_choose_distributor_dialog_title_android">"Виберіть спосіб отримання сповіщень"</string>
|
||||
<string name="push_distributor_background_sync_android">"Фонова синхронізація"</string>
|
||||
<string name="push_distributor_firebase_android">"Сервіси Google"</string>
|
||||
<string name="push_no_valid_google_play_services_apk_android">"Не знайдено дійсних сервісів Google Play. Сповіщення можуть не працювати належним чином."</string>
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
<item quantity="other">"%d 則新訊息"</item>
|
||||
</plurals>
|
||||
<string name="notification_reaction_body">"回應 %1$s"</string>
|
||||
<string name="notification_room_action_mark_as_read">"標為已讀"</string>
|
||||
<string name="notification_room_action_quick_reply">"快速回覆"</string>
|
||||
<string name="notification_room_invite_body">"邀請您加入聊天室"</string>
|
||||
<string name="notification_sender_me">"我"</string>
|
||||
|
|
@ -31,7 +32,6 @@
|
|||
<plurals name="notification_unread_notified_messages_in_room_rooms">
|
||||
<item quantity="other">"%d 個聊天室"</item>
|
||||
</plurals>
|
||||
<string name="push_choose_distributor_dialog_title_android">"選擇接收通知的機制"</string>
|
||||
<string name="push_distributor_background_sync_android">"背景同步"</string>
|
||||
<string name="push_distributor_firebase_android">"Google 服務"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -40,7 +40,6 @@
|
|||
<plurals name="notification_unread_notified_messages_in_room_rooms">
|
||||
<item quantity="other">"%d 个房间"</item>
|
||||
</plurals>
|
||||
<string name="push_choose_distributor_dialog_title_android">"选择如何接收通知"</string>
|
||||
<string name="push_distributor_background_sync_android">"后台同步"</string>
|
||||
<string name="push_distributor_firebase_android">"谷歌服务"</string>
|
||||
<string name="push_no_valid_google_play_services_apk_android">"找不到有效的 Google Play 服务。通知可能无法正常工作。"</string>
|
||||
|
|
|
|||
|
|
@ -46,7 +46,6 @@
|
|||
<item quantity="one">"%d room"</item>
|
||||
<item quantity="other">"%d rooms"</item>
|
||||
</plurals>
|
||||
<string name="push_choose_distributor_dialog_title_android">"Choose how to receive notifications"</string>
|
||||
<string name="push_distributor_background_sync_android">"Background synchronization"</string>
|
||||
<string name="push_distributor_firebase_android">"Google Services"</string>
|
||||
<string name="push_no_valid_google_play_services_apk_android">"No valid Google Play Services found. Notifications may not work properly."</string>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,221 @@
|
|||
/*
|
||||
* Copyright (c) 2024 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.push.impl
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.libraries.push.api.GetCurrentPushProvider
|
||||
import io.element.android.libraries.push.impl.test.FakeTestPush
|
||||
import io.element.android.libraries.push.impl.test.TestPush
|
||||
import io.element.android.libraries.push.test.FakeGetCurrentPushProvider
|
||||
import io.element.android.libraries.pushproviders.api.CurrentUserPushConfig
|
||||
import io.element.android.libraries.pushproviders.api.Distributor
|
||||
import io.element.android.libraries.pushproviders.api.PushProvider
|
||||
import io.element.android.libraries.pushproviders.test.FakePushProvider
|
||||
import io.element.android.libraries.pushstore.api.UserPushStoreFactory
|
||||
import io.element.android.libraries.pushstore.test.userpushstore.FakeUserPushStore
|
||||
import io.element.android.libraries.pushstore.test.userpushstore.FakeUserPushStoreFactory
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.lambda.value
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
class DefaultPushServiceTest {
|
||||
@Test
|
||||
fun `test push no push provider`() = runTest {
|
||||
val defaultPushService = createDefaultPushService()
|
||||
assertThat(defaultPushService.testPush()).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test push no config`() = runTest {
|
||||
val aPushProvider = FakePushProvider()
|
||||
val defaultPushService = createDefaultPushService(
|
||||
pushProviders = setOf(aPushProvider),
|
||||
getCurrentPushProvider = FakeGetCurrentPushProvider(currentPushProvider = aPushProvider.name),
|
||||
)
|
||||
assertThat(defaultPushService.testPush()).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test push ok`() = runTest {
|
||||
val aConfig = CurrentUserPushConfig(
|
||||
url = "aUrl",
|
||||
pushKey = "aPushKey",
|
||||
)
|
||||
val testPushResult = lambdaRecorder<CurrentUserPushConfig, Unit> { }
|
||||
val aPushProvider = FakePushProvider(
|
||||
currentUserPushConfig = aConfig
|
||||
)
|
||||
val defaultPushService = createDefaultPushService(
|
||||
pushProviders = setOf(aPushProvider),
|
||||
getCurrentPushProvider = FakeGetCurrentPushProvider(currentPushProvider = aPushProvider.name),
|
||||
testPush = FakeTestPush(executeResult = testPushResult),
|
||||
)
|
||||
assertThat(defaultPushService.testPush()).isTrue()
|
||||
testPushResult.assertions()
|
||||
.isCalledOnce()
|
||||
.with(value(aConfig))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getCurrentPushProvider null`() = runTest {
|
||||
val defaultPushService = createDefaultPushService()
|
||||
val result = defaultPushService.getCurrentPushProvider()
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getCurrentPushProvider ok`() = runTest {
|
||||
val aPushProvider = FakePushProvider()
|
||||
val defaultPushService = createDefaultPushService(
|
||||
pushProviders = setOf(aPushProvider),
|
||||
getCurrentPushProvider = FakeGetCurrentPushProvider(currentPushProvider = aPushProvider.name),
|
||||
)
|
||||
val result = defaultPushService.getCurrentPushProvider()
|
||||
assertThat(result).isEqualTo(aPushProvider)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getAvailablePushProviders empty`() = runTest {
|
||||
val defaultPushService = createDefaultPushService()
|
||||
val result = defaultPushService.getAvailablePushProviders()
|
||||
assertThat(result).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `registerWith ok`() = runTest {
|
||||
val client = FakeMatrixClient()
|
||||
val aPushProvider = FakePushProvider(
|
||||
registerWithResult = { _, _ -> Result.success(Unit) },
|
||||
)
|
||||
val aDistributor = Distributor("aValue", "aName")
|
||||
val defaultPushService = createDefaultPushService()
|
||||
val result = defaultPushService.registerWith(client, aPushProvider, aDistributor)
|
||||
assertThat(result).isEqualTo(Result.success(Unit))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `registerWith fail to register`() = runTest {
|
||||
val client = FakeMatrixClient()
|
||||
val aPushProvider = FakePushProvider(
|
||||
registerWithResult = { _, _ -> Result.failure(AN_EXCEPTION) },
|
||||
)
|
||||
val aDistributor = Distributor("aValue", "aName")
|
||||
val defaultPushService = createDefaultPushService()
|
||||
val result = defaultPushService.registerWith(client, aPushProvider, aDistributor)
|
||||
assertThat(result.isFailure).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `registerWith fail to unregister previous push provider`() = runTest {
|
||||
val client = FakeMatrixClient()
|
||||
val aCurrentPushProvider = FakePushProvider(
|
||||
unregisterWithResult = { Result.failure(AN_EXCEPTION) },
|
||||
name = "aCurrentPushProvider",
|
||||
)
|
||||
val aPushProvider = FakePushProvider(
|
||||
name = "aPushProvider",
|
||||
)
|
||||
val userPushStore = FakeUserPushStore().apply {
|
||||
setPushProviderName(aCurrentPushProvider.name)
|
||||
}
|
||||
val aDistributor = Distributor("aValue", "aName")
|
||||
val defaultPushService = createDefaultPushService(
|
||||
pushProviders = setOf(aCurrentPushProvider, aPushProvider),
|
||||
getCurrentPushProvider = FakeGetCurrentPushProvider(currentPushProvider = aCurrentPushProvider.name),
|
||||
userPushStoreFactory = FakeUserPushStoreFactory(
|
||||
userPushStore = { userPushStore },
|
||||
),
|
||||
)
|
||||
val result = defaultPushService.registerWith(client, aPushProvider, aDistributor)
|
||||
assertThat(result.isFailure).isTrue()
|
||||
assertThat(userPushStore.getPushProviderName()).isEqualTo(aCurrentPushProvider.name)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `registerWith unregister previous push provider and register new OK`() = runTest {
|
||||
val client = FakeMatrixClient()
|
||||
val unregisterLambda = lambdaRecorder<MatrixClient, Result<Unit>> { Result.success(Unit) }
|
||||
val registerLambda = lambdaRecorder<MatrixClient, Distributor, Result<Unit>> { _, _ -> Result.success(Unit) }
|
||||
val aCurrentPushProvider = FakePushProvider(
|
||||
unregisterWithResult = unregisterLambda,
|
||||
name = "aCurrentPushProvider",
|
||||
)
|
||||
val aPushProvider = FakePushProvider(
|
||||
registerWithResult = registerLambda,
|
||||
name = "aPushProvider",
|
||||
)
|
||||
val userPushStore = FakeUserPushStore().apply {
|
||||
setPushProviderName(aCurrentPushProvider.name)
|
||||
}
|
||||
val aDistributor = Distributor("aValue", "aName")
|
||||
val defaultPushService = createDefaultPushService(
|
||||
pushProviders = setOf(aCurrentPushProvider, aPushProvider),
|
||||
getCurrentPushProvider = FakeGetCurrentPushProvider(currentPushProvider = aCurrentPushProvider.name),
|
||||
userPushStoreFactory = FakeUserPushStoreFactory(
|
||||
userPushStore = { userPushStore },
|
||||
),
|
||||
)
|
||||
val result = defaultPushService.registerWith(client, aPushProvider, aDistributor)
|
||||
assertThat(result.isSuccess).isTrue()
|
||||
assertThat(userPushStore.getPushProviderName()).isEqualTo(aPushProvider.name)
|
||||
unregisterLambda.assertions()
|
||||
.isCalledOnce()
|
||||
.with(value(client))
|
||||
registerLambda.assertions()
|
||||
.isCalledOnce()
|
||||
.with(value(client), value(aDistributor))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getAvailablePushProviders sorted`() = runTest {
|
||||
val aPushProvider1 = FakePushProvider(
|
||||
index = 1,
|
||||
name = "aPushProvider1",
|
||||
)
|
||||
val aPushProvider2 = FakePushProvider(
|
||||
index = 2,
|
||||
name = "aPushProvider2",
|
||||
)
|
||||
val aPushProvider3 = FakePushProvider(
|
||||
index = 3,
|
||||
name = "aPushProvider3",
|
||||
)
|
||||
val defaultPushService = createDefaultPushService(
|
||||
pushProviders = setOf(aPushProvider1, aPushProvider3, aPushProvider2),
|
||||
)
|
||||
val result = defaultPushService.getAvailablePushProviders()
|
||||
assertThat(result).containsExactly(aPushProvider1, aPushProvider2, aPushProvider3).inOrder()
|
||||
}
|
||||
|
||||
private fun createDefaultPushService(
|
||||
testPush: TestPush = FakeTestPush(),
|
||||
userPushStoreFactory: UserPushStoreFactory = FakeUserPushStoreFactory(),
|
||||
pushProviders: Set<@JvmSuppressWildcards PushProvider> = emptySet(),
|
||||
getCurrentPushProvider: GetCurrentPushProvider = FakeGetCurrentPushProvider(currentPushProvider = null),
|
||||
): DefaultPushService {
|
||||
return DefaultPushService(
|
||||
testPush = testPush,
|
||||
userPushStoreFactory = userPushStoreFactory,
|
||||
pushProviders = pushProviders,
|
||||
getCurrentPushProvider = getCurrentPushProvider,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,193 @@
|
|||
/*
|
||||
* Copyright (c) 2024 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.push.impl
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.appconfig.PushConfig
|
||||
import io.element.android.libraries.core.meta.BuildMeta
|
||||
import io.element.android.libraries.matrix.api.pusher.SetHttpPusherData
|
||||
import io.element.android.libraries.matrix.api.pusher.UnsetHttpPusherData
|
||||
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
||||
import io.element.android.libraries.matrix.test.A_SECRET
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||
import io.element.android.libraries.matrix.test.pushers.FakePushersService
|
||||
import io.element.android.libraries.pushstore.api.UserPushStoreFactory
|
||||
import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret
|
||||
import io.element.android.libraries.pushstore.test.userpushstore.FakeUserPushStore
|
||||
import io.element.android.libraries.pushstore.test.userpushstore.FakeUserPushStoreFactory
|
||||
import io.element.android.libraries.pushstore.test.userpushstore.clientsecret.FakePushClientSecret
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.lambda.value
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
class DefaultPusherSubscriberTest {
|
||||
@Test
|
||||
fun `test register pusher OK`() = runTest {
|
||||
testRegisterPusher(
|
||||
currentPushKey = null,
|
||||
registerResult = Result.success(Unit),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test re-register pusher OK`() = runTest {
|
||||
testRegisterPusher(
|
||||
currentPushKey = "aPushKey",
|
||||
registerResult = Result.success(Unit),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test register pusher error`() = runTest {
|
||||
testRegisterPusher(
|
||||
currentPushKey = null,
|
||||
registerResult = Result.failure(AN_EXCEPTION),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test re-register pusher error`() = runTest {
|
||||
testRegisterPusher(
|
||||
currentPushKey = "aPushKey",
|
||||
registerResult = Result.failure(AN_EXCEPTION),
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun testRegisterPusher(
|
||||
currentPushKey: String?,
|
||||
registerResult: Result<Unit>,
|
||||
) {
|
||||
val setHttpPusherResult = lambdaRecorder<SetHttpPusherData, Result<Unit>> { registerResult }
|
||||
val userPushStore = FakeUserPushStore().apply {
|
||||
setCurrentRegisteredPushKey(currentPushKey)
|
||||
}
|
||||
assertThat(userPushStore.getCurrentRegisteredPushKey()).isEqualTo(currentPushKey)
|
||||
|
||||
val matrixClient = FakeMatrixClient(
|
||||
pushersService = FakePushersService(
|
||||
setHttpPusherResult = setHttpPusherResult,
|
||||
),
|
||||
)
|
||||
val defaultPusherSubscriber = createDefaultPusherSubscriber(
|
||||
pushClientSecret = FakePushClientSecret(
|
||||
getSecretForUserResult = { A_SECRET },
|
||||
),
|
||||
userPushStoreFactory = FakeUserPushStoreFactory(
|
||||
userPushStore = { userPushStore },
|
||||
),
|
||||
)
|
||||
val result = defaultPusherSubscriber.registerPusher(
|
||||
matrixClient = matrixClient,
|
||||
pushKey = "aPushKey",
|
||||
gateway = "aGateway",
|
||||
)
|
||||
assertThat(result).isEqualTo(registerResult)
|
||||
setHttpPusherResult.assertions()
|
||||
.isCalledOnce()
|
||||
.with(
|
||||
value(
|
||||
SetHttpPusherData(
|
||||
pushKey = "aPushKey",
|
||||
appId = PushConfig.PUSHER_APP_ID,
|
||||
url = "aGateway",
|
||||
appDisplayName = "MyApp",
|
||||
deviceDisplayName = "MyDevice",
|
||||
profileTag = DEFAULT_PUSHER_FILE_TAG + "_",
|
||||
lang = "en",
|
||||
defaultPayload = "{\"cs\":\"$A_SECRET\"}",
|
||||
),
|
||||
)
|
||||
)
|
||||
assertThat(userPushStore.getCurrentRegisteredPushKey()).isEqualTo(
|
||||
if (registerResult.isSuccess) "aPushKey" else currentPushKey
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test unregister pusher OK`() = runTest {
|
||||
testUnregisterPusher(
|
||||
currentPushKey = "aPushKey",
|
||||
unregisterResult = Result.success(Unit),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test unregister pusher error`() = runTest {
|
||||
testUnregisterPusher(
|
||||
currentPushKey = "aPushKey",
|
||||
unregisterResult = Result.failure(AN_EXCEPTION),
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun testUnregisterPusher(
|
||||
currentPushKey: String?,
|
||||
unregisterResult: Result<Unit>,
|
||||
) {
|
||||
val unsetHttpPusherResult = lambdaRecorder<UnsetHttpPusherData, Result<Unit>> { unregisterResult }
|
||||
val userPushStore = FakeUserPushStore().apply {
|
||||
setCurrentRegisteredPushKey(currentPushKey)
|
||||
}
|
||||
assertThat(userPushStore.getCurrentRegisteredPushKey()).isEqualTo(currentPushKey)
|
||||
|
||||
val matrixClient = FakeMatrixClient(
|
||||
pushersService = FakePushersService(
|
||||
unsetHttpPusherResult = unsetHttpPusherResult,
|
||||
),
|
||||
)
|
||||
val defaultPusherSubscriber = createDefaultPusherSubscriber(
|
||||
pushClientSecret = FakePushClientSecret(
|
||||
getSecretForUserResult = { A_SECRET },
|
||||
),
|
||||
userPushStoreFactory = FakeUserPushStoreFactory(
|
||||
userPushStore = { userPushStore },
|
||||
),
|
||||
)
|
||||
val result = defaultPusherSubscriber.unregisterPusher(
|
||||
matrixClient = matrixClient,
|
||||
pushKey = "aPushKey",
|
||||
gateway = "aGateway",
|
||||
)
|
||||
assertThat(result).isEqualTo(unregisterResult)
|
||||
unsetHttpPusherResult.assertions()
|
||||
.isCalledOnce()
|
||||
.with(
|
||||
value(
|
||||
UnsetHttpPusherData(
|
||||
pushKey = "aPushKey",
|
||||
appId = PushConfig.PUSHER_APP_ID,
|
||||
),
|
||||
)
|
||||
)
|
||||
assertThat(userPushStore.getCurrentRegisteredPushKey()).isEqualTo(
|
||||
if (unregisterResult.isSuccess) null else currentPushKey
|
||||
)
|
||||
}
|
||||
|
||||
private fun createDefaultPusherSubscriber(
|
||||
buildMeta: BuildMeta = aBuildMeta(applicationName = "MyApp"),
|
||||
userPushStoreFactory: UserPushStoreFactory = FakeUserPushStoreFactory(),
|
||||
pushClientSecret: PushClientSecret = FakePushClientSecret(),
|
||||
): DefaultPusherSubscriber {
|
||||
return DefaultPusherSubscriber(
|
||||
buildMeta = buildMeta,
|
||||
pushClientSecret = pushClientSecret,
|
||||
userPushStoreFactory = userPushStoreFactory,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -59,17 +59,17 @@ import org.robolectric.RuntimeEnvironment
|
|||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class NotifiableEventResolverTest {
|
||||
class DefaultNotifiableEventResolverTest {
|
||||
@Test
|
||||
fun `resolve event no session`() = runTest {
|
||||
val sut = createNotifiableEventResolver(notificationService = null)
|
||||
val sut = createDefaultNotifiableEventResolver(notificationService = null)
|
||||
val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID)
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `resolve event failure`() = runTest {
|
||||
val sut = createNotifiableEventResolver(
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.failure(AN_EXCEPTION)
|
||||
)
|
||||
val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID)
|
||||
|
|
@ -78,7 +78,7 @@ class NotifiableEventResolverTest {
|
|||
|
||||
@Test
|
||||
fun `resolve event null`() = runTest {
|
||||
val sut = createNotifiableEventResolver(
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(null)
|
||||
)
|
||||
val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID)
|
||||
|
|
@ -87,7 +87,7 @@ class NotifiableEventResolverTest {
|
|||
|
||||
@Test
|
||||
fun `resolve event message text`() = runTest {
|
||||
val sut = createNotifiableEventResolver(
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
createNotificationData(
|
||||
content = NotificationContent.MessageLike.RoomMessage(
|
||||
|
|
@ -105,7 +105,7 @@ class NotifiableEventResolverTest {
|
|||
@Test
|
||||
@Config(qualifiers = "en")
|
||||
fun `resolve event message with mention`() = runTest {
|
||||
val sut = createNotifiableEventResolver(
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
createNotificationData(
|
||||
content = NotificationContent.MessageLike.RoomMessage(
|
||||
|
|
@ -123,7 +123,7 @@ class NotifiableEventResolverTest {
|
|||
|
||||
@Test
|
||||
fun `resolve HTML formatted event message text takes plain text version`() = runTest {
|
||||
val sut = createNotifiableEventResolver(
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
createNotificationData(
|
||||
content = NotificationContent.MessageLike.RoomMessage(
|
||||
|
|
@ -146,7 +146,7 @@ class NotifiableEventResolverTest {
|
|||
|
||||
@Test
|
||||
fun `resolve incorrectly formatted event message text uses fallback`() = runTest {
|
||||
val sut = createNotifiableEventResolver(
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
createNotificationData(
|
||||
content = NotificationContent.MessageLike.RoomMessage(
|
||||
|
|
@ -169,7 +169,7 @@ class NotifiableEventResolverTest {
|
|||
|
||||
@Test
|
||||
fun `resolve event message audio`() = runTest {
|
||||
val sut = createNotifiableEventResolver(
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
createNotificationData(
|
||||
content = NotificationContent.MessageLike.RoomMessage(
|
||||
|
|
@ -186,7 +186,7 @@ class NotifiableEventResolverTest {
|
|||
|
||||
@Test
|
||||
fun `resolve event message video`() = runTest {
|
||||
val sut = createNotifiableEventResolver(
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
createNotificationData(
|
||||
content = NotificationContent.MessageLike.RoomMessage(
|
||||
|
|
@ -203,7 +203,7 @@ class NotifiableEventResolverTest {
|
|||
|
||||
@Test
|
||||
fun `resolve event message voice`() = runTest {
|
||||
val sut = createNotifiableEventResolver(
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
createNotificationData(
|
||||
content = NotificationContent.MessageLike.RoomMessage(
|
||||
|
|
@ -220,7 +220,7 @@ class NotifiableEventResolverTest {
|
|||
|
||||
@Test
|
||||
fun `resolve event message image`() = runTest {
|
||||
val sut = createNotifiableEventResolver(
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
createNotificationData(
|
||||
content = NotificationContent.MessageLike.RoomMessage(
|
||||
|
|
@ -237,7 +237,7 @@ class NotifiableEventResolverTest {
|
|||
|
||||
@Test
|
||||
fun `resolve event message sticker`() = runTest {
|
||||
val sut = createNotifiableEventResolver(
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
createNotificationData(
|
||||
content = NotificationContent.MessageLike.RoomMessage(
|
||||
|
|
@ -254,7 +254,7 @@ class NotifiableEventResolverTest {
|
|||
|
||||
@Test
|
||||
fun `resolve event message file`() = runTest {
|
||||
val sut = createNotifiableEventResolver(
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
createNotificationData(
|
||||
content = NotificationContent.MessageLike.RoomMessage(
|
||||
|
|
@ -271,7 +271,7 @@ class NotifiableEventResolverTest {
|
|||
|
||||
@Test
|
||||
fun `resolve event message location`() = runTest {
|
||||
val sut = createNotifiableEventResolver(
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
createNotificationData(
|
||||
content = NotificationContent.MessageLike.RoomMessage(
|
||||
|
|
@ -288,7 +288,7 @@ class NotifiableEventResolverTest {
|
|||
|
||||
@Test
|
||||
fun `resolve event message notice`() = runTest {
|
||||
val sut = createNotifiableEventResolver(
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
createNotificationData(
|
||||
content = NotificationContent.MessageLike.RoomMessage(
|
||||
|
|
@ -305,7 +305,7 @@ class NotifiableEventResolverTest {
|
|||
|
||||
@Test
|
||||
fun `resolve event message emote`() = runTest {
|
||||
val sut = createNotifiableEventResolver(
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
createNotificationData(
|
||||
content = NotificationContent.MessageLike.RoomMessage(
|
||||
|
|
@ -322,7 +322,7 @@ class NotifiableEventResolverTest {
|
|||
|
||||
@Test
|
||||
fun `resolve poll`() = runTest {
|
||||
val sut = createNotifiableEventResolver(
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
createNotificationData(
|
||||
content = NotificationContent.MessageLike.Poll(
|
||||
|
|
@ -339,7 +339,7 @@ class NotifiableEventResolverTest {
|
|||
|
||||
@Test
|
||||
fun `resolve RoomMemberContent invite room`() = runTest {
|
||||
val sut = createNotifiableEventResolver(
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
createNotificationData(
|
||||
content = NotificationContent.StateEvent.RoomMemberContent(
|
||||
|
|
@ -372,7 +372,7 @@ class NotifiableEventResolverTest {
|
|||
|
||||
@Test
|
||||
fun `resolve RoomMemberContent invite direct`() = runTest {
|
||||
val sut = createNotifiableEventResolver(
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
createNotificationData(
|
||||
content = NotificationContent.StateEvent.RoomMemberContent(
|
||||
|
|
@ -405,7 +405,7 @@ class NotifiableEventResolverTest {
|
|||
|
||||
@Test
|
||||
fun `resolve RoomMemberContent other`() = runTest {
|
||||
val sut = createNotifiableEventResolver(
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
createNotificationData(
|
||||
content = NotificationContent.StateEvent.RoomMemberContent(
|
||||
|
|
@ -421,7 +421,7 @@ class NotifiableEventResolverTest {
|
|||
|
||||
@Test
|
||||
fun `resolve RoomEncrypted`() = runTest {
|
||||
val sut = createNotifiableEventResolver(
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
createNotificationData(
|
||||
content = NotificationContent.MessageLike.RoomEncrypted
|
||||
|
|
@ -445,7 +445,7 @@ class NotifiableEventResolverTest {
|
|||
|
||||
@Test
|
||||
fun `resolve CallInvite`() = runTest {
|
||||
val sut = createNotifiableEventResolver(
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
createNotificationData(
|
||||
content = NotificationContent.MessageLike.CallInvite(A_USER_ID_2)
|
||||
|
|
@ -517,7 +517,7 @@ class NotifiableEventResolverTest {
|
|||
}
|
||||
|
||||
private fun testNull(content: NotificationContent) = runTest {
|
||||
val sut = createNotifiableEventResolver(
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
createNotificationData(
|
||||
content = content
|
||||
|
|
@ -528,10 +528,10 @@ class NotifiableEventResolverTest {
|
|||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
private fun createNotifiableEventResolver(
|
||||
private fun createDefaultNotifiableEventResolver(
|
||||
notificationService: FakeNotificationService? = FakeNotificationService(),
|
||||
notificationResult: Result<NotificationData?> = Result.success(null),
|
||||
): NotifiableEventResolver {
|
||||
): DefaultNotifiableEventResolver {
|
||||
val context = RuntimeEnvironment.getApplication() as Context
|
||||
notificationService?.givenGetNotificationResult(notificationResult)
|
||||
val matrixClientProvider = FakeMatrixClientProvider(getClient = {
|
||||
|
|
@ -544,7 +544,7 @@ class NotifiableEventResolverTest {
|
|||
val notificationMediaRepoFactory = NotificationMediaRepo.Factory {
|
||||
FakeNotificationMediaRepo()
|
||||
}
|
||||
return NotifiableEventResolver(
|
||||
return DefaultNotifiableEventResolver(
|
||||
stringProvider = AndroidStringProvider(context.resources),
|
||||
clock = FakeSystemClock(),
|
||||
matrixClientProvider = matrixClientProvider,
|
||||
|
|
@ -59,7 +59,6 @@ class DefaultNotificationDrawerManagerTest {
|
|||
fun `cover all APIs`() = runTest {
|
||||
// For now just call all the API. Later, add more valuable tests.
|
||||
val defaultNotificationDrawerManager = createDefaultNotificationDrawerManager()
|
||||
defaultNotificationDrawerManager.notificationStyleChanged()
|
||||
defaultNotificationDrawerManager.clearAllMessagesEvents(A_SESSION_ID, doRender = true)
|
||||
defaultNotificationDrawerManager.clearAllMessagesEvents(A_SESSION_ID, doRender = false)
|
||||
defaultNotificationDrawerManager.clearEvent(A_SESSION_ID, AN_EVENT_ID, doRender = true)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (c) 2024 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.push.impl.notifications
|
||||
|
||||
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.push.impl.notifications.model.NotifiableEvent
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
|
||||
class FakeNotifiableEventResolver(
|
||||
private val notifiableEventResult: (SessionId, RoomId, EventId) -> NotifiableEvent? = { _, _, _ -> lambdaError() }
|
||||
) : NotifiableEventResolver {
|
||||
override suspend fun resolveEvent(sessionId: SessionId, roomId: RoomId, eventId: EventId): NotifiableEvent? {
|
||||
return notifiableEventResult(sessionId, roomId, eventId)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,268 @@
|
|||
/*
|
||||
* Copyright (c) 2024 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.
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalCoroutinesApi::class)
|
||||
|
||||
package io.element.android.libraries.push.impl.push
|
||||
|
||||
import app.cash.turbine.test
|
||||
import io.element.android.libraries.core.meta.BuildMeta
|
||||
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
||||
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.test.AN_EVENT_ID
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
import io.element.android.libraries.matrix.test.A_SECRET
|
||||
import io.element.android.libraries.matrix.test.A_USER_ID
|
||||
import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService
|
||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||
import io.element.android.libraries.push.impl.notifications.FakeNotifiableEventResolver
|
||||
import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent
|
||||
import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent
|
||||
import io.element.android.libraries.push.impl.test.DefaultTestPush
|
||||
import io.element.android.libraries.push.impl.troubleshoot.DiagnosticPushHandler
|
||||
import io.element.android.libraries.pushproviders.api.PushData
|
||||
import io.element.android.libraries.pushstore.api.UserPushStore
|
||||
import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret
|
||||
import io.element.android.libraries.pushstore.test.userpushstore.FakeUserPushStore
|
||||
import io.element.android.libraries.pushstore.test.userpushstore.FakeUserPushStoreFactory
|
||||
import io.element.android.libraries.pushstore.test.userpushstore.clientsecret.FakePushClientSecret
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.lambda.value
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
class DefaultPushHandlerTest {
|
||||
@Test
|
||||
fun `when classical PushData is received, the notification drawer is informed`() = runTest {
|
||||
val aNotifiableMessageEvent = aNotifiableMessageEvent()
|
||||
val notifiableEventResult =
|
||||
lambdaRecorder<SessionId, RoomId, EventId, NotifiableEvent> { _, _, _ -> aNotifiableMessageEvent }
|
||||
val onNotifiableEventReceived = lambdaRecorder<NotifiableEvent, Unit> {}
|
||||
val incrementPushCounterResult = lambdaRecorder<Unit> {}
|
||||
val aPushData = PushData(
|
||||
eventId = AN_EVENT_ID,
|
||||
roomId = A_ROOM_ID,
|
||||
unread = 0,
|
||||
clientSecret = A_SECRET,
|
||||
)
|
||||
val defaultPushHandler = createDefaultPushHandler(
|
||||
onNotifiableEventReceived = onNotifiableEventReceived,
|
||||
notifiableEventResult = notifiableEventResult,
|
||||
pushClientSecret = FakePushClientSecret(
|
||||
getUserIdFromSecretResult = { A_USER_ID }
|
||||
),
|
||||
incrementPushCounterResult = incrementPushCounterResult
|
||||
)
|
||||
defaultPushHandler.handle(aPushData)
|
||||
incrementPushCounterResult.assertions()
|
||||
.isCalledOnce()
|
||||
notifiableEventResult.assertions()
|
||||
.isCalledOnce()
|
||||
.with(value(A_USER_ID), value(A_ROOM_ID), value(AN_EVENT_ID))
|
||||
onNotifiableEventReceived.assertions()
|
||||
.isCalledOnce()
|
||||
.with(value(aNotifiableMessageEvent))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when classical PushData is received, but notifications are disabled, nothing happen`() =
|
||||
runTest {
|
||||
val aNotifiableMessageEvent = aNotifiableMessageEvent()
|
||||
val notifiableEventResult =
|
||||
lambdaRecorder<SessionId, RoomId, EventId, NotifiableEvent> { _, _, _ -> aNotifiableMessageEvent }
|
||||
val onNotifiableEventReceived = lambdaRecorder<NotifiableEvent, Unit> {}
|
||||
val incrementPushCounterResult = lambdaRecorder<Unit> {}
|
||||
val aPushData = PushData(
|
||||
eventId = AN_EVENT_ID,
|
||||
roomId = A_ROOM_ID,
|
||||
unread = 0,
|
||||
clientSecret = A_SECRET,
|
||||
)
|
||||
val defaultPushHandler = createDefaultPushHandler(
|
||||
onNotifiableEventReceived = onNotifiableEventReceived,
|
||||
notifiableEventResult = notifiableEventResult,
|
||||
pushClientSecret = FakePushClientSecret(
|
||||
getUserIdFromSecretResult = { A_USER_ID }
|
||||
),
|
||||
userPushStore = FakeUserPushStore().apply {
|
||||
setNotificationEnabledForDevice(false)
|
||||
},
|
||||
incrementPushCounterResult = incrementPushCounterResult
|
||||
)
|
||||
defaultPushHandler.handle(aPushData)
|
||||
incrementPushCounterResult.assertions()
|
||||
.isCalledOnce()
|
||||
notifiableEventResult.assertions()
|
||||
.isNeverCalled()
|
||||
onNotifiableEventReceived.assertions()
|
||||
.isNeverCalled()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when PushData is received, but client secret is not known, fallback the latest session`() =
|
||||
runTest {
|
||||
val aNotifiableMessageEvent = aNotifiableMessageEvent()
|
||||
val notifiableEventResult =
|
||||
lambdaRecorder<SessionId, RoomId, EventId, NotifiableEvent> { _, _, _ -> aNotifiableMessageEvent }
|
||||
val onNotifiableEventReceived = lambdaRecorder<NotifiableEvent, Unit> {}
|
||||
val incrementPushCounterResult = lambdaRecorder<Unit> {}
|
||||
val aPushData = PushData(
|
||||
eventId = AN_EVENT_ID,
|
||||
roomId = A_ROOM_ID,
|
||||
unread = 0,
|
||||
clientSecret = A_SECRET,
|
||||
)
|
||||
val defaultPushHandler = createDefaultPushHandler(
|
||||
onNotifiableEventReceived = onNotifiableEventReceived,
|
||||
notifiableEventResult = notifiableEventResult,
|
||||
pushClientSecret = FakePushClientSecret(
|
||||
getUserIdFromSecretResult = { null }
|
||||
),
|
||||
matrixAuthenticationService = FakeMatrixAuthenticationService().apply {
|
||||
getLatestSessionIdLambda = { A_USER_ID }
|
||||
},
|
||||
incrementPushCounterResult = incrementPushCounterResult
|
||||
)
|
||||
defaultPushHandler.handle(aPushData)
|
||||
incrementPushCounterResult.assertions()
|
||||
.isCalledOnce()
|
||||
notifiableEventResult.assertions()
|
||||
.isCalledOnce()
|
||||
.with(value(A_USER_ID), value(A_ROOM_ID), value(AN_EVENT_ID))
|
||||
onNotifiableEventReceived.assertions()
|
||||
.isCalledOnce()
|
||||
.with(value(aNotifiableMessageEvent))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when PushData is received, but client secret is not known, and there is no latest session, nothing happen`() =
|
||||
runTest {
|
||||
val aNotifiableMessageEvent = aNotifiableMessageEvent()
|
||||
val notifiableEventResult =
|
||||
lambdaRecorder<SessionId, RoomId, EventId, NotifiableEvent> { _, _, _ -> aNotifiableMessageEvent }
|
||||
val onNotifiableEventReceived = lambdaRecorder<NotifiableEvent, Unit> {}
|
||||
val incrementPushCounterResult = lambdaRecorder<Unit> {}
|
||||
val aPushData = PushData(
|
||||
eventId = AN_EVENT_ID,
|
||||
roomId = A_ROOM_ID,
|
||||
unread = 0,
|
||||
clientSecret = A_SECRET,
|
||||
)
|
||||
val defaultPushHandler = createDefaultPushHandler(
|
||||
onNotifiableEventReceived = onNotifiableEventReceived,
|
||||
notifiableEventResult = notifiableEventResult,
|
||||
pushClientSecret = FakePushClientSecret(
|
||||
getUserIdFromSecretResult = { null }
|
||||
),
|
||||
matrixAuthenticationService = FakeMatrixAuthenticationService().apply {
|
||||
getLatestSessionIdLambda = { null }
|
||||
},
|
||||
incrementPushCounterResult = incrementPushCounterResult
|
||||
)
|
||||
defaultPushHandler.handle(aPushData)
|
||||
incrementPushCounterResult.assertions()
|
||||
.isCalledOnce()
|
||||
notifiableEventResult.assertions()
|
||||
.isNeverCalled()
|
||||
onNotifiableEventReceived.assertions()
|
||||
.isNeverCalled()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when classical PushData is received, but not able to resolve the event, nothing happen`() =
|
||||
runTest {
|
||||
val notifiableEventResult =
|
||||
lambdaRecorder<SessionId, RoomId, EventId, NotifiableEvent?> { _, _, _ -> null }
|
||||
val onNotifiableEventReceived = lambdaRecorder<NotifiableEvent, Unit> {}
|
||||
val incrementPushCounterResult = lambdaRecorder<Unit> {}
|
||||
val aPushData = PushData(
|
||||
eventId = AN_EVENT_ID,
|
||||
roomId = A_ROOM_ID,
|
||||
unread = 0,
|
||||
clientSecret = A_SECRET,
|
||||
)
|
||||
val defaultPushHandler = createDefaultPushHandler(
|
||||
onNotifiableEventReceived = onNotifiableEventReceived,
|
||||
notifiableEventResult = notifiableEventResult,
|
||||
buildMeta = aBuildMeta(
|
||||
// Also test `lowPrivacyLoggingEnabled = false` here
|
||||
lowPrivacyLoggingEnabled = false
|
||||
),
|
||||
pushClientSecret = FakePushClientSecret(
|
||||
getUserIdFromSecretResult = { A_USER_ID }
|
||||
),
|
||||
incrementPushCounterResult = incrementPushCounterResult
|
||||
)
|
||||
defaultPushHandler.handle(aPushData)
|
||||
incrementPushCounterResult.assertions()
|
||||
.isCalledOnce()
|
||||
notifiableEventResult.assertions()
|
||||
.isCalledOnce()
|
||||
.with(value(A_USER_ID), value(A_ROOM_ID), value(AN_EVENT_ID))
|
||||
onNotifiableEventReceived.assertions()
|
||||
.isNeverCalled()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when diagnostic PushData is received, the diagnostic push handler is informed `() =
|
||||
runTest {
|
||||
val aPushData = PushData(
|
||||
eventId = DefaultTestPush.TEST_EVENT_ID,
|
||||
roomId = A_ROOM_ID,
|
||||
unread = 0,
|
||||
clientSecret = A_SECRET,
|
||||
)
|
||||
val diagnosticPushHandler = DiagnosticPushHandler()
|
||||
val defaultPushHandler = createDefaultPushHandler(
|
||||
diagnosticPushHandler = diagnosticPushHandler,
|
||||
incrementPushCounterResult = { }
|
||||
)
|
||||
diagnosticPushHandler.state.test {
|
||||
defaultPushHandler.handle(aPushData)
|
||||
awaitItem()
|
||||
}
|
||||
}
|
||||
|
||||
private fun createDefaultPushHandler(
|
||||
onNotifiableEventReceived: (NotifiableEvent) -> Unit = { lambdaError() },
|
||||
notifiableEventResult: (SessionId, RoomId, EventId) -> NotifiableEvent? = { _, _, _ -> lambdaError() },
|
||||
incrementPushCounterResult: () -> Unit = { lambdaError() },
|
||||
userPushStore: UserPushStore = FakeUserPushStore(),
|
||||
pushClientSecret: PushClientSecret = FakePushClientSecret(),
|
||||
buildMeta: BuildMeta = aBuildMeta(),
|
||||
matrixAuthenticationService: MatrixAuthenticationService = FakeMatrixAuthenticationService(),
|
||||
diagnosticPushHandler: DiagnosticPushHandler = DiagnosticPushHandler(),
|
||||
): DefaultPushHandler {
|
||||
return DefaultPushHandler(
|
||||
onNotifiableEventReceived = FakeOnNotifiableEventReceived(onNotifiableEventReceived),
|
||||
notifiableEventResolver = FakeNotifiableEventResolver(notifiableEventResult),
|
||||
incrementPushDataStore = object : IncrementPushDataStore {
|
||||
override suspend fun incrementPushCounter() {
|
||||
incrementPushCounterResult()
|
||||
}
|
||||
},
|
||||
userPushStoreFactory = FakeUserPushStoreFactory { userPushStore },
|
||||
pushClientSecret = pushClientSecret,
|
||||
buildMeta = buildMeta,
|
||||
matrixAuthenticationService = matrixAuthenticationService,
|
||||
diagnosticPushHandler = diagnosticPushHandler,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright (c) 2024 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.push.impl.push
|
||||
|
||||
import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent
|
||||
|
||||
class FakeOnNotifiableEventReceived(
|
||||
private val onNotifiableEventReceivedResult: (NotifiableEvent) -> Unit,
|
||||
) : OnNotifiableEventReceived {
|
||||
override fun onNotifiableEventReceived(notifiableEvent: NotifiableEvent) {
|
||||
onNotifiableEventReceivedResult(notifiableEvent)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* Copyright (c) 2024 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.push.impl.pushgateway
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.push.api.gateway.PushGatewayFailure
|
||||
import io.element.android.libraries.push.impl.test.DefaultTestPush
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Assert.assertThrows
|
||||
import org.junit.Test
|
||||
|
||||
class DefaultPushGatewayNotifyRequestTest {
|
||||
@Test
|
||||
fun `notify success`() = runTest {
|
||||
val factory = FakePushGatewayApiFactory(
|
||||
notifyResponse = {
|
||||
PushGatewayNotifyResponse(
|
||||
rejectedPushKeys = emptyList()
|
||||
)
|
||||
}
|
||||
)
|
||||
val pushGatewayNotifyRequest = DefaultPushGatewayNotifyRequest(
|
||||
pushGatewayApiFactory = factory,
|
||||
)
|
||||
pushGatewayNotifyRequest.execute(
|
||||
PushGatewayNotifyRequest.Params(
|
||||
url = "aUrl",
|
||||
appId = "anAppId",
|
||||
pushKey = "aPushKey",
|
||||
eventId = DefaultTestPush.TEST_EVENT_ID,
|
||||
roomId = DefaultTestPush.TEST_ROOM_ID,
|
||||
)
|
||||
)
|
||||
assertThat(factory.baseUrlParameter).isEqualTo("aUrl")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `notify success, url is stripped`() = runTest {
|
||||
val factory = FakePushGatewayApiFactory(
|
||||
notifyResponse = {
|
||||
PushGatewayNotifyResponse(
|
||||
rejectedPushKeys = emptyList()
|
||||
)
|
||||
}
|
||||
)
|
||||
val pushGatewayNotifyRequest = DefaultPushGatewayNotifyRequest(
|
||||
pushGatewayApiFactory = factory,
|
||||
)
|
||||
pushGatewayNotifyRequest.execute(
|
||||
PushGatewayNotifyRequest.Params(
|
||||
url = "aUrl" + PushGatewayConfig.URI_PUSH_GATEWAY_PREFIX_PATH,
|
||||
appId = "anAppId",
|
||||
pushKey = "aPushKey",
|
||||
eventId = DefaultTestPush.TEST_EVENT_ID,
|
||||
roomId = DefaultTestPush.TEST_ROOM_ID,
|
||||
)
|
||||
)
|
||||
assertThat(factory.baseUrlParameter).isEqualTo("aUrl")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `notify with rejected push key should throw expected Exception`() {
|
||||
val factory = FakePushGatewayApiFactory(
|
||||
notifyResponse = {
|
||||
PushGatewayNotifyResponse(
|
||||
rejectedPushKeys = listOf("aPushKey")
|
||||
)
|
||||
}
|
||||
)
|
||||
val pushGatewayNotifyRequest = DefaultPushGatewayNotifyRequest(
|
||||
pushGatewayApiFactory = factory,
|
||||
)
|
||||
assertThrows(PushGatewayFailure.PusherRejected::class.java) {
|
||||
runTest {
|
||||
pushGatewayNotifyRequest.execute(
|
||||
PushGatewayNotifyRequest.Params(
|
||||
url = "aUrl",
|
||||
appId = "anAppId",
|
||||
pushKey = "aPushKey",
|
||||
eventId = DefaultTestPush.TEST_EVENT_ID,
|
||||
roomId = DefaultTestPush.TEST_ROOM_ID,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
assertThat(factory.baseUrlParameter).isEqualTo("aUrl")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright (c) 2024 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.push.impl.pushgateway
|
||||
|
||||
class FakePushGatewayApiFactory(
|
||||
private val notifyResponse: () -> PushGatewayNotifyResponse
|
||||
) : PushGatewayApiFactory {
|
||||
var baseUrlParameter: String? = null
|
||||
private set
|
||||
|
||||
override fun create(baseUrl: String): PushGatewayAPI {
|
||||
baseUrlParameter = baseUrl
|
||||
return FakePushGatewayAPI(notifyResponse)
|
||||
}
|
||||
}
|
||||
|
||||
class FakePushGatewayAPI(
|
||||
private val notifyResponse: () -> PushGatewayNotifyResponse
|
||||
) : PushGatewayAPI {
|
||||
override suspend fun notify(body: PushGatewayNotifyBody): PushGatewayNotifyResponse {
|
||||
return notifyResponse()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright (c) 2024 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.push.impl.test
|
||||
|
||||
import io.element.android.appconfig.PushConfig
|
||||
import io.element.android.libraries.push.impl.pushgateway.PushGatewayNotifyRequest
|
||||
import io.element.android.libraries.pushproviders.api.CurrentUserPushConfig
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.lambda.value
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
class DefaultTestPushTest {
|
||||
@Test
|
||||
fun `test DefaultTestPush`() = runTest {
|
||||
val executeResult = lambdaRecorder<PushGatewayNotifyRequest.Params, Unit> { }
|
||||
val defaultTestPush = DefaultTestPush(
|
||||
pushGatewayNotifyRequest = FakePushGatewayNotifyRequest(
|
||||
executeResult = executeResult,
|
||||
)
|
||||
)
|
||||
val aConfig = CurrentUserPushConfig(
|
||||
url = "aUrl",
|
||||
pushKey = "aPushKey",
|
||||
)
|
||||
defaultTestPush.execute(aConfig)
|
||||
executeResult.assertions()
|
||||
.isCalledOnce()
|
||||
.with(
|
||||
value(
|
||||
PushGatewayNotifyRequest.Params(
|
||||
url = aConfig.url,
|
||||
appId = PushConfig.PUSHER_APP_ID,
|
||||
pushKey = aConfig.pushKey,
|
||||
eventId = DefaultTestPush.TEST_EVENT_ID,
|
||||
roomId = DefaultTestPush.TEST_ROOM_ID,
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright (c) 2024 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.push.impl.test
|
||||
|
||||
import io.element.android.libraries.push.impl.pushgateway.PushGatewayNotifyRequest
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
|
||||
class FakePushGatewayNotifyRequest(
|
||||
private val executeResult: (PushGatewayNotifyRequest.Params) -> Unit = { lambdaError() }
|
||||
) : PushGatewayNotifyRequest {
|
||||
override suspend fun execute(params: PushGatewayNotifyRequest.Params) {
|
||||
executeResult(params)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright (c) 2024 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.push.impl.test
|
||||
|
||||
import io.element.android.libraries.pushproviders.api.CurrentUserPushConfig
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
|
||||
class FakeTestPush(
|
||||
private val executeResult: (CurrentUserPushConfig) -> Unit = { lambdaError() }
|
||||
) : TestPush {
|
||||
override suspend fun execute(config: CurrentUserPushConfig) {
|
||||
executeResult(config)
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue