Merge branch 'release/0.7.0' into main
This commit is contained in:
commit
90f704b8ae
197 changed files with 1305 additions and 875 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -65,6 +65,9 @@ captures/
|
||||||
.idea/shelf
|
.idea/shelf
|
||||||
.idea/sonarlint
|
.idea/sonarlint
|
||||||
|
|
||||||
|
# .kotlin folder
|
||||||
|
.kotlin
|
||||||
|
|
||||||
# Keystore files
|
# Keystore files
|
||||||
# Uncomment the following lines if you do not want to check your keystore files in.
|
# Uncomment the following lines if you do not want to check your keystore files in.
|
||||||
#*.jks
|
#*.jks
|
||||||
|
|
|
||||||
2
.idea/kotlinc.xml
generated
2
.idea/kotlinc.xml
generated
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="KotlinJpsPluginSettings">
|
<component name="KotlinJpsPluginSettings">
|
||||||
<option name="version" value="1.9.25" />
|
<option name="version" value="2.0.20" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
74
CHANGES.md
74
CHANGES.md
|
|
@ -1,3 +1,77 @@
|
||||||
|
Changes in Element X v0.6.5 (2024-10-09)
|
||||||
|
========================================
|
||||||
|
|
||||||
|
## What's Changed
|
||||||
|
### ✨ Features
|
||||||
|
* Add developer setting to hide images in the timeline by @bmarty in https://github.com/element-hq/element-x-android/pull/3592
|
||||||
|
* Warn the user when unverified user has changed their identity by @bmarty in https://github.com/element-hq/element-x-android/pull/3621
|
||||||
|
### 🙌 Improvements
|
||||||
|
* Handle no network error when starting Element Call. by @bmarty in https://github.com/element-hq/element-x-android/pull/3527
|
||||||
|
### 🐛 Bugfixes
|
||||||
|
* Fix room settings not treating unencrypted DMs as DMs by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3545
|
||||||
|
* Fix crash when aspectRatio is null. by @bmarty in https://github.com/element-hq/element-x-android/pull/3561
|
||||||
|
* Don't delete uploaded logs by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3540
|
||||||
|
* Don't display security banner for unknown RecoveryState by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3579
|
||||||
|
* Fix the logic of the room list banner state by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3615
|
||||||
|
### 🗣 Translations
|
||||||
|
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/3560
|
||||||
|
* Sync Strings - import translations to Persian by @ElementBot in https://github.com/element-hq/element-x-android/pull/3612
|
||||||
|
### 🧱 Build
|
||||||
|
* Introduce ModulesConfig by @bmarty in https://github.com/element-hq/element-x-android/pull/3530
|
||||||
|
* Centralise the DI code generation logic by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3562
|
||||||
|
* Update Gradle impl module template with `setupAnvil()` call by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3563
|
||||||
|
* Use Anvil KSP instead of the Square KAPT one by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3564
|
||||||
|
* Upgrade the used JDK in the project to v21 by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3582
|
||||||
|
* Merge unit, screenshot tests and coverage in a single CI call by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3593
|
||||||
|
* Disable configuration cache in the CI by default by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3601
|
||||||
|
* Fix screenshot recording in CI by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3607
|
||||||
|
* Ensure the CI compile and execute all the unit tests. by @bmarty in https://github.com/element-hq/element-x-android/pull/3617
|
||||||
|
### Dependency upgrades
|
||||||
|
* Update dependency androidx.compose:compose-bom to v2024.09.00 by @renovate in https://github.com/element-hq/element-x-android/pull/3399
|
||||||
|
* Update dependency androidx.compose:compose-bom to v2024.09.02 by @renovate in https://github.com/element-hq/element-x-android/pull/3544
|
||||||
|
* Update dependency io.element.android:compound-android to v0.1.0 by @renovate in https://github.com/element-hq/element-x-android/pull/3524
|
||||||
|
* Update dependency com.google.firebase:firebase-bom to v33.3.0 by @renovate in https://github.com/element-hq/element-x-android/pull/3549
|
||||||
|
* Update dependency org.maplibre.gl:android-sdk to v11.5.0 by @renovate in https://github.com/element-hq/element-x-android/pull/3550
|
||||||
|
* Update dependency org.maplibre.gl:android-plugin-annotation-v9 to v3.0.1 by @renovate in https://github.com/element-hq/element-x-android/pull/3505
|
||||||
|
* Update dependency androidx.webkit:webkit to v1.12.0 by @renovate in https://github.com/element-hq/element-x-android/pull/3520
|
||||||
|
* Update dependency com.posthog:posthog-android to v3.7.5 by @renovate in https://github.com/element-hq/element-x-android/pull/3546
|
||||||
|
* Update gradle-update/update-gradle-wrapper-action action to v2 by @renovate in https://github.com/element-hq/element-x-android/pull/3551
|
||||||
|
* Update dependency com.lemonappdev:konsist to v0.16.1 by @renovate in https://github.com/element-hq/element-x-android/pull/3371
|
||||||
|
* Update android.gradle.plugin to v8.6.1 by @renovate in https://github.com/element-hq/element-x-android/pull/3504
|
||||||
|
* Update dependency org.matrix.rustcomponents:sdk-android to v0.2.49 by @renovate in https://github.com/element-hq/element-x-android/pull/3553
|
||||||
|
* Update lifecycle to v2.8.6 by @renovate in https://github.com/element-hq/element-x-android/pull/3398
|
||||||
|
* Update dependency com.google.accompanist:accompanist-permissions to v0.36.0 by @renovate in https://github.com/element-hq/element-x-android/pull/3400
|
||||||
|
* Update dependency org.matrix.rustcomponents:sdk-android to v0.2.50 by @renovate in https://github.com/element-hq/element-x-android/pull/3565
|
||||||
|
* Update dependency com.google.firebase:firebase-bom to v33.4.0 by @renovate in https://github.com/element-hq/element-x-android/pull/3578
|
||||||
|
* Update android.gradle.plugin to v8.7.0 by @renovate in https://github.com/element-hq/element-x-android/pull/3577
|
||||||
|
* Update dependency com.posthog:posthog-android to v3.8.0 by @renovate in https://github.com/element-hq/element-x-android/pull/3591
|
||||||
|
* dependency: Bump rust sdk to 0.2.51 by @BillCarsonFr in https://github.com/element-hq/element-x-android/pull/3602
|
||||||
|
* chore(deps): update dependencyanalysis to v2.1.3 by @renovate in https://github.com/element-hq/element-x-android/pull/3559
|
||||||
|
* Update wysiwyg to v2.37.13 by @renovate in https://github.com/element-hq/element-x-android/pull/3596
|
||||||
|
* fix(deps): update dependency io.nlopez.compose.rules:detekt to v0.4.15 by @renovate in https://github.com/element-hq/element-x-android/pull/3595
|
||||||
|
* fix(deps): update dependency com.google.testparameterinjector:test-parameter-injector to v1.18 by @renovate in https://github.com/element-hq/element-x-android/pull/3606
|
||||||
|
* fix(deps): update dependency com.squareup:kotlinpoet-ksp to v1.18.1 by @renovate in https://github.com/element-hq/element-x-android/pull/3580
|
||||||
|
* Update dependency org.matrix.rustcomponents:sdk-android to v0.2.52 by @renovate in https://github.com/element-hq/element-x-android/pull/3619
|
||||||
|
* SDK 0.2.53 19b9a73ecc3e31d502dbf0c5850bfdfaddf02afe by @bmarty in https://github.com/element-hq/element-x-android/pull/3622
|
||||||
|
* Update dependency org.maplibre.gl:android-sdk to v11.5.1 by @renovate in https://github.com/element-hq/element-x-android/pull/3608
|
||||||
|
### Others
|
||||||
|
* rename invisible flag to onlySignedDeviceIsolation flag by @BillCarsonFr in https://github.com/element-hq/element-x-android/pull/3542
|
||||||
|
* Fix image viewer glitch by @ganfra in https://github.com/element-hq/element-x-android/pull/3537
|
||||||
|
* Prefix message sent by the current user by `You` instead of the sender name. by @bmarty in https://github.com/element-hq/element-x-android/pull/3547
|
||||||
|
* timeline : remove animateItem by @ganfra in https://github.com/element-hq/element-x-android/pull/3548
|
||||||
|
* Fix a couple of build-time warnings in Gradle output by @frebib in https://github.com/element-hq/element-x-android/pull/3349
|
||||||
|
* Use MSC2530 filename when loading media by @frebib in https://github.com/element-hq/element-x-android/pull/3567
|
||||||
|
* Prevent crash with duplicate room suggestion by @frebib in https://github.com/element-hq/element-x-android/pull/3576
|
||||||
|
* Add unit tests on TimelineItemsSubscriber by @bmarty in https://github.com/element-hq/element-x-android/pull/3554
|
||||||
|
* Fix tests on develop by @bmarty in https://github.com/element-hq/element-x-android/pull/3585
|
||||||
|
* Timeline better jump to behaviours by @ganfra in https://github.com/element-hq/element-x-android/pull/3597
|
||||||
|
* Fix building the app using a local SDK. by @bmarty in https://github.com/element-hq/element-x-android/pull/3604
|
||||||
|
* crypto: Use OnlySigned isolation flag to setup decryption trust req. by @BillCarsonFr in https://github.com/element-hq/element-x-android/pull/3569
|
||||||
|
* Fix black-on-black status bars with hidden media by @frebib in https://github.com/element-hq/element-x-android/pull/3611
|
||||||
|
* Remove supportSlidingSync boolean. by @bmarty in https://github.com/element-hq/element-x-android/pull/3609
|
||||||
|
* Ensure that `Presenter`s do not depend on other presenters. by @bmarty in https://github.com/element-hq/element-x-android/pull/3618
|
||||||
|
* Do not render pin violation in clear rooms. by @bmarty in https://github.com/element-hq/element-x-android/pull/3630
|
||||||
|
|
||||||
Changes in Element X v0.6.4 (2024-09-25)
|
Changes in Element X v0.6.4 (2024-09-25)
|
||||||
========================================
|
========================================
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
package io.element.android.appconfig
|
package io.element.android.appconfig
|
||||||
|
|
||||||
object LearnMoreConfig {
|
object LearnMoreConfig {
|
||||||
|
const val ENCRYPTION_URL: String = "https://element.io/help#encryption"
|
||||||
const val SECURE_BACKUP_URL: String = "https://element.io/help#encryption5"
|
const val SECURE_BACKUP_URL: String = "https://element.io/help#encryption5"
|
||||||
const val IDENTITY_CHANGE_URL: String = "https://element.io/help#encryption18"
|
const val IDENTITY_CHANGE_URL: String = "https://element.io/help#encryption18"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,8 @@ import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
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.RoomId
|
||||||
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
|
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
|
||||||
|
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
|
||||||
|
import io.element.android.libraries.matrix.api.getRoomInfoFlow
|
||||||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||||
import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias
|
import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
|
|
@ -120,12 +122,9 @@ class RoomFlowNode @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun subscribeToRoomInfoFlow(roomId: RoomId, serverNames: List<String>) {
|
private fun subscribeToRoomInfoFlow(roomId: RoomId, serverNames: List<String>) {
|
||||||
val roomInfoFlow = client.getRoomInfoFlow(
|
val roomInfoFlow = client.getRoomInfoFlow(roomIdOrAlias = roomId.toRoomIdOrAlias())
|
||||||
roomId = roomId
|
val isSpaceFlow = roomInfoFlow.map { it.getOrNull()?.isSpace.orFalse() }.distinctUntilChanged()
|
||||||
).map { it.getOrNull() }
|
val currentMembershipFlow = roomInfoFlow.map { it.getOrNull()?.currentUserMembership }.distinctUntilChanged()
|
||||||
|
|
||||||
val isSpaceFlow = roomInfoFlow.map { it?.isSpace.orFalse() }.distinctUntilChanged()
|
|
||||||
val currentMembershipFlow = roomInfoFlow.map { it?.currentUserMembership }.distinctUntilChanged()
|
|
||||||
combine(currentMembershipFlow, isSpaceFlow) { membership, isSpace ->
|
combine(currentMembershipFlow, isSpaceFlow) { membership, isSpace ->
|
||||||
Timber.d("Room membership: $membership")
|
Timber.d("Room membership: $membership")
|
||||||
when (membership) {
|
when (membership) {
|
||||||
|
|
|
||||||
|
|
@ -478,7 +478,7 @@ class LoggedInPresenterTest {
|
||||||
distributors = listOf(Distributor("aDistributorValue1", "aDistributorName1")),
|
distributors = listOf(Distributor("aDistributorValue1", "aDistributorName1")),
|
||||||
currentDistributor = { null },
|
currentDistributor = { null },
|
||||||
),
|
),
|
||||||
registerWithLambda: suspend (MatrixClient, PushProvider, Distributor) -> Result<Unit> = { _, _, _ ->
|
registerWithLambda: (MatrixClient, PushProvider, Distributor) -> Result<Unit> = { _, _, _ ->
|
||||||
Result.success(Unit)
|
Result.success(Unit)
|
||||||
},
|
},
|
||||||
selectPushProviderLambda: (MatrixClient, PushProvider) -> Unit = { _, _ -> lambdaError() },
|
selectPushProviderLambda: (MatrixClient, PushProvider) -> Unit = { _, _ -> lambdaError() },
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ plugins {
|
||||||
alias(libs.plugins.android.application) apply false
|
alias(libs.plugins.android.application) apply false
|
||||||
alias(libs.plugins.android.library) apply false
|
alias(libs.plugins.android.library) apply false
|
||||||
alias(libs.plugins.kotlin.android) apply false
|
alias(libs.plugins.kotlin.android) apply false
|
||||||
|
alias(libs.plugins.compose.compiler) apply false
|
||||||
alias(libs.plugins.ksp) apply false
|
alias(libs.plugins.ksp) apply false
|
||||||
alias(libs.plugins.anvil) apply false
|
alias(libs.plugins.anvil) apply false
|
||||||
alias(libs.plugins.kotlin.jvm) apply false
|
alias(libs.plugins.kotlin.jvm) apply false
|
||||||
|
|
@ -82,20 +83,15 @@ allprojects {
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
|
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
|
||||||
// Warnings are potential errors, so stop ignoring them
|
compilerOptions {
|
||||||
// This is disabled by default, but the CI will enforce this.
|
// Warnings are potential errors, so stop ignoring them
|
||||||
// You can override by passing `-PallWarningsAsErrors=true` in the command line
|
// This is disabled by default, but the CI will enforce this.
|
||||||
// Or add a line with "allWarningsAsErrors=true" in your ~/.gradle/gradle.properties file
|
// You can override by passing `-PallWarningsAsErrors=true` in the command line
|
||||||
kotlinOptions.allWarningsAsErrors = project.properties["allWarningsAsErrors"] == "true"
|
// Or add a line with "allWarningsAsErrors=true" in your ~/.gradle/gradle.properties file
|
||||||
|
allWarningsAsErrors = project.properties["allWarningsAsErrors"] == "true"
|
||||||
|
|
||||||
kotlinOptions {
|
|
||||||
/*
|
|
||||||
// Uncomment to suppress Compose Kotlin compiler compatibility warning
|
// Uncomment to suppress Compose Kotlin compiler compatibility warning
|
||||||
freeCompilerArgs += listOf(
|
// freeCompilerArgs.addAll(listOf("-P", "plugin:androidx.compose.compiler.plugins.kotlin:suppressKotlinVersionCompatibilityCheck=true"))
|
||||||
"-P",
|
|
||||||
"plugin:androidx.compose.compiler.plugins.kotlin:suppressKotlinVersionCompatibilityCheck=true"
|
|
||||||
)
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -192,19 +188,23 @@ subprojects {
|
||||||
|
|
||||||
subprojects {
|
subprojects {
|
||||||
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
|
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
|
||||||
kotlinOptions {
|
compilerOptions {
|
||||||
if (project.findProperty("composeCompilerReports") == "true") {
|
if (project.findProperty("composeCompilerReports") == "true") {
|
||||||
freeCompilerArgs += listOf(
|
freeCompilerArgs.addAll(
|
||||||
"-P",
|
listOf(
|
||||||
"plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" +
|
"-P",
|
||||||
"${project.layout.buildDirectory.asFile.get().absolutePath}/compose_compiler"
|
"plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" +
|
||||||
|
"${project.layout.buildDirectory.asFile.get().absolutePath}/compose_compiler"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (project.findProperty("composeCompilerMetrics") == "true") {
|
if (project.findProperty("composeCompilerMetrics") == "true") {
|
||||||
freeCompilerArgs += listOf(
|
freeCompilerArgs.addAll(
|
||||||
"-P",
|
listOf(
|
||||||
"plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=" +
|
"-P",
|
||||||
"${project.layout.buildDirectory.asFile.get().absolutePath}/compose_compiler"
|
"plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=" +
|
||||||
|
"${project.layout.buildDirectory.asFile.get().absolutePath}/compose_compiler"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
fastlane/metadata/android/en-US/changelogs/40007000.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/40007000.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
Main changes in this version: bug fixes and performance improvement.
|
||||||
|
Full changelog: https://github.com/element-hq/element-x-android/releases
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import extension.setupAnvil
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright 2023, 2024 New Vector Ltd.
|
* Copyright 2023, 2024 New Vector Ltd.
|
||||||
*
|
*
|
||||||
|
|
@ -7,13 +9,14 @@
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("io.element.android-library")
|
id("io.element.android-library")
|
||||||
alias(libs.plugins.anvil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "io.element.android.features.cachecleaner.api"
|
namespace = "io.element.android.features.cachecleaner.api"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setupAnvil()
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(projects.libraries.architecture)
|
implementation(projects.libraries.architecture)
|
||||||
implementation(libs.androidx.startup)
|
implementation(libs.androidx.startup)
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,9 @@
|
||||||
package io.element.android.features.call.impl.ui
|
package io.element.android.features.call.impl.ui
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.util.Log
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import android.webkit.ConsoleMessage
|
||||||
import android.webkit.PermissionRequest
|
import android.webkit.PermissionRequest
|
||||||
import android.webkit.WebChromeClient
|
import android.webkit.WebChromeClient
|
||||||
import android.webkit.WebView
|
import android.webkit.WebView
|
||||||
|
|
@ -43,6 +45,7 @@ import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||||
import io.element.android.libraries.designsystem.theme.components.Text
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
typealias RequestPermissionCallback = (Array<String>) -> Unit
|
typealias RequestPermissionCallback = (Array<String>) -> Unit
|
||||||
|
|
||||||
|
|
@ -189,6 +192,25 @@ private fun WebView.setup(
|
||||||
override fun onPermissionRequest(request: PermissionRequest) {
|
override fun onPermissionRequest(request: PermissionRequest) {
|
||||||
onPermissionsRequested(request)
|
onPermissionsRequested(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onConsoleMessage(consoleMessage: ConsoleMessage): Boolean {
|
||||||
|
val priority = when (consoleMessage.messageLevel()) {
|
||||||
|
ConsoleMessage.MessageLevel.ERROR -> Log.ERROR
|
||||||
|
ConsoleMessage.MessageLevel.WARNING -> Log.WARN
|
||||||
|
else -> Log.DEBUG
|
||||||
|
}
|
||||||
|
Timber.tag("WebView").log(
|
||||||
|
priority = priority,
|
||||||
|
message = buildString {
|
||||||
|
append(consoleMessage.sourceId())
|
||||||
|
append(":")
|
||||||
|
append(consoleMessage.lineNumber())
|
||||||
|
append(" ")
|
||||||
|
append(consoleMessage.message())
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -109,12 +109,7 @@ class CallScreenPresenterTest {
|
||||||
assertThat(initialState.isInWidgetMode).isTrue()
|
assertThat(initialState.isInWidgetMode).isTrue()
|
||||||
assertThat(widgetProvider.getWidgetCalled).isTrue()
|
assertThat(widgetProvider.getWidgetCalled).isTrue()
|
||||||
assertThat(widgetDriver.runCalledCount).isEqualTo(1)
|
assertThat(widgetDriver.runCalledCount).isEqualTo(1)
|
||||||
// Called several times because of the recomposition
|
analyticsLambda.assertions().isCalledOnce().with(value(MobileScreen.ScreenName.RoomCall))
|
||||||
analyticsLambda.assertions().isCalledExactly(2)
|
|
||||||
.withSequence(
|
|
||||||
listOf(value(MobileScreen.ScreenName.RoomCall)),
|
|
||||||
listOf(value(MobileScreen.ScreenName.RoomCall))
|
|
||||||
)
|
|
||||||
sendCallNotificationIfNeededLambda.assertions().isCalledOnce()
|
sendCallNotificationIfNeededLambda.assertions().isCalledOnce()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import extension.setupAnvil
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright 2024 New Vector Ltd.
|
* Copyright 2024 New Vector Ltd.
|
||||||
*
|
*
|
||||||
|
|
@ -6,13 +8,14 @@
|
||||||
*/
|
*/
|
||||||
plugins {
|
plugins {
|
||||||
id("io.element.android-library")
|
id("io.element.android-library")
|
||||||
alias(libs.plugins.anvil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "io.element.android.features.enterprise.impl"
|
namespace = "io.element.android.features.enterprise.impl"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setupAnvil()
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(projects.anvilannotations)
|
implementation(projects.anvilannotations)
|
||||||
api(projects.features.enterprise.api)
|
api(projects.features.enterprise.api)
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import extension.setupAnvil
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright 2024 New Vector Ltd.
|
* Copyright 2024 New Vector Ltd.
|
||||||
*
|
*
|
||||||
|
|
@ -7,7 +9,6 @@
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("io.element.android-compose-library")
|
id("io.element.android-compose-library")
|
||||||
alias(libs.plugins.anvil)
|
|
||||||
id("kotlin-parcelize")
|
id("kotlin-parcelize")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -15,6 +16,8 @@ android {
|
||||||
namespace = "io.element.android.features.ftue.test"
|
namespace = "io.element.android.features.ftue.test"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setupAnvil()
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(projects.features.ftue.api)
|
implementation(projects.features.ftue.api)
|
||||||
implementation(projects.tests.testutils)
|
implementation(projects.tests.testutils)
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,7 @@ class AcceptDeclineInvitePresenterTest {
|
||||||
InternalAcceptDeclineInviteEvents.ConfirmDeclineInvite
|
InternalAcceptDeclineInviteEvents.ConfirmDeclineInvite
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
skipItems(1)
|
skipItems(2)
|
||||||
awaitItem().also { state ->
|
awaitItem().also { state ->
|
||||||
assertThat(state.declineAction).isInstanceOf(AsyncAction.Failure::class.java)
|
assertThat(state.declineAction).isInstanceOf(AsyncAction.Failure::class.java)
|
||||||
state.eventSink(
|
state.eventSink(
|
||||||
|
|
@ -147,7 +147,7 @@ class AcceptDeclineInvitePresenterTest {
|
||||||
InternalAcceptDeclineInviteEvents.ConfirmDeclineInvite
|
InternalAcceptDeclineInviteEvents.ConfirmDeclineInvite
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
skipItems(1)
|
skipItems(2)
|
||||||
awaitItem().also { state ->
|
awaitItem().also { state ->
|
||||||
assertThat(state.declineAction).isInstanceOf(AsyncAction.Success::class.java)
|
assertThat(state.declineAction).isInstanceOf(AsyncAction.Success::class.java)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,8 @@ import io.element.android.libraries.core.meta.BuildMeta
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
|
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
|
||||||
|
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
|
||||||
|
import io.element.android.libraries.matrix.api.getRoomInfoFlow
|
||||||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||||
import io.element.android.libraries.matrix.api.room.MatrixRoomInfo
|
import io.element.android.libraries.matrix.api.room.MatrixRoomInfo
|
||||||
import io.element.android.libraries.matrix.api.room.RoomType
|
import io.element.android.libraries.matrix.api.room.RoomType
|
||||||
|
|
@ -70,7 +72,7 @@ class JoinRoomPresenter @AssistedInject constructor(
|
||||||
override fun present(): JoinRoomState {
|
override fun present(): JoinRoomState {
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
var retryCount by remember { mutableIntStateOf(0) }
|
var retryCount by remember { mutableIntStateOf(0) }
|
||||||
val roomInfo by matrixClient.getRoomInfoFlow(roomId).collectAsState(initial = Optional.empty())
|
val roomInfo by matrixClient.getRoomInfoFlow(roomId.toRoomIdOrAlias()).collectAsState(initial = Optional.empty())
|
||||||
val joinAction: MutableState<AsyncAction<Unit>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
|
val joinAction: MutableState<AsyncAction<Unit>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
|
||||||
val knockAction: MutableState<AsyncAction<Unit>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
|
val knockAction: MutableState<AsyncAction<Unit>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
|
||||||
val contentState by produceState<ContentState>(
|
val contentState by produceState<ContentState>(
|
||||||
|
|
@ -204,7 +206,7 @@ internal fun MatrixRoomInfo.toContentState(): ContentState {
|
||||||
name = name,
|
name = name,
|
||||||
topic = topic,
|
topic = topic,
|
||||||
alias = canonicalAlias,
|
alias = canonicalAlias,
|
||||||
numberOfMembers = activeMembersCount,
|
numberOfMembers = activeMembersCount.toLong(),
|
||||||
isDm = isDm,
|
isDm = isDm,
|
||||||
roomType = if (isSpace) RoomType.Space else RoomType.Room,
|
roomType = if (isSpace) RoomType.Space else RoomType.Room,
|
||||||
roomAvatarUrl = avatarUrl,
|
roomAvatarUrl = avatarUrl,
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ 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.core.UserId
|
||||||
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
|
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
|
||||||
import io.element.android.libraries.matrix.api.room.RoomType
|
import io.element.android.libraries.matrix.api.room.RoomType
|
||||||
import io.element.android.libraries.matrix.api.room.isDm
|
|
||||||
import io.element.android.libraries.matrix.ui.model.InviteSender
|
import io.element.android.libraries.matrix.ui.model.InviteSender
|
||||||
|
|
||||||
open class JoinRoomStateProvider : PreviewParameterProvider<JoinRoomState> {
|
open class JoinRoomStateProvider : PreviewParameterProvider<JoinRoomState> {
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,8 @@ import io.element.android.libraries.matrix.test.A_ROOM_NAME
|
||||||
import io.element.android.libraries.matrix.test.A_SERVER_LIST
|
import io.element.android.libraries.matrix.test.A_SERVER_LIST
|
||||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||||
import io.element.android.libraries.matrix.test.room.aRoomInfo
|
|
||||||
import io.element.android.libraries.matrix.test.room.aRoomMember
|
import io.element.android.libraries.matrix.test.room.aRoomMember
|
||||||
|
import io.element.android.libraries.matrix.test.room.aRoomSummary
|
||||||
import io.element.android.libraries.matrix.test.room.join.FakeJoinRoom
|
import io.element.android.libraries.matrix.test.room.join.FakeJoinRoom
|
||||||
import io.element.android.libraries.matrix.ui.model.toInviteSender
|
import io.element.android.libraries.matrix.ui.model.toInviteSender
|
||||||
import io.element.android.tests.testutils.WarmUpRule
|
import io.element.android.tests.testutils.WarmUpRule
|
||||||
|
|
@ -67,10 +67,10 @@ class JoinRoomPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - when room is joined then content state is filled with his data`() = runTest {
|
fun `present - when room is joined then content state is filled with his data`() = runTest {
|
||||||
val roomInfo = aRoomInfo()
|
val roomSummary = aRoomSummary()
|
||||||
val matrixClient = FakeMatrixClient().apply {
|
val matrixClient = FakeMatrixClient().apply {
|
||||||
getRoomInfoFlowLambda = { _ ->
|
getRoomSummaryFlowLambda = { _ ->
|
||||||
flowOf(Optional.of(roomInfo))
|
flowOf(Optional.of(roomSummary))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val presenter = createJoinRoomPresenter(
|
val presenter = createJoinRoomPresenter(
|
||||||
|
|
@ -81,22 +81,22 @@ class JoinRoomPresenterTest {
|
||||||
awaitItem().also { state ->
|
awaitItem().also { state ->
|
||||||
val contentState = state.contentState as ContentState.Loaded
|
val contentState = state.contentState as ContentState.Loaded
|
||||||
assertThat(contentState.roomId).isEqualTo(A_ROOM_ID)
|
assertThat(contentState.roomId).isEqualTo(A_ROOM_ID)
|
||||||
assertThat(contentState.name).isEqualTo(roomInfo.name)
|
assertThat(contentState.name).isEqualTo(roomSummary.info.name)
|
||||||
assertThat(contentState.topic).isEqualTo(roomInfo.topic)
|
assertThat(contentState.topic).isEqualTo(roomSummary.info.topic)
|
||||||
assertThat(contentState.alias).isEqualTo(roomInfo.canonicalAlias)
|
assertThat(contentState.alias).isEqualTo(roomSummary.info.canonicalAlias)
|
||||||
assertThat(contentState.numberOfMembers).isEqualTo(roomInfo.activeMembersCount)
|
assertThat(contentState.numberOfMembers).isEqualTo(roomSummary.info.activeMembersCount)
|
||||||
assertThat(contentState.isDm).isEqualTo(roomInfo.isDirect)
|
assertThat(contentState.isDm).isEqualTo(roomSummary.info.isDirect)
|
||||||
assertThat(contentState.roomAvatarUrl).isEqualTo(roomInfo.avatarUrl)
|
assertThat(contentState.roomAvatarUrl).isEqualTo(roomSummary.info.avatarUrl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - when room is invited then join authorization is equal to invited`() = runTest {
|
fun `present - when room is invited then join authorization is equal to invited`() = runTest {
|
||||||
val roomInfo = aRoomInfo(currentUserMembership = CurrentUserMembership.INVITED)
|
val roomSummary = aRoomSummary(currentUserMembership = CurrentUserMembership.INVITED)
|
||||||
val matrixClient = FakeMatrixClient().apply {
|
val matrixClient = FakeMatrixClient().apply {
|
||||||
getRoomInfoFlowLambda = { _ ->
|
getRoomSummaryFlowLambda = { _ ->
|
||||||
flowOf(Optional.of(roomInfo))
|
flowOf(Optional.of(roomSummary))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val presenter = createJoinRoomPresenter(
|
val presenter = createJoinRoomPresenter(
|
||||||
|
|
@ -114,13 +114,13 @@ class JoinRoomPresenterTest {
|
||||||
fun `present - when room is invited then join authorization is equal to invited, an inviter is provided`() = runTest {
|
fun `present - when room is invited then join authorization is equal to invited, an inviter is provided`() = runTest {
|
||||||
val inviter = aRoomMember(userId = UserId("@bob:example.com"), displayName = "Bob")
|
val inviter = aRoomMember(userId = UserId("@bob:example.com"), displayName = "Bob")
|
||||||
val expectedInviteSender = inviter.toInviteSender()
|
val expectedInviteSender = inviter.toInviteSender()
|
||||||
val roomInfo = aRoomInfo(
|
val roomSummary = aRoomSummary(
|
||||||
currentUserMembership = CurrentUserMembership.INVITED,
|
currentUserMembership = CurrentUserMembership.INVITED,
|
||||||
inviter = inviter,
|
inviter = inviter,
|
||||||
)
|
)
|
||||||
val matrixClient = FakeMatrixClient().apply {
|
val matrixClient = FakeMatrixClient().apply {
|
||||||
getRoomInfoFlowLambda = { _ ->
|
getRoomSummaryFlowLambda = { _ ->
|
||||||
flowOf(Optional.of(roomInfo))
|
flowOf(Optional.of(roomSummary))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val presenter = createJoinRoomPresenter(
|
val presenter = createJoinRoomPresenter(
|
||||||
|
|
@ -140,10 +140,10 @@ class JoinRoomPresenterTest {
|
||||||
val acceptDeclinePresenter = Presenter {
|
val acceptDeclinePresenter = Presenter {
|
||||||
anAcceptDeclineInviteState(eventSink = eventSinkRecorder)
|
anAcceptDeclineInviteState(eventSink = eventSinkRecorder)
|
||||||
}
|
}
|
||||||
val roomInfo = aRoomInfo(currentUserMembership = CurrentUserMembership.INVITED)
|
val roomSummary = aRoomSummary(currentUserMembership = CurrentUserMembership.INVITED)
|
||||||
val matrixClient = FakeMatrixClient().apply {
|
val matrixClient = FakeMatrixClient().apply {
|
||||||
getRoomInfoFlowLambda = { _ ->
|
getRoomSummaryFlowLambda = { _ ->
|
||||||
flowOf(Optional.of(roomInfo))
|
flowOf(Optional.of(roomSummary))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val presenter = createJoinRoomPresenter(
|
val presenter = createJoinRoomPresenter(
|
||||||
|
|
@ -224,10 +224,10 @@ class JoinRoomPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - when room is left and public then join authorization is equal to canJoin`() = runTest {
|
fun `present - when room is left and public then join authorization is equal to canJoin`() = runTest {
|
||||||
val roomInfo = aRoomInfo(currentUserMembership = CurrentUserMembership.LEFT, isPublic = true)
|
val roomSummary = aRoomSummary(currentUserMembership = CurrentUserMembership.LEFT, isPublic = true)
|
||||||
val matrixClient = FakeMatrixClient().apply {
|
val matrixClient = FakeMatrixClient().apply {
|
||||||
getRoomInfoFlowLambda = { _ ->
|
getRoomSummaryFlowLambda = { _ ->
|
||||||
flowOf(Optional.of(roomInfo))
|
flowOf(Optional.of(roomSummary))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val presenter = createJoinRoomPresenter(
|
val presenter = createJoinRoomPresenter(
|
||||||
|
|
@ -243,10 +243,10 @@ class JoinRoomPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - when room is left and not public then join authorization is equal to unknown`() = runTest {
|
fun `present - when room is left and not public then join authorization is equal to unknown`() = runTest {
|
||||||
val roomInfo = aRoomInfo(currentUserMembership = CurrentUserMembership.LEFT, isPublic = false)
|
val roomSummary = aRoomSummary(currentUserMembership = CurrentUserMembership.LEFT, isPublic = false)
|
||||||
val matrixClient = FakeMatrixClient().apply {
|
val matrixClient = FakeMatrixClient().apply {
|
||||||
getRoomInfoFlowLambda = { _ ->
|
getRoomSummaryFlowLambda = { _ ->
|
||||||
flowOf(Optional.of(roomInfo))
|
flowOf(Optional.of(roomSummary))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val presenter = createJoinRoomPresenter(
|
val presenter = createJoinRoomPresenter(
|
||||||
|
|
@ -338,11 +338,15 @@ class JoinRoomPresenterTest {
|
||||||
awaitItem().also { state ->
|
awaitItem().also { state ->
|
||||||
state.eventSink(JoinRoomEvents.KnockRoom)
|
state.eventSink(JoinRoomEvents.KnockRoom)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assertThat(awaitItem().knockAction).isEqualTo(AsyncAction.Loading)
|
||||||
awaitItem().also { state ->
|
awaitItem().also { state ->
|
||||||
assertThat(state.knockAction).isEqualTo(AsyncAction.Success(Unit))
|
assertThat(state.knockAction).isEqualTo(AsyncAction.Success(Unit))
|
||||||
fakeKnockRoom.lambda = knockRoomFailure
|
fakeKnockRoom.lambda = knockRoomFailure
|
||||||
state.eventSink(JoinRoomEvents.KnockRoom)
|
state.eventSink(JoinRoomEvents.KnockRoom)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assertThat(awaitItem().knockAction).isEqualTo(AsyncAction.Loading)
|
||||||
awaitItem().also { state ->
|
awaitItem().also { state ->
|
||||||
assertThat(state.knockAction).isInstanceOf(AsyncAction.Failure::class.java)
|
assertThat(state.knockAction).isInstanceOf(AsyncAction.Failure::class.java)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ import extension.setupAnvil
|
||||||
plugins {
|
plugins {
|
||||||
id("io.element.android-compose-library")
|
id("io.element.android-compose-library")
|
||||||
id("kotlin-parcelize")
|
id("kotlin-parcelize")
|
||||||
alias(libs.plugins.anvil)
|
|
||||||
alias(libs.plugins.kotlin.serialization)
|
alias(libs.plugins.kotlin.serialization)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ class WebViewMessageInterceptor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
|
override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
|
||||||
request ?: return super.shouldOverrideUrlLoading(view, request)
|
request ?: return false
|
||||||
// Load the URL in a Chrome Custom Tab, and return true to cancel the load
|
// Load the URL in a Chrome Custom Tab, and return true to cancel the load
|
||||||
onOpenExternalUrl(request.url.toString())
|
onOpenExternalUrl(request.url.toString())
|
||||||
return true
|
return true
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,6 @@ import io.element.android.libraries.designsystem.theme.components.Button
|
||||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||||
import io.element.android.libraries.designsystem.utils.annotatedTextWithBold
|
import io.element.android.libraries.designsystem.utils.annotatedTextWithBold
|
||||||
import io.element.android.libraries.permissions.api.PermissionsView
|
import io.element.android.libraries.permissions.api.PermissionsView
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -51,6 +50,7 @@ fun QrCodeIntroView(
|
||||||
onBackClick = onBackClick,
|
onBackClick = onBackClick,
|
||||||
iconStyle = BigIcon.Style.Default(CompoundIcons.Computer()),
|
iconStyle = BigIcon.Style.Default(CompoundIcons.Computer()),
|
||||||
title = stringResource(id = R.string.screen_qr_code_login_initial_state_title, state.desktopAppName),
|
title = stringResource(id = R.string.screen_qr_code_login_initial_state_title, state.desktopAppName),
|
||||||
|
subTitle = stringResource(id = R.string.screen_qr_code_login_initial_state_subtitle),
|
||||||
content = { Content(state = state) },
|
content = { Content(state = state) },
|
||||||
buttons = { Buttons(state = state) }
|
buttons = { Buttons(state = state) }
|
||||||
)
|
)
|
||||||
|
|
@ -87,7 +87,7 @@ private fun ColumnScope.Buttons(
|
||||||
state: QrCodeIntroState,
|
state: QrCodeIntroState,
|
||||||
) {
|
) {
|
||||||
Button(
|
Button(
|
||||||
text = stringResource(id = CommonStrings.action_continue),
|
text = stringResource(id = R.string.screen_qr_code_login_initial_state_button_title),
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
onClick = {
|
onClick = {
|
||||||
state.eventSink.invoke(QrCodeIntroEvents.Continue)
|
state.eventSink.invoke(QrCodeIntroEvents.Continue)
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,7 @@ Try signing in manually, or scan the QR code with another device."</string>
|
||||||
<string name="screen_qr_code_login_initial_state_item_3">"Select %1$s"</string>
|
<string name="screen_qr_code_login_initial_state_item_3">"Select %1$s"</string>
|
||||||
<string name="screen_qr_code_login_initial_state_item_3_action">"“Link new device”"</string>
|
<string name="screen_qr_code_login_initial_state_item_3_action">"“Link new device”"</string>
|
||||||
<string name="screen_qr_code_login_initial_state_item_4">"Scan the QR code with this device"</string>
|
<string name="screen_qr_code_login_initial_state_item_4">"Scan the QR code with this device"</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_subtitle">"Only available if your account provider supports it."</string>
|
||||||
<string name="screen_qr_code_login_initial_state_title">"Open %1$s on another device to get the QR code"</string>
|
<string name="screen_qr_code_login_initial_state_title">"Open %1$s on another device to get the QR code"</string>
|
||||||
<string name="screen_qr_code_login_invalid_scan_state_description">"Use the QR code shown on the other device."</string>
|
<string name="screen_qr_code_login_invalid_scan_state_description">"Use the QR code shown on the other device."</string>
|
||||||
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Try again"</string>
|
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Try again"</string>
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.features.login.impl.R
|
||||||
import io.element.android.tests.testutils.EnsureNeverCalled
|
import io.element.android.tests.testutils.EnsureNeverCalled
|
||||||
import io.element.android.tests.testutils.EventsRecorder
|
import io.element.android.tests.testutils.EventsRecorder
|
||||||
import io.element.android.tests.testutils.clickOn
|
import io.element.android.tests.testutils.clickOn
|
||||||
|
|
@ -61,12 +61,12 @@ class QrCodeIntroViewTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `on continue button clicked - emits the Continue event`() {
|
fun `on submit button clicked - emits the Continue event`() {
|
||||||
val eventRecorder = EventsRecorder<QrCodeIntroEvents>()
|
val eventRecorder = EventsRecorder<QrCodeIntroEvents>()
|
||||||
rule.setQrCodeIntroView(
|
rule.setQrCodeIntroView(
|
||||||
state = aQrCodeIntroState(eventSink = eventRecorder),
|
state = aQrCodeIntroState(eventSink = eventRecorder),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_continue)
|
rule.clickOn(R.string.screen_qr_code_login_initial_state_button_title)
|
||||||
eventRecorder.assertSingle(QrCodeIntroEvents.Continue)
|
eventRecorder.assertSingle(QrCodeIntroEvents.Continue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||||
|
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||||
|
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
||||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||||
|
|
@ -31,7 +33,6 @@ import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.filter
|
import kotlinx.coroutines.flow.filter
|
||||||
import kotlinx.coroutines.flow.flatMapLatest
|
import kotlinx.coroutines.flow.flatMapLatest
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
@ -40,6 +41,7 @@ import javax.inject.Inject
|
||||||
class IdentityChangeStatePresenter @Inject constructor(
|
class IdentityChangeStatePresenter @Inject constructor(
|
||||||
private val room: MatrixRoom,
|
private val room: MatrixRoom,
|
||||||
private val encryptionService: EncryptionService,
|
private val encryptionService: EncryptionService,
|
||||||
|
private val featureFlagService: FeatureFlagService,
|
||||||
) : Presenter<IdentityChangeState> {
|
) : Presenter<IdentityChangeState> {
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): IdentityChangeState {
|
override fun present(): IdentityChangeState {
|
||||||
|
|
@ -62,14 +64,18 @@ class IdentityChangeStatePresenter @Inject constructor(
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
private fun ProduceStateScope<PersistentList<RoomMemberIdentityStateChange>>.observeRoomMemberIdentityStateChange() {
|
private fun ProduceStateScope<PersistentList<RoomMemberIdentityStateChange>>.observeRoomMemberIdentityStateChange() {
|
||||||
room.syncUpdateFlow
|
featureFlagService.isFeatureEnabledFlow(FeatureFlags.IdentityPinningViolationNotifications)
|
||||||
|
.filter { it }
|
||||||
|
.flatMapLatest {
|
||||||
|
room.syncUpdateFlow
|
||||||
|
}
|
||||||
.filter {
|
.filter {
|
||||||
// Room cannot become unencrypted, so we can just apply a filter here.
|
// Room cannot become unencrypted, so we can just apply a filter here.
|
||||||
room.isEncrypted
|
room.isEncrypted
|
||||||
}
|
}
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.flatMapLatest {
|
.flatMapLatest {
|
||||||
combine(room.identityStateChangesFlow, room.membersStateFlow,) { identityStateChanges, membersState ->
|
combine(room.identityStateChangesFlow, room.membersStateFlow) { identityStateChanges, membersState ->
|
||||||
identityStateChanges.map { identityStateChange ->
|
identityStateChanges.map { identityStateChange ->
|
||||||
val member = membersState.roomMembers()
|
val member = membersState.roomMembers()
|
||||||
?.firstOrNull { roomMember -> roomMember.userId == identityStateChange.userId }
|
?.firstOrNull { roomMember -> roomMember.userId == identityStateChange.userId }
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ import im.vector.app.features.analytics.plan.Interaction
|
||||||
import io.element.android.features.messages.impl.attachments.Attachment
|
import io.element.android.features.messages.impl.attachments.Attachment
|
||||||
import io.element.android.features.messages.impl.attachments.preview.error.sendAttachmentError
|
import io.element.android.features.messages.impl.attachments.preview.error.sendAttachmentError
|
||||||
import io.element.android.features.messages.impl.draft.ComposerDraftService
|
import io.element.android.features.messages.impl.draft.ComposerDraftService
|
||||||
|
import io.element.android.features.messages.impl.messagecomposer.suggestions.RoomAliasSuggestionsDataSource
|
||||||
import io.element.android.features.messages.impl.messagecomposer.suggestions.SuggestionsProcessor
|
import io.element.android.features.messages.impl.messagecomposer.suggestions.SuggestionsProcessor
|
||||||
import io.element.android.features.messages.impl.timeline.TimelineController
|
import io.element.android.features.messages.impl.timeline.TimelineController
|
||||||
import io.element.android.features.messages.impl.utils.TextPillificationHelper
|
import io.element.android.features.messages.impl.utils.TextPillificationHelper
|
||||||
|
|
|
||||||
|
|
@ -5,20 +5,22 @@
|
||||||
* Please see LICENSE in the repository root for full details.
|
* Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.messages.impl.messagecomposer
|
package io.element.android.features.messages.impl.messagecomposer.suggestions
|
||||||
|
|
||||||
import com.squareup.anvil.annotations.ContributesBinding
|
import com.squareup.anvil.annotations.ContributesBinding
|
||||||
import io.element.android.libraries.di.SessionScope
|
import io.element.android.libraries.di.SessionScope
|
||||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
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.roomlist.RoomListService
|
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
data class RoomAliasSuggestion(
|
data class RoomAliasSuggestion(
|
||||||
val roomAlias: RoomAlias,
|
val roomAlias: RoomAlias,
|
||||||
val roomSummary: RoomSummary,
|
val roomId: RoomId,
|
||||||
|
val roomName: String?,
|
||||||
|
val roomAvatarUrl: String?,
|
||||||
)
|
)
|
||||||
|
|
||||||
interface RoomAliasSuggestionsDataSource {
|
interface RoomAliasSuggestionsDataSource {
|
||||||
|
|
@ -32,14 +34,16 @@ class DefaultRoomAliasSuggestionsDataSource @Inject constructor(
|
||||||
override fun getAllRoomAliasSuggestions(): Flow<List<RoomAliasSuggestion>> {
|
override fun getAllRoomAliasSuggestions(): Flow<List<RoomAliasSuggestion>> {
|
||||||
return roomListService
|
return roomListService
|
||||||
.allRooms
|
.allRooms
|
||||||
.filteredSummaries
|
.summaries
|
||||||
.map { roomSummaries ->
|
.map { roomSummaries ->
|
||||||
roomSummaries
|
roomSummaries
|
||||||
.mapNotNull { roomSummary ->
|
.mapNotNull { roomSummary ->
|
||||||
roomSummary.canonicalAlias?.let { roomAlias ->
|
roomSummary.info.canonicalAlias?.let { roomAlias ->
|
||||||
RoomAliasSuggestion(
|
RoomAliasSuggestion(
|
||||||
roomAlias = roomAlias,
|
roomAlias = roomAlias,
|
||||||
roomSummary = roomSummary,
|
roomId = roomSummary.roomId,
|
||||||
|
roomName = roomSummary.info.name,
|
||||||
|
roomAvatarUrl = roomSummary.info.avatarUrl,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -36,7 +36,6 @@ import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
||||||
import io.element.android.libraries.matrix.ui.components.aRoomSummaryDetails
|
|
||||||
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
||||||
import io.element.android.libraries.textcomposer.mentions.ResolvedSuggestion
|
import io.element.android.libraries.textcomposer.mentions.ResolvedSuggestion
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
@ -60,7 +59,7 @@ fun SuggestionsPickerView(
|
||||||
when (suggestion) {
|
when (suggestion) {
|
||||||
is ResolvedSuggestion.AtRoom -> "@room"
|
is ResolvedSuggestion.AtRoom -> "@room"
|
||||||
is ResolvedSuggestion.Member -> suggestion.roomMember.userId.value
|
is ResolvedSuggestion.Member -> suggestion.roomMember.userId.value
|
||||||
is ResolvedSuggestion.Alias -> suggestion.roomSummary.roomId.value
|
is ResolvedSuggestion.Alias -> suggestion.roomId.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
|
|
@ -96,12 +95,12 @@ private fun SuggestionItemView(
|
||||||
val avatarData = when (suggestion) {
|
val avatarData = when (suggestion) {
|
||||||
is ResolvedSuggestion.AtRoom -> roomAvatar?.copy(size = avatarSize) ?: AvatarData(roomId, roomName, null, avatarSize)
|
is ResolvedSuggestion.AtRoom -> roomAvatar?.copy(size = avatarSize) ?: AvatarData(roomId, roomName, null, avatarSize)
|
||||||
is ResolvedSuggestion.Member -> suggestion.roomMember.getAvatarData(avatarSize)
|
is ResolvedSuggestion.Member -> suggestion.roomMember.getAvatarData(avatarSize)
|
||||||
is ResolvedSuggestion.Alias -> suggestion.roomSummary.getAvatarData(avatarSize)
|
is ResolvedSuggestion.Alias -> suggestion.getAvatarData(avatarSize)
|
||||||
}
|
}
|
||||||
val title = when (suggestion) {
|
val title = when (suggestion) {
|
||||||
is ResolvedSuggestion.AtRoom -> stringResource(R.string.screen_room_mentions_at_room_title)
|
is ResolvedSuggestion.AtRoom -> stringResource(R.string.screen_room_mentions_at_room_title)
|
||||||
is ResolvedSuggestion.Member -> suggestion.roomMember.displayName
|
is ResolvedSuggestion.Member -> suggestion.roomMember.displayName
|
||||||
is ResolvedSuggestion.Alias -> suggestion.roomSummary.name
|
is ResolvedSuggestion.Alias -> suggestion.roomName
|
||||||
}
|
}
|
||||||
val subtitle = when (suggestion) {
|
val subtitle = when (suggestion) {
|
||||||
is ResolvedSuggestion.AtRoom -> "@room"
|
is ResolvedSuggestion.AtRoom -> "@room"
|
||||||
|
|
@ -152,11 +151,6 @@ internal fun SuggestionsPickerViewPreview() {
|
||||||
role = RoomMember.Role.USER,
|
role = RoomMember.Role.USER,
|
||||||
)
|
)
|
||||||
val anAlias = remember { RoomAlias("#room:domain.org") }
|
val anAlias = remember { RoomAlias("#room:domain.org") }
|
||||||
val roomSummaryDetails = remember {
|
|
||||||
aRoomSummaryDetails(
|
|
||||||
name = "My room",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
SuggestionsPickerView(
|
SuggestionsPickerView(
|
||||||
roomId = RoomId("!room:matrix.org"),
|
roomId = RoomId("!room:matrix.org"),
|
||||||
roomName = "Room",
|
roomName = "Room",
|
||||||
|
|
@ -166,8 +160,10 @@ internal fun SuggestionsPickerViewPreview() {
|
||||||
ResolvedSuggestion.Member(roomMember),
|
ResolvedSuggestion.Member(roomMember),
|
||||||
ResolvedSuggestion.Member(roomMember.copy(userId = UserId("@bob:server.org"), displayName = "Bob")),
|
ResolvedSuggestion.Member(roomMember.copy(userId = UserId("@bob:server.org"), displayName = "Bob")),
|
||||||
ResolvedSuggestion.Alias(
|
ResolvedSuggestion.Alias(
|
||||||
anAlias,
|
roomAlias = anAlias,
|
||||||
roomSummaryDetails,
|
roomId = RoomId("!room:matrix.org"),
|
||||||
|
roomName = "My room",
|
||||||
|
roomAvatarUrl = null,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
onSelectSuggestion = {}
|
onSelectSuggestion = {}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@
|
||||||
|
|
||||||
package io.element.android.features.messages.impl.messagecomposer.suggestions
|
package io.element.android.features.messages.impl.messagecomposer.suggestions
|
||||||
|
|
||||||
import io.element.android.features.messages.impl.messagecomposer.RoomAliasSuggestion
|
|
||||||
import io.element.android.libraries.core.data.filterUpTo
|
import io.element.android.libraries.core.data.filterUpTo
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
|
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
|
||||||
|
|
@ -55,7 +54,14 @@ class SuggestionsProcessor @Inject constructor() {
|
||||||
SuggestionType.Room -> {
|
SuggestionType.Room -> {
|
||||||
roomAliasSuggestions
|
roomAliasSuggestions
|
||||||
.filter { it.roomAlias.value.contains(suggestion.text, ignoreCase = true) }
|
.filter { it.roomAlias.value.contains(suggestion.text, ignoreCase = true) }
|
||||||
.map { ResolvedSuggestion.Alias(it.roomAlias, it.roomSummary) }
|
.map {
|
||||||
|
ResolvedSuggestion.Alias(
|
||||||
|
roomAlias = it.roomAlias,
|
||||||
|
roomId = it.roomId,
|
||||||
|
roomName = it.roomName,
|
||||||
|
roomAvatarUrl = it.roomAvatarUrl,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
SuggestionType.Command,
|
SuggestionType.Command,
|
||||||
is SuggestionType.Custom -> {
|
is SuggestionType.Custom -> {
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,9 @@ package io.element.android.features.messages.impl.crypto.identity
|
||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||||
|
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||||
|
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||||
|
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
||||||
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
|
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
|
||||||
|
|
@ -65,6 +68,43 @@ class IdentityChangeStatePresenterTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - when the room emits identity change, but the feature is disabled, the presenter emits new state`() = runTest {
|
||||||
|
val room = FakeMatrixRoom(
|
||||||
|
isEncrypted = true,
|
||||||
|
)
|
||||||
|
val featureFlagService = FakeFeatureFlagService(
|
||||||
|
initialState = mapOf(
|
||||||
|
FeatureFlags.IdentityPinningViolationNotifications.key to false,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val presenter = createIdentityChangeStatePresenter(
|
||||||
|
room = room,
|
||||||
|
featureFlagService = featureFlagService,
|
||||||
|
)
|
||||||
|
presenter.test {
|
||||||
|
val initialState = awaitItem()
|
||||||
|
assertThat(initialState.roomMemberIdentityStateChanges).isEmpty()
|
||||||
|
room.emitIdentityStateChanges(
|
||||||
|
listOf(
|
||||||
|
IdentityStateChange(
|
||||||
|
userId = A_USER_ID_2,
|
||||||
|
identityState = IdentityState.PinViolation,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
// No item emitted.
|
||||||
|
expectNoEvents()
|
||||||
|
// Enable the feature
|
||||||
|
featureFlagService.setFeatureEnabled(FeatureFlags.IdentityPinningViolationNotifications, true)
|
||||||
|
val finalItem = awaitItem()
|
||||||
|
assertThat(finalItem.roomMemberIdentityStateChanges).hasSize(1)
|
||||||
|
val value = finalItem.roomMemberIdentityStateChanges.first()
|
||||||
|
assertThat(value.identityRoomMember.userId).isEqualTo(A_USER_ID_2)
|
||||||
|
assertThat(value.identityState).isEqualTo(IdentityState.PinViolation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - when the clear room emits identity change, the presenter does not emits new state`() = runTest {
|
fun `present - when the clear room emits identity change, the presenter does not emits new state`() = runTest {
|
||||||
val room = FakeMatrixRoom(isEncrypted = false)
|
val room = FakeMatrixRoom(isEncrypted = false)
|
||||||
|
|
@ -147,10 +187,16 @@ class IdentityChangeStatePresenterTest {
|
||||||
private fun createIdentityChangeStatePresenter(
|
private fun createIdentityChangeStatePresenter(
|
||||||
room: MatrixRoom = FakeMatrixRoom(),
|
room: MatrixRoom = FakeMatrixRoom(),
|
||||||
encryptionService: EncryptionService = FakeEncryptionService(),
|
encryptionService: EncryptionService = FakeEncryptionService(),
|
||||||
|
featureFlagService: FeatureFlagService = FakeFeatureFlagService(
|
||||||
|
initialState = mapOf(
|
||||||
|
FeatureFlags.IdentityPinningViolationNotifications.key to true,
|
||||||
|
)
|
||||||
|
),
|
||||||
): IdentityChangeStatePresenter {
|
): IdentityChangeStatePresenter {
|
||||||
return IdentityChangeStatePresenter(
|
return IdentityChangeStatePresenter(
|
||||||
room = room,
|
room = room,
|
||||||
encryptionService = encryptionService,
|
encryptionService = encryptionService,
|
||||||
|
featureFlagService = featureFlagService,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ package io.element.android.features.messages.impl.messagecomposer
|
||||||
|
|
||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import io.element.android.features.messages.impl.messagecomposer.suggestions.DefaultRoomAliasSuggestionsDataSource
|
||||||
|
import io.element.android.features.messages.impl.messagecomposer.suggestions.RoomAliasSuggestion
|
||||||
import io.element.android.libraries.matrix.test.A_ROOM_ALIAS
|
import io.element.android.libraries.matrix.test.A_ROOM_ALIAS
|
||||||
import io.element.android.libraries.matrix.test.A_ROOM_ID_2
|
import io.element.android.libraries.matrix.test.A_ROOM_ID_2
|
||||||
import io.element.android.libraries.matrix.test.room.aRoomSummary
|
import io.element.android.libraries.matrix.test.room.aRoomSummary
|
||||||
|
|
@ -38,7 +40,9 @@ class DefaultRoomAliasSuggestionsDataSourceTest {
|
||||||
listOf(
|
listOf(
|
||||||
RoomAliasSuggestion(
|
RoomAliasSuggestion(
|
||||||
roomAlias = A_ROOM_ALIAS,
|
roomAlias = A_ROOM_ALIAS,
|
||||||
roomSummary = aRoomSummaryWithAnAlias
|
roomId = aRoomSummaryWithAnAlias.roomId,
|
||||||
|
roomName = aRoomSummaryWithAnAlias.info.name,
|
||||||
|
roomAvatarUrl = aRoomSummaryWithAnAlias.info.avatarUrl
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@
|
||||||
|
|
||||||
package io.element.android.features.messages.impl.messagecomposer
|
package io.element.android.features.messages.impl.messagecomposer
|
||||||
|
|
||||||
|
import io.element.android.features.messages.impl.messagecomposer.suggestions.RoomAliasSuggestion
|
||||||
|
import io.element.android.features.messages.impl.messagecomposer.suggestions.RoomAliasSuggestionsDataSource
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@
|
||||||
package io.element.android.features.messages.impl.messagecomposer.suggestions
|
package io.element.android.features.messages.impl.messagecomposer.suggestions
|
||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.features.messages.impl.messagecomposer.RoomAliasSuggestion
|
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
|
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
||||||
|
|
@ -110,13 +109,25 @@ class SuggestionsProcessorTest {
|
||||||
val result = suggestionsProcessor.process(
|
val result = suggestionsProcessor.process(
|
||||||
suggestion = aRoomSuggestion("ALI"),
|
suggestion = aRoomSuggestion("ALI"),
|
||||||
roomMembersState = MatrixRoomMembersState.Ready(persistentListOf()),
|
roomMembersState = MatrixRoomMembersState.Ready(persistentListOf()),
|
||||||
roomAliasSuggestions = listOf(RoomAliasSuggestion(A_ROOM_ALIAS, aRoomSummary)),
|
roomAliasSuggestions = listOf(
|
||||||
|
RoomAliasSuggestion(
|
||||||
|
roomAlias = A_ROOM_ALIAS,
|
||||||
|
roomId = aRoomSummary.roomId,
|
||||||
|
roomName = aRoomSummary.info.name,
|
||||||
|
roomAvatarUrl = aRoomSummary.info.avatarUrl,
|
||||||
|
)
|
||||||
|
),
|
||||||
currentUserId = A_USER_ID,
|
currentUserId = A_USER_ID,
|
||||||
canSendRoomMention = { true },
|
canSendRoomMention = { true },
|
||||||
)
|
)
|
||||||
assertThat(result).isEqualTo(
|
assertThat(result).isEqualTo(
|
||||||
listOf(
|
listOf(
|
||||||
ResolvedSuggestion.Alias(A_ROOM_ALIAS, aRoomSummary)
|
ResolvedSuggestion.Alias(
|
||||||
|
roomAlias = A_ROOM_ALIAS,
|
||||||
|
roomId = aRoomSummary.roomId,
|
||||||
|
roomName = aRoomSummary.info.name,
|
||||||
|
roomAvatarUrl = aRoomSummary.info.avatarUrl,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -127,13 +138,25 @@ class SuggestionsProcessorTest {
|
||||||
val result = suggestionsProcessor.process(
|
val result = suggestionsProcessor.process(
|
||||||
suggestion = aRoomSuggestion("ali"),
|
suggestion = aRoomSuggestion("ali"),
|
||||||
roomMembersState = MatrixRoomMembersState.Ready(persistentListOf()),
|
roomMembersState = MatrixRoomMembersState.Ready(persistentListOf()),
|
||||||
roomAliasSuggestions = listOf(RoomAliasSuggestion(A_ROOM_ALIAS, aRoomSummary)),
|
roomAliasSuggestions = listOf(
|
||||||
|
RoomAliasSuggestion(
|
||||||
|
roomAlias = A_ROOM_ALIAS,
|
||||||
|
roomId = aRoomSummary.roomId,
|
||||||
|
roomName = aRoomSummary.info.name,
|
||||||
|
roomAvatarUrl = aRoomSummary.info.avatarUrl,
|
||||||
|
)
|
||||||
|
),
|
||||||
currentUserId = A_USER_ID,
|
currentUserId = A_USER_ID,
|
||||||
canSendRoomMention = { true },
|
canSendRoomMention = { true },
|
||||||
)
|
)
|
||||||
assertThat(result).isEqualTo(
|
assertThat(result).isEqualTo(
|
||||||
listOf(
|
listOf(
|
||||||
ResolvedSuggestion.Alias(A_ROOM_ALIAS, aRoomSummary)
|
ResolvedSuggestion.Alias(
|
||||||
|
roomAlias = A_ROOM_ALIAS,
|
||||||
|
roomId = aRoomSummary.roomId,
|
||||||
|
roomName = aRoomSummary.info.name,
|
||||||
|
roomAvatarUrl = aRoomSummary.info.avatarUrl,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -144,7 +167,14 @@ class SuggestionsProcessorTest {
|
||||||
val result = suggestionsProcessor.process(
|
val result = suggestionsProcessor.process(
|
||||||
suggestion = aRoomSuggestion("tot"),
|
suggestion = aRoomSuggestion("tot"),
|
||||||
roomMembersState = MatrixRoomMembersState.Ready(persistentListOf()),
|
roomMembersState = MatrixRoomMembersState.Ready(persistentListOf()),
|
||||||
roomAliasSuggestions = listOf(RoomAliasSuggestion(A_ROOM_ALIAS, aRoomSummary)),
|
roomAliasSuggestions = listOf(
|
||||||
|
RoomAliasSuggestion(
|
||||||
|
roomAlias = A_ROOM_ALIAS,
|
||||||
|
roomId = aRoomSummary.roomId,
|
||||||
|
roomName = aRoomSummary.info.name,
|
||||||
|
roomAvatarUrl = aRoomSummary.info.avatarUrl,
|
||||||
|
)
|
||||||
|
),
|
||||||
currentUserId = A_USER_ID,
|
currentUserId = A_USER_ID,
|
||||||
canSendRoomMention = { true },
|
canSendRoomMention = { true },
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import extension.setupAnvil
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright 2024 New Vector Ltd.
|
* Copyright 2024 New Vector Ltd.
|
||||||
*
|
*
|
||||||
|
|
@ -7,13 +9,14 @@
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("io.element.android-compose-library")
|
id("io.element.android-compose-library")
|
||||||
alias(libs.plugins.anvil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "io.element.android.features.migration.impl"
|
namespace = "io.element.android.features.migration.impl"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setupAnvil()
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(projects.features.migration.api)
|
implementation(projects.features.migration.api)
|
||||||
implementation(projects.libraries.architecture)
|
implementation(projects.libraries.architecture)
|
||||||
|
|
|
||||||
|
|
@ -21,11 +21,12 @@ import dagger.assisted.AssistedInject
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.architecture.runUpdatingStateNoSuccess
|
import io.element.android.libraries.architecture.runUpdatingStateNoSuccess
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||||
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
|
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
|
||||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
||||||
|
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.FlowPreview
|
import kotlinx.coroutines.FlowPreview
|
||||||
|
|
@ -40,7 +41,6 @@ class EditDefaultNotificationSettingPresenter @AssistedInject constructor(
|
||||||
private val notificationSettingsService: NotificationSettingsService,
|
private val notificationSettingsService: NotificationSettingsService,
|
||||||
@Assisted private val isOneToOne: Boolean,
|
@Assisted private val isOneToOne: Boolean,
|
||||||
private val roomListService: RoomListService,
|
private val roomListService: RoomListService,
|
||||||
private val matrixClient: MatrixClient,
|
|
||||||
) : Presenter<EditDefaultNotificationSettingState> {
|
) : Presenter<EditDefaultNotificationSettingState> {
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
interface Factory {
|
interface Factory {
|
||||||
|
|
@ -57,8 +57,8 @@ class EditDefaultNotificationSettingPresenter @AssistedInject constructor(
|
||||||
|
|
||||||
val changeNotificationSettingAction: MutableState<AsyncAction<Unit>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
|
val changeNotificationSettingAction: MutableState<AsyncAction<Unit>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
|
||||||
|
|
||||||
val roomsWithUserDefinedMode: MutableState<List<RoomSummary>> = remember {
|
val roomsWithUserDefinedMode: MutableState<List<EditNotificationSettingRoomInfo>> = remember {
|
||||||
mutableStateOf(listOf())
|
mutableStateOf(emptyList())
|
||||||
}
|
}
|
||||||
|
|
||||||
val localCoroutineScope = rememberCoroutineScope()
|
val localCoroutineScope = rememberCoroutineScope()
|
||||||
|
|
@ -106,31 +106,37 @@ class EditDefaultNotificationSettingPresenter @AssistedInject constructor(
|
||||||
.launchIn(this)
|
.launchIn(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun CoroutineScope.observeRoomSummaries(roomsWithUserDefinedMode: MutableState<List<RoomSummary>>) {
|
private fun CoroutineScope.observeRoomSummaries(roomsWithUserDefinedMode: MutableState<List<EditNotificationSettingRoomInfo>>) {
|
||||||
roomListService.allRooms
|
roomListService.allRooms
|
||||||
.summaries
|
.summaries
|
||||||
.onEach {
|
.onEach { roomSummaries ->
|
||||||
updateRoomsWithUserDefinedMode(it, roomsWithUserDefinedMode)
|
updateRoomsWithUserDefinedMode(roomSummaries, roomsWithUserDefinedMode)
|
||||||
}
|
}
|
||||||
.launchIn(this)
|
.launchIn(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun CoroutineScope.updateRoomsWithUserDefinedMode(
|
private suspend fun updateRoomsWithUserDefinedMode(
|
||||||
summaries: List<RoomSummary>,
|
summaries: List<RoomSummary>,
|
||||||
roomsWithUserDefinedMode: MutableState<List<RoomSummary>>
|
roomsWithUserDefinedMode: MutableState<List<EditNotificationSettingRoomInfo>>
|
||||||
) = launch {
|
) {
|
||||||
val roomWithUserDefinedRules: Set<String> = notificationSettingsService.getRoomsWithUserDefinedRules().getOrThrow().toSet()
|
val roomWithUserDefinedRules: Set<String> = notificationSettingsService.getRoomsWithUserDefinedRules().getOrDefault(emptyList()).toSet()
|
||||||
|
roomsWithUserDefinedMode.value = summaries
|
||||||
val sortedSummaries = summaries
|
.filter { roomSummary ->
|
||||||
.filterIsInstance<RoomSummary>()
|
roomWithUserDefinedRules.contains(roomSummary.roomId.value) && roomSummary.isOneToOne == isOneToOne
|
||||||
.filter {
|
}
|
||||||
val room = matrixClient.getRoom(it.roomId) ?: return@filter false
|
.map { roomSummary ->
|
||||||
roomWithUserDefinedRules.contains(it.roomId.value) && isOneToOne == room.isOneToOne
|
EditNotificationSettingRoomInfo(
|
||||||
|
roomId = roomSummary.roomId,
|
||||||
|
name = roomSummary.info.name,
|
||||||
|
heroesAvatar = roomSummary.info.heroes.map { hero ->
|
||||||
|
hero.getAvatarData(AvatarSize.CustomRoomNotificationSetting)
|
||||||
|
}.toImmutableList(),
|
||||||
|
avatarData = roomSummary.info.getAvatarData(AvatarSize.CustomRoomNotificationSetting),
|
||||||
|
notificationMode = roomSummary.info.userDefinedNotificationMode,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
// locale sensitive sorting
|
// locale sensitive sorting
|
||||||
.sortedWith(compareBy(Collator.getInstance()) { it.name })
|
.sortedWith(compareBy(Collator.getInstance()) { roomSummary -> roomSummary.name })
|
||||||
|
|
||||||
roomsWithUserDefinedMode.value = sortedSummaries
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun CoroutineScope.setDefaultNotificationMode(mode: RoomNotificationMode, action: MutableState<AsyncAction<Unit>>) = launch {
|
private fun CoroutineScope.setDefaultNotificationMode(mode: RoomNotificationMode, action: MutableState<AsyncAction<Unit>>) = launch {
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,12 @@ package io.element.android.features.preferences.impl.notifications.edit
|
||||||
|
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
||||||
data class EditDefaultNotificationSettingState(
|
data class EditDefaultNotificationSettingState(
|
||||||
val isOneToOne: Boolean,
|
val isOneToOne: Boolean,
|
||||||
val mode: RoomNotificationMode?,
|
val mode: RoomNotificationMode?,
|
||||||
val roomsWithUserDefinedMode: ImmutableList<RoomSummary>,
|
val roomsWithUserDefinedMode: ImmutableList<EditNotificationSettingRoomInfo>,
|
||||||
val changeNotificationSettingAction: AsyncAction<Unit>,
|
val changeNotificationSettingAction: AsyncAction<Unit>,
|
||||||
val displayMentionsOnlyDisclaimer: Boolean,
|
val displayMentionsOnlyDisclaimer: Boolean,
|
||||||
val eventSink: (EditDefaultNotificationSettingStateEvents) -> Unit,
|
val eventSink: (EditDefaultNotificationSettingStateEvents) -> Unit,
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,10 @@ package io.element.android.features.preferences.impl.notifications.edit
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
|
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||||
|
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||||
import io.element.android.libraries.matrix.ui.components.aRoomSummaryDetails
|
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
||||||
open class EditDefaultNotificationSettingStateProvider : PreviewParameterProvider<EditDefaultNotificationSettingState> {
|
open class EditDefaultNotificationSettingStateProvider : PreviewParameterProvider<EditDefaultNotificationSettingState> {
|
||||||
|
|
@ -33,21 +34,25 @@ private fun anEditDefaultNotificationSettingsState(
|
||||||
isOneToOne = isOneToOne,
|
isOneToOne = isOneToOne,
|
||||||
mode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY,
|
mode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY,
|
||||||
roomsWithUserDefinedMode = persistentListOf(
|
roomsWithUserDefinedMode = persistentListOf(
|
||||||
aRoomSummary("Room"),
|
anEditNotificationSettingRoomInfo("Room"),
|
||||||
aRoomSummary(null),
|
anEditNotificationSettingRoomInfo(null),
|
||||||
),
|
),
|
||||||
changeNotificationSettingAction = changeNotificationSettingAction,
|
changeNotificationSettingAction = changeNotificationSettingAction,
|
||||||
displayMentionsOnlyDisclaimer = displayMentionsOnlyDisclaimer,
|
displayMentionsOnlyDisclaimer = displayMentionsOnlyDisclaimer,
|
||||||
eventSink = {}
|
eventSink = {}
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun aRoomSummary(
|
private fun anEditNotificationSettingRoomInfo(
|
||||||
name: String?,
|
name: String?,
|
||||||
) = aRoomSummaryDetails(
|
) = EditNotificationSettingRoomInfo(
|
||||||
roomId = RoomId("!roomId:domain"),
|
roomId = RoomId("!roomId:domain"),
|
||||||
name = name,
|
name = name,
|
||||||
avatarUrl = null,
|
avatarData = AvatarData(
|
||||||
isDirect = false,
|
id = "!roomId:domain",
|
||||||
lastMessage = null,
|
name = name,
|
||||||
|
url = null,
|
||||||
|
size = AvatarSize.CustomRoomNotificationSetting,
|
||||||
|
),
|
||||||
|
heroesAvatar = persistentListOf(),
|
||||||
notificationMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY,
|
notificationMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@ import androidx.compose.ui.text.font.FontStyle
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import io.element.android.features.preferences.impl.R
|
import io.element.android.features.preferences.impl.R
|
||||||
import io.element.android.libraries.designsystem.components.async.AsyncActionView
|
import io.element.android.libraries.designsystem.components.async.AsyncActionView
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
|
||||||
import io.element.android.libraries.designsystem.components.avatar.CompositeAvatar
|
import io.element.android.libraries.designsystem.components.avatar.CompositeAvatar
|
||||||
import io.element.android.libraries.designsystem.components.list.ListItemContent
|
import io.element.android.libraries.designsystem.components.list.ListItemContent
|
||||||
import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory
|
import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory
|
||||||
|
|
@ -27,9 +26,7 @@ import io.element.android.libraries.designsystem.theme.components.ListItem
|
||||||
import io.element.android.libraries.designsystem.theme.components.Text
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||||
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import kotlinx.collections.immutable.toPersistentList
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A view that allows a user to edit the default notification setting for rooms. This can be set separately
|
* A view that allows a user to edit the default notification setting for rooms. This can be set separately
|
||||||
|
|
@ -80,7 +77,7 @@ fun EditDefaultNotificationSettingView(
|
||||||
if (state.roomsWithUserDefinedMode.isNotEmpty()) {
|
if (state.roomsWithUserDefinedMode.isNotEmpty()) {
|
||||||
PreferenceCategory(title = stringResource(id = R.string.screen_notification_settings_edit_custom_settings_section_title)) {
|
PreferenceCategory(title = stringResource(id = R.string.screen_notification_settings_edit_custom_settings_section_title)) {
|
||||||
state.roomsWithUserDefinedMode.forEach { summary ->
|
state.roomsWithUserDefinedMode.forEach { summary ->
|
||||||
val subtitle = when (summary.userDefinedNotificationMode) {
|
val subtitle = when (summary.notificationMode) {
|
||||||
RoomNotificationMode.ALL_MESSAGES -> stringResource(id = R.string.screen_notification_settings_edit_mode_all_messages)
|
RoomNotificationMode.ALL_MESSAGES -> stringResource(id = R.string.screen_notification_settings_edit_mode_all_messages)
|
||||||
RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY -> {
|
RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY -> {
|
||||||
stringResource(id = R.string.screen_notification_settings_edit_mode_mentions_and_keywords)
|
stringResource(id = R.string.screen_notification_settings_edit_mode_mentions_and_keywords)
|
||||||
|
|
@ -101,10 +98,8 @@ fun EditDefaultNotificationSettingView(
|
||||||
},
|
},
|
||||||
leadingContent = ListItemContent.Custom {
|
leadingContent = ListItemContent.Custom {
|
||||||
CompositeAvatar(
|
CompositeAvatar(
|
||||||
avatarData = summary.getAvatarData(size = AvatarSize.CustomRoomNotificationSetting),
|
avatarData = summary.avatarData,
|
||||||
heroes = summary.heroes.map { user ->
|
heroes = summary.heroesAvatar,
|
||||||
user.getAvatarData(size = AvatarSize.CustomRoomNotificationSetting)
|
|
||||||
}.toPersistentList()
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
* Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.preferences.impl.notifications.edit
|
||||||
|
|
||||||
|
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||||
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
||||||
|
data class EditNotificationSettingRoomInfo(
|
||||||
|
val roomId: RoomId,
|
||||||
|
val name: String?,
|
||||||
|
val heroesAvatar: ImmutableList<AvatarData>,
|
||||||
|
val avatarData: AvatarData,
|
||||||
|
val notificationMode: RoomNotificationMode?
|
||||||
|
)
|
||||||
|
|
@ -14,11 +14,8 @@ import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.features.preferences.impl.notifications.edit.EditDefaultNotificationSettingPresenter
|
import io.element.android.features.preferences.impl.notifications.edit.EditDefaultNotificationSettingPresenter
|
||||||
import io.element.android.features.preferences.impl.notifications.edit.EditDefaultNotificationSettingStateEvents
|
import io.element.android.features.preferences.impl.notifications.edit.EditDefaultNotificationSettingStateEvents
|
||||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
|
||||||
import io.element.android.libraries.matrix.test.A_THROWABLE
|
import io.element.android.libraries.matrix.test.A_THROWABLE
|
||||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
|
||||||
import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService
|
import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService
|
||||||
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
|
||||||
import io.element.android.libraries.matrix.test.room.aRoomSummary
|
import io.element.android.libraries.matrix.test.room.aRoomSummary
|
||||||
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
|
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
|
||||||
import io.element.android.tests.testutils.awaitLastSequentialItem
|
import io.element.android.tests.testutils.awaitLastSequentialItem
|
||||||
|
|
@ -49,24 +46,20 @@ class EditDefaultNotificationSettingsPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - ensure list of rooms with user defined mode`() = runTest {
|
fun `present - ensure list of rooms with user defined mode`() = runTest {
|
||||||
val room = FakeMatrixRoom()
|
|
||||||
val notificationSettingsService = FakeNotificationSettingsService(
|
val notificationSettingsService = FakeNotificationSettingsService(
|
||||||
initialRoomMode = RoomNotificationMode.ALL_MESSAGES,
|
initialRoomMode = RoomNotificationMode.ALL_MESSAGES,
|
||||||
initialRoomModeIsDefault = false
|
initialRoomModeIsDefault = false
|
||||||
)
|
)
|
||||||
val matrixClient = FakeMatrixClient(notificationSettingsService = notificationSettingsService).apply {
|
|
||||||
givenGetRoomResult(A_ROOM_ID, room)
|
|
||||||
}
|
|
||||||
val roomListService = FakeRoomListService()
|
val roomListService = FakeRoomListService()
|
||||||
val presenter = createEditDefaultNotificationSettingPresenter(notificationSettingsService, roomListService, matrixClient)
|
val presenter = createEditDefaultNotificationSettingPresenter(notificationSettingsService, roomListService)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
roomListService.postAllRooms(listOf(aRoomSummary(notificationMode = RoomNotificationMode.ALL_MESSAGES)))
|
roomListService.postAllRooms(listOf(aRoomSummary(userDefinedNotificationMode = RoomNotificationMode.ALL_MESSAGES)))
|
||||||
val loadedState = consumeItemsUntilPredicate { state ->
|
val loadedState = consumeItemsUntilPredicate { state ->
|
||||||
state.roomsWithUserDefinedMode.any { it.userDefinedNotificationMode == RoomNotificationMode.ALL_MESSAGES }
|
state.roomsWithUserDefinedMode.any { it.notificationMode == RoomNotificationMode.ALL_MESSAGES }
|
||||||
}.last()
|
}.last()
|
||||||
assertThat(loadedState.roomsWithUserDefinedMode.any { it.userDefinedNotificationMode == RoomNotificationMode.ALL_MESSAGES }).isTrue()
|
assertThat(loadedState.roomsWithUserDefinedMode.any { it.notificationMode == RoomNotificationMode.ALL_MESSAGES }).isTrue()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -121,13 +114,11 @@ class EditDefaultNotificationSettingsPresenterTest {
|
||||||
private fun createEditDefaultNotificationSettingPresenter(
|
private fun createEditDefaultNotificationSettingPresenter(
|
||||||
notificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService(),
|
notificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService(),
|
||||||
roomListService: FakeRoomListService = FakeRoomListService(),
|
roomListService: FakeRoomListService = FakeRoomListService(),
|
||||||
matrixClient: FakeMatrixClient = FakeMatrixClient(notificationSettingsService = notificationSettingsService)
|
|
||||||
): EditDefaultNotificationSettingPresenter {
|
): EditDefaultNotificationSettingPresenter {
|
||||||
return EditDefaultNotificationSettingPresenter(
|
return EditDefaultNotificationSettingPresenter(
|
||||||
notificationSettingsService = notificationSettingsService,
|
notificationSettingsService = notificationSettingsService,
|
||||||
isOneToOne = false,
|
isOneToOne = false,
|
||||||
roomListService = roomListService,
|
roomListService = roomListService,
|
||||||
matrixClient = matrixClient
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ import extension.setupAnvil
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("io.element.android-compose-library")
|
id("io.element.android-compose-library")
|
||||||
alias(libs.plugins.anvil)
|
|
||||||
id("kotlin-parcelize")
|
id("kotlin-parcelize")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormat
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||||
import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter
|
import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter
|
||||||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||||
|
import io.element.android.libraries.matrix.api.room.isDm
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
||||||
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
||||||
import io.element.android.libraries.matrix.ui.model.toInviteSender
|
import io.element.android.libraries.matrix.ui.model.toInviteSender
|
||||||
|
|
@ -24,34 +25,35 @@ class RoomListRoomSummaryFactory @Inject constructor(
|
||||||
private val lastMessageTimestampFormatter: LastMessageTimestampFormatter,
|
private val lastMessageTimestampFormatter: LastMessageTimestampFormatter,
|
||||||
private val roomLastMessageFormatter: RoomLastMessageFormatter,
|
private val roomLastMessageFormatter: RoomLastMessageFormatter,
|
||||||
) {
|
) {
|
||||||
fun create(details: RoomSummary): RoomListRoomSummary {
|
fun create(roomSummary: RoomSummary): RoomListRoomSummary {
|
||||||
val avatarData = details.getAvatarData(size = AvatarSize.RoomListItem)
|
val roomInfo = roomSummary.info
|
||||||
|
val avatarData = roomInfo.getAvatarData(size = AvatarSize.RoomListItem)
|
||||||
return RoomListRoomSummary(
|
return RoomListRoomSummary(
|
||||||
id = details.roomId.value,
|
id = roomSummary.roomId.value,
|
||||||
roomId = details.roomId,
|
roomId = roomSummary.roomId,
|
||||||
name = details.name,
|
name = roomInfo.name,
|
||||||
numberOfUnreadMessages = details.numUnreadMessages,
|
numberOfUnreadMessages = roomInfo.numUnreadMessages,
|
||||||
numberOfUnreadMentions = details.numUnreadMentions,
|
numberOfUnreadMentions = roomInfo.numUnreadMentions,
|
||||||
numberOfUnreadNotifications = details.numUnreadNotifications,
|
numberOfUnreadNotifications = roomInfo.numUnreadNotifications,
|
||||||
isMarkedUnread = details.isMarkedUnread,
|
isMarkedUnread = roomInfo.isMarkedUnread,
|
||||||
timestamp = lastMessageTimestampFormatter.format(details.lastMessageTimestamp),
|
timestamp = lastMessageTimestampFormatter.format(roomSummary.lastMessageTimestamp),
|
||||||
lastMessage = details.lastMessage?.let { message ->
|
lastMessage = roomSummary.lastMessage?.let { message ->
|
||||||
roomLastMessageFormatter.format(message.event, details.isDm)
|
roomLastMessageFormatter.format(message.event, roomInfo.isDm)
|
||||||
}.orEmpty(),
|
}.orEmpty(),
|
||||||
avatarData = avatarData,
|
avatarData = avatarData,
|
||||||
userDefinedNotificationMode = details.userDefinedNotificationMode,
|
userDefinedNotificationMode = roomInfo.userDefinedNotificationMode,
|
||||||
hasRoomCall = details.hasRoomCall,
|
hasRoomCall = roomInfo.hasRoomCall,
|
||||||
isDirect = details.isDirect,
|
isDirect = roomInfo.isDirect,
|
||||||
isFavorite = details.isFavorite,
|
isFavorite = roomInfo.isFavorite,
|
||||||
inviteSender = details.inviter?.toInviteSender(),
|
inviteSender = roomInfo.inviter?.toInviteSender(),
|
||||||
isDm = details.isDm,
|
isDm = roomInfo.isDm,
|
||||||
canonicalAlias = details.canonicalAlias,
|
canonicalAlias = roomInfo.canonicalAlias,
|
||||||
displayType = if (details.currentUserMembership == CurrentUserMembership.INVITED) {
|
displayType = if (roomInfo.currentUserMembership == CurrentUserMembership.INVITED) {
|
||||||
RoomSummaryDisplayType.INVITE
|
RoomSummaryDisplayType.INVITE
|
||||||
} else {
|
} else {
|
||||||
RoomSummaryDisplayType.ROOM
|
RoomSummaryDisplayType.ROOM
|
||||||
},
|
},
|
||||||
heroes = details.heroes.map { user ->
|
heroes = roomInfo.heroes.map { user ->
|
||||||
user.getAvatarData(size = AvatarSize.RoomListItem)
|
user.getAvatarData(size = AvatarSize.RoomListItem)
|
||||||
}.toImmutableList(),
|
}.toImmutableList(),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,9 @@ data class RoomListRoomSummary(
|
||||||
val roomId: RoomId,
|
val roomId: RoomId,
|
||||||
val name: String?,
|
val name: String?,
|
||||||
val canonicalAlias: RoomAlias?,
|
val canonicalAlias: RoomAlias?,
|
||||||
val numberOfUnreadMessages: Int,
|
val numberOfUnreadMessages: Long,
|
||||||
val numberOfUnreadMentions: Int,
|
val numberOfUnreadMentions: Long,
|
||||||
val numberOfUnreadNotifications: Int,
|
val numberOfUnreadNotifications: Long,
|
||||||
val isMarkedUnread: Boolean,
|
val isMarkedUnread: Boolean,
|
||||||
val timestamp: String?,
|
val timestamp: String?,
|
||||||
val lastMessage: CharSequence?,
|
val lastMessage: CharSequence?,
|
||||||
|
|
|
||||||
|
|
@ -119,9 +119,9 @@ internal fun anInviteSender(
|
||||||
internal fun aRoomListRoomSummary(
|
internal fun aRoomListRoomSummary(
|
||||||
id: String = "!roomId:domain",
|
id: String = "!roomId:domain",
|
||||||
name: String? = "Room name",
|
name: String? = "Room name",
|
||||||
numberOfUnreadMessages: Int = 0,
|
numberOfUnreadMessages: Long = 0,
|
||||||
numberOfUnreadMentions: Int = 0,
|
numberOfUnreadMentions: Long = 0,
|
||||||
numberOfUnreadNotifications: Int = 0,
|
numberOfUnreadNotifications: Long = 0,
|
||||||
isMarkedUnread: Boolean = false,
|
isMarkedUnread: Boolean = false,
|
||||||
lastMessage: String? = "Last message",
|
lastMessage: String? = "Last message",
|
||||||
timestamp: String? = lastMessage?.let { "88:88" },
|
timestamp: String? = lastMessage?.let { "88:88" },
|
||||||
|
|
|
||||||
|
|
@ -396,7 +396,7 @@ class RoomListPresenterTest {
|
||||||
val notificationSettingsService = FakeNotificationSettingsService()
|
val notificationSettingsService = FakeNotificationSettingsService()
|
||||||
val roomListService = FakeRoomListService()
|
val roomListService = FakeRoomListService()
|
||||||
roomListService.postAllRoomsLoadingState(RoomList.LoadingState.Loaded(1))
|
roomListService.postAllRoomsLoadingState(RoomList.LoadingState.Loaded(1))
|
||||||
roomListService.postAllRooms(listOf(aRoomSummary(notificationMode = userDefinedMode)))
|
roomListService.postAllRooms(listOf(aRoomSummary(userDefinedNotificationMode = userDefinedMode)))
|
||||||
val matrixClient = FakeMatrixClient(
|
val matrixClient = FakeMatrixClient(
|
||||||
roomListService = roomListService,
|
roomListService = roomListService,
|
||||||
notificationSettingsService = notificationSettingsService
|
notificationSettingsService = notificationSettingsService
|
||||||
|
|
|
||||||
|
|
@ -76,9 +76,9 @@ class RoomListRoomSummaryTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun createRoomListRoomSummary(
|
internal fun createRoomListRoomSummary(
|
||||||
numberOfUnreadMentions: Int = 0,
|
numberOfUnreadMentions: Long = 0,
|
||||||
numberOfUnreadMessages: Int = 0,
|
numberOfUnreadMessages: Long = 0,
|
||||||
numberOfUnreadNotifications: Int = 0,
|
numberOfUnreadNotifications: Long = 0,
|
||||||
isMarkedUnread: Boolean = false,
|
isMarkedUnread: Boolean = false,
|
||||||
userDefinedNotificationMode: RoomNotificationMode? = null,
|
userDefinedNotificationMode: RoomNotificationMode? = null,
|
||||||
isFavorite: Boolean = false,
|
isFavorite: Boolean = false,
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,6 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.designsystem.theme.components.Button
|
import io.element.android.libraries.designsystem.theme.components.Button
|
||||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||||
import io.element.android.libraries.designsystem.theme.components.Text
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -50,7 +49,7 @@ fun ResetIdentityRootView(
|
||||||
buttons = {
|
buttons = {
|
||||||
Button(
|
Button(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
text = stringResource(id = CommonStrings.action_continue),
|
text = stringResource(id = R.string.screen_encryption_reset_action_continue_reset),
|
||||||
onClick = { state.eventSink(ResetIdentityRootEvent.Continue) },
|
onClick = { state.eventSink(ResetIdentityRootEvent.Continue) },
|
||||||
destructive = true,
|
destructive = true,
|
||||||
)
|
)
|
||||||
|
|
@ -98,9 +97,9 @@ private fun Content() {
|
||||||
iconComposable = {
|
iconComposable = {
|
||||||
Icon(
|
Icon(
|
||||||
modifier = Modifier.size(20.dp),
|
modifier = Modifier.size(20.dp),
|
||||||
imageVector = CompoundIcons.Close(),
|
imageVector = CompoundIcons.Info(),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
tint = ElementTheme.colors.iconCriticalPrimary,
|
tint = ElementTheme.colors.iconSecondary,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
@ -109,9 +108,9 @@ private fun Content() {
|
||||||
iconComposable = {
|
iconComposable = {
|
||||||
Icon(
|
Icon(
|
||||||
modifier = Modifier.size(20.dp),
|
modifier = Modifier.size(20.dp),
|
||||||
imageVector = CompoundIcons.Close(),
|
imageVector = CompoundIcons.Info(),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
tint = ElementTheme.colors.iconCriticalPrimary,
|
tint = ElementTheme.colors.iconSecondary,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
<string name="screen_create_new_recovery_key_title">"Reset the encryption for your account using another device"</string>
|
<string name="screen_create_new_recovery_key_title">"Reset the encryption for your account using another device"</string>
|
||||||
<string name="screen_encryption_reset_action_continue_reset">"Continue reset"</string>
|
<string name="screen_encryption_reset_action_continue_reset">"Continue reset"</string>
|
||||||
<string name="screen_encryption_reset_bullet_1">"Your account details, contacts, preferences, and chat list will be kept"</string>
|
<string name="screen_encryption_reset_bullet_1">"Your account details, contacts, preferences, and chat list will be kept"</string>
|
||||||
<string name="screen_encryption_reset_bullet_2">"You will lose your existing message history unless it is stored on another device"</string>
|
<string name="screen_encryption_reset_bullet_2">"You will lose any message history that’s stored only on the server"</string>
|
||||||
<string name="screen_encryption_reset_bullet_3">"You will need to verify all your existing devices and contacts again"</string>
|
<string name="screen_encryption_reset_bullet_3">"You will need to verify all your existing devices and contacts again"</string>
|
||||||
<string name="screen_encryption_reset_footer">"Only reset your identity if you don’t have access to another signed-in device and you’ve lost your recovery key."</string>
|
<string name="screen_encryption_reset_footer">"Only reset your identity if you don’t have access to another signed-in device and you’ve lost your recovery key."</string>
|
||||||
<string name="screen_encryption_reset_title">"Can\'t confirm? You’ll need to reset your identity."</string>
|
<string name="screen_encryption_reset_title">"Can\'t confirm? You’ll need to reset your identity."</string>
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ class ResetIdentityRootViewTest {
|
||||||
ResetIdentityRootState(displayConfirmationDialog = false, eventSink = eventsRecorder),
|
ResetIdentityRootState(displayConfirmationDialog = false, eventSink = eventsRecorder),
|
||||||
)
|
)
|
||||||
|
|
||||||
rule.clickOn(CommonStrings.action_continue)
|
rule.clickOn(R.string.screen_encryption_reset_action_continue_reset)
|
||||||
|
|
||||||
eventsRecorder.assertSingle(ResetIdentityRootEvent.Continue)
|
eventsRecorder.assertSingle(ResetIdentityRootEvent.Continue)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@ android {
|
||||||
setupAnvil()
|
setupAnvil()
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation(projects.appconfig)
|
||||||
|
implementation(projects.libraries.androidutils)
|
||||||
implementation(projects.libraries.core)
|
implementation(projects.libraries.core)
|
||||||
implementation(projects.libraries.architecture)
|
implementation(projects.libraries.architecture)
|
||||||
implementation(projects.libraries.matrix.api)
|
implementation(projects.libraries.matrix.api)
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,11 @@ import com.bumble.appyx.core.plugin.plugins
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import io.element.android.anvilannotations.ContributesNode
|
import io.element.android.anvilannotations.ContributesNode
|
||||||
|
import io.element.android.appconfig.LearnMoreConfig
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
import io.element.android.features.logout.api.util.onSuccessLogout
|
import io.element.android.features.logout.api.util.onSuccessLogout
|
||||||
import io.element.android.features.verifysession.api.VerifySessionEntryPoint
|
import io.element.android.features.verifysession.api.VerifySessionEntryPoint
|
||||||
|
import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTab
|
||||||
import io.element.android.libraries.architecture.inputs
|
import io.element.android.libraries.architecture.inputs
|
||||||
import io.element.android.libraries.di.SessionScope
|
import io.element.android.libraries.di.SessionScope
|
||||||
|
|
||||||
|
|
@ -36,6 +38,10 @@ class VerifySelfSessionNode @AssistedInject constructor(
|
||||||
showDeviceVerifiedScreen = inputs<VerifySessionEntryPoint.Params>().showDeviceVerifiedScreen,
|
showDeviceVerifiedScreen = inputs<VerifySessionEntryPoint.Params>().showDeviceVerifiedScreen,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private fun onLearnMoreClick(activity: Activity, dark: Boolean) {
|
||||||
|
activity.openUrlInChromeCustomTab(null, dark, LearnMoreConfig.ENCRYPTION_URL)
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun View(modifier: Modifier) {
|
override fun View(modifier: Modifier) {
|
||||||
val state = presenter.present()
|
val state = presenter.present()
|
||||||
|
|
@ -44,6 +50,9 @@ class VerifySelfSessionNode @AssistedInject constructor(
|
||||||
VerifySelfSessionView(
|
VerifySelfSessionView(
|
||||||
state = state,
|
state = state,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
|
onLearnMoreClick = {
|
||||||
|
onLearnMoreClick(activity, isDark)
|
||||||
|
},
|
||||||
onEnterRecoveryKey = callback::onEnterRecoveryKey,
|
onEnterRecoveryKey = callback::onEnterRecoveryKey,
|
||||||
onResetKey = callback::onResetKey,
|
onResetKey = callback::onResetKey,
|
||||||
onFinish = callback::onDone,
|
onFinish = callback::onDone,
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@ data class VerifySelfSessionState(
|
||||||
@Stable
|
@Stable
|
||||||
sealed interface VerificationStep {
|
sealed interface VerificationStep {
|
||||||
data object Loading : VerificationStep
|
data object Loading : VerificationStep
|
||||||
|
|
||||||
|
// FIXME canEnterRecoveryKey value is never read.
|
||||||
data class Initial(val canEnterRecoveryKey: Boolean, val isLastDevice: Boolean = false) : VerificationStep
|
data class Initial(val canEnterRecoveryKey: Boolean, val isLastDevice: Boolean = false) : VerificationStep
|
||||||
data object Canceled : VerificationStep
|
data object Canceled : VerificationStep
|
||||||
data object AwaitingOtherDeviceResponse : VerificationStep
|
data object AwaitingOtherDeviceResponse : VerificationStep
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,13 @@ package io.element.android.features.verifysession.impl
|
||||||
|
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.ColumnScope
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
|
|
@ -64,6 +64,7 @@ import io.element.android.features.verifysession.impl.VerifySelfSessionState.Ver
|
||||||
@Composable
|
@Composable
|
||||||
fun VerifySelfSessionView(
|
fun VerifySelfSessionView(
|
||||||
state: VerifySelfSessionState,
|
state: VerifySelfSessionState,
|
||||||
|
onLearnMoreClick: () -> Unit,
|
||||||
onEnterRecoveryKey: () -> Unit,
|
onEnterRecoveryKey: () -> Unit,
|
||||||
onResetKey: () -> Unit,
|
onResetKey: () -> Unit,
|
||||||
onFinish: () -> Unit,
|
onFinish: () -> Unit,
|
||||||
|
|
@ -140,7 +141,10 @@ fun VerifySelfSessionView(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Content(flowState = verificationFlowStep)
|
Content(
|
||||||
|
flowState = verificationFlowStep,
|
||||||
|
onLearnMoreClick = onLearnMoreClick,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -203,38 +207,68 @@ private fun HeaderContent(verificationFlowStep: FlowStep) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun Content(flowState: FlowStep) {
|
private fun Content(
|
||||||
Column(Modifier.fillMaxHeight(), verticalArrangement = Arrangement.Center) {
|
flowState: FlowStep,
|
||||||
if (flowState is FlowStep.Verifying) {
|
onLearnMoreClick: () -> Unit,
|
||||||
|
) {
|
||||||
|
when (flowState) {
|
||||||
|
is VerifySelfSessionState.VerificationStep.Initial -> {
|
||||||
|
ContentInitial(onLearnMoreClick)
|
||||||
|
}
|
||||||
|
is FlowStep.Verifying -> {
|
||||||
ContentVerifying(flowState)
|
ContentVerifying(flowState)
|
||||||
}
|
}
|
||||||
|
else -> Unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ContentInitial(
|
||||||
|
onLearnMoreClick: () -> Unit,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.Center,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier
|
||||||
|
.clickable { onLearnMoreClick() }
|
||||||
|
.padding(vertical = 4.dp, horizontal = 16.dp),
|
||||||
|
text = stringResource(CommonStrings.action_learn_more),
|
||||||
|
style = ElementTheme.typography.fontBodyLgMedium
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ContentVerifying(verificationFlowStep: FlowStep.Verifying) {
|
private fun ContentVerifying(verificationFlowStep: FlowStep.Verifying) {
|
||||||
when (verificationFlowStep.data) {
|
Box(
|
||||||
is SessionVerificationData.Decimals -> {
|
modifier = Modifier.fillMaxSize(),
|
||||||
val text = verificationFlowStep.data.decimals.joinToString(separator = " - ") { it.toString() }
|
contentAlignment = Alignment.Center
|
||||||
Text(
|
) {
|
||||||
modifier = Modifier.fillMaxWidth(),
|
when (verificationFlowStep.data) {
|
||||||
text = text,
|
is SessionVerificationData.Decimals -> {
|
||||||
style = ElementTheme.typography.fontHeadingLgBold,
|
val text = verificationFlowStep.data.decimals.joinToString(separator = " - ") { it.toString() }
|
||||||
color = MaterialTheme.colorScheme.primary,
|
Text(
|
||||||
textAlign = TextAlign.Center,
|
modifier = Modifier.fillMaxWidth(),
|
||||||
)
|
text = text,
|
||||||
}
|
style = ElementTheme.typography.fontHeadingLgBold,
|
||||||
is SessionVerificationData.Emojis -> {
|
color = MaterialTheme.colorScheme.primary,
|
||||||
// We want each row to have up to 4 emojis
|
textAlign = TextAlign.Center,
|
||||||
val rows = verificationFlowStep.data.emojis.chunked(4)
|
)
|
||||||
Column(
|
}
|
||||||
modifier = Modifier.fillMaxWidth(),
|
is SessionVerificationData.Emojis -> {
|
||||||
verticalArrangement = Arrangement.spacedBy(40.dp),
|
// We want each row to have up to 4 emojis
|
||||||
) {
|
val rows = verificationFlowStep.data.emojis.chunked(4)
|
||||||
rows.forEach { emojis ->
|
Column(
|
||||||
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly) {
|
modifier = Modifier.fillMaxWidth(),
|
||||||
for (emoji in emojis) {
|
verticalArrangement = Arrangement.spacedBy(40.dp),
|
||||||
EmojiItemView(emoji = emoji, modifier = Modifier.widthIn(max = 60.dp))
|
) {
|
||||||
|
rows.forEach { emojis ->
|
||||||
|
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly) {
|
||||||
|
for (emoji in emojis) {
|
||||||
|
EmojiItemView(emoji = emoji, modifier = Modifier.widthIn(max = 60.dp))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -292,14 +326,14 @@ private fun BottomMenu(
|
||||||
text = stringResource(R.string.screen_identity_use_another_device),
|
text = stringResource(R.string.screen_identity_use_another_device),
|
||||||
onClick = { eventSink(VerifySelfSessionViewEvents.RequestVerification) },
|
onClick = { eventSink(VerifySelfSessionViewEvents.RequestVerification) },
|
||||||
)
|
)
|
||||||
OutlinedButton(
|
Button(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
text = stringResource(R.string.screen_session_verification_enter_recovery_key),
|
text = stringResource(R.string.screen_session_verification_enter_recovery_key),
|
||||||
onClick = onEnterRecoveryKey,
|
onClick = onEnterRecoveryKey,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// This option should always be displayed
|
// This option should always be displayed
|
||||||
TextButton(
|
OutlinedButton(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
text = stringResource(R.string.screen_identity_confirmation_cannot_confirm),
|
text = stringResource(R.string.screen_identity_confirmation_cannot_confirm),
|
||||||
onClick = onResetKey,
|
onClick = onResetKey,
|
||||||
|
|
@ -402,6 +436,7 @@ private fun BottomMenu(
|
||||||
internal fun VerifySelfSessionViewPreview(@PreviewParameter(VerifySelfSessionStateProvider::class) state: VerifySelfSessionState) = ElementPreview {
|
internal fun VerifySelfSessionViewPreview(@PreviewParameter(VerifySelfSessionStateProvider::class) state: VerifySelfSessionState) = ElementPreview {
|
||||||
VerifySelfSessionView(
|
VerifySelfSessionView(
|
||||||
state = state,
|
state = state,
|
||||||
|
onLearnMoreClick = {},
|
||||||
onEnterRecoveryKey = {},
|
onEnterRecoveryKey = {},
|
||||||
onResetKey = {},
|
onResetKey = {},
|
||||||
onFinish = {},
|
onFinish = {},
|
||||||
|
|
|
||||||
|
|
@ -343,6 +343,7 @@ class VerifySelfSessionPresenterTest {
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
val initialItem = awaitItem()
|
val initialItem = awaitItem()
|
||||||
initialItem.eventSink(VerifySelfSessionViewEvents.SignOut)
|
initialItem.eventSink(VerifySelfSessionViewEvents.SignOut)
|
||||||
|
assertThat(awaitItem().signOutAction.isLoading()).isTrue()
|
||||||
val finalItem = awaitItem()
|
val finalItem = awaitItem()
|
||||||
assertThat(finalItem.signOutAction.isSuccess()).isTrue()
|
assertThat(finalItem.signOutAction.isSuccess()).isTrue()
|
||||||
assertThat(finalItem.signOutAction.dataOrNull()).isEqualTo("aUrl")
|
assertThat(finalItem.signOutAction.dataOrNull()).isEqualTo("aUrl")
|
||||||
|
|
|
||||||
|
|
@ -146,6 +146,22 @@ class VerifySelfSessionViewTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Config(qualifiers = "h1024dp")
|
||||||
|
@Test
|
||||||
|
fun `clicking on learn more invokes the expected callback`() {
|
||||||
|
val eventsRecorder = EventsRecorder<VerifySelfSessionViewEvents>(expectEvents = false)
|
||||||
|
ensureCalledOnce { callback ->
|
||||||
|
rule.setVerifySelfSessionView(
|
||||||
|
aVerifySelfSessionState(
|
||||||
|
verificationFlowStep = VerifySelfSessionState.VerificationStep.Initial(true),
|
||||||
|
eventSink = eventsRecorder
|
||||||
|
),
|
||||||
|
onLearnMoreClick = callback,
|
||||||
|
)
|
||||||
|
rule.clickOn(CommonStrings.action_learn_more)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on they match emits the expected event`() {
|
fun `clicking on they match emits the expected event`() {
|
||||||
val eventsRecorder = EventsRecorder<VerifySelfSessionViewEvents>()
|
val eventsRecorder = EventsRecorder<VerifySelfSessionViewEvents>()
|
||||||
|
|
@ -222,6 +238,7 @@ class VerifySelfSessionViewTest {
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setVerifySelfSessionView(
|
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setVerifySelfSessionView(
|
||||||
state: VerifySelfSessionState,
|
state: VerifySelfSessionState,
|
||||||
|
onLearnMoreClick: () -> Unit = EnsureNeverCalled(),
|
||||||
onEnterRecoveryKey: () -> Unit = EnsureNeverCalled(),
|
onEnterRecoveryKey: () -> Unit = EnsureNeverCalled(),
|
||||||
onFinished: () -> Unit = EnsureNeverCalled(),
|
onFinished: () -> Unit = EnsureNeverCalled(),
|
||||||
onResetKey: () -> Unit = EnsureNeverCalled(),
|
onResetKey: () -> Unit = EnsureNeverCalled(),
|
||||||
|
|
@ -230,6 +247,7 @@ class VerifySelfSessionViewTest {
|
||||||
setContent {
|
setContent {
|
||||||
VerifySelfSessionView(
|
VerifySelfSessionView(
|
||||||
state = state,
|
state = state,
|
||||||
|
onLearnMoreClick = onLearnMoreClick,
|
||||||
onEnterRecoveryKey = onEnterRecoveryKey,
|
onEnterRecoveryKey = onEnterRecoveryKey,
|
||||||
onFinish = onFinished,
|
onFinish = onFinished,
|
||||||
onResetKey = onResetKey,
|
onResetKey = onResetKey,
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,9 @@
|
||||||
[versions]
|
[versions]
|
||||||
# Project
|
# Project
|
||||||
android_gradle_plugin = "8.7.0"
|
android_gradle_plugin = "8.7.0"
|
||||||
kotlin = "1.9.25"
|
kotlin = "2.0.20"
|
||||||
kotlinpoetKsp = "1.18.1"
|
kotlinpoetKsp = "1.18.1"
|
||||||
ksp = "1.9.25-1.0.20"
|
ksp = "2.0.20-1.0.25"
|
||||||
firebaseAppDistribution = "5.0.0"
|
firebaseAppDistribution = "5.0.0"
|
||||||
|
|
||||||
# AndroidX
|
# AndroidX
|
||||||
|
|
@ -25,7 +25,7 @@ media3 = "1.4.1"
|
||||||
camera = "1.3.4"
|
camera = "1.3.4"
|
||||||
|
|
||||||
# Compose
|
# Compose
|
||||||
compose_bom = "2024.09.02"
|
compose_bom = "2024.09.03"
|
||||||
composecompiler = "1.5.15"
|
composecompiler = "1.5.15"
|
||||||
|
|
||||||
# Coroutines
|
# Coroutines
|
||||||
|
|
@ -40,7 +40,7 @@ test_core = "1.6.1"
|
||||||
#other
|
#other
|
||||||
coil = "2.7.0"
|
coil = "2.7.0"
|
||||||
datetime = "0.6.0"
|
datetime = "0.6.0"
|
||||||
dependencyAnalysis = "2.1.3"
|
dependencyAnalysis = "2.1.4"
|
||||||
serialization_json = "1.6.3"
|
serialization_json = "1.6.3"
|
||||||
showkase = "1.0.3"
|
showkase = "1.0.3"
|
||||||
appyx = "1.4.0"
|
appyx = "1.4.0"
|
||||||
|
|
@ -62,6 +62,7 @@ kover = "0.8.3"
|
||||||
[libraries]
|
[libraries]
|
||||||
# Project
|
# Project
|
||||||
android_gradle_plugin = { module = "com.android.tools.build:gradle", version.ref = "android_gradle_plugin" }
|
android_gradle_plugin = { module = "com.android.tools.build:gradle", version.ref = "android_gradle_plugin" }
|
||||||
|
compose_compiler_plugin = { module = "org.jetbrains.kotlin:compose-compiler-gradle-plugin", version.ref = "kotlin" }
|
||||||
# https://developer.android.com/studio/write/java8-support#library-desugaring-versions
|
# https://developer.android.com/studio/write/java8-support#library-desugaring-versions
|
||||||
android_desugar = "com.android.tools:desugar_jdk_libs:2.1.2"
|
android_desugar = "com.android.tools:desugar_jdk_libs:2.1.2"
|
||||||
anvil_gradle_plugin = { module = "dev.zacsweers.anvil:gradle-plugin", version.ref = "anvil" }
|
anvil_gradle_plugin = { module = "dev.zacsweers.anvil:gradle-plugin", version.ref = "anvil" }
|
||||||
|
|
@ -103,7 +104,7 @@ androidx_activity_activity = { module = "androidx.activity:activity", version.re
|
||||||
androidx_activity_compose = { module = "androidx.activity:activity-compose", version.ref = "activity" }
|
androidx_activity_compose = { module = "androidx.activity:activity-compose", version.ref = "activity" }
|
||||||
androidx_startup = "androidx.startup:startup-runtime:1.2.0"
|
androidx_startup = "androidx.startup:startup-runtime:1.2.0"
|
||||||
androidx_preference = "androidx.preference:preference:1.2.1"
|
androidx_preference = "androidx.preference:preference:1.2.1"
|
||||||
androidx_webkit = "androidx.webkit:webkit:1.12.0"
|
androidx_webkit = "androidx.webkit:webkit:1.12.1"
|
||||||
|
|
||||||
androidx_compose_bom = { module = "androidx.compose:compose-bom", version.ref = "compose_bom" }
|
androidx_compose_bom = { module = "androidx.compose:compose-bom", version.ref = "compose_bom" }
|
||||||
androidx_compose_material3 = { module = "androidx.compose.material3:material3" }
|
androidx_compose_material3 = { module = "androidx.compose.material3:material3" }
|
||||||
|
|
@ -143,7 +144,7 @@ test_corektx = { module = "androidx.test:core-ktx", version.ref = "test_core" }
|
||||||
test_arch_core = "androidx.arch.core:core-testing:2.2.0"
|
test_arch_core = "androidx.arch.core:core-testing:2.2.0"
|
||||||
test_junit = "junit:junit:4.13.2"
|
test_junit = "junit:junit:4.13.2"
|
||||||
test_runner = "androidx.test:runner:1.6.2"
|
test_runner = "androidx.test:runner:1.6.2"
|
||||||
test_mockk = "io.mockk:mockk:1.13.12"
|
test_mockk = "io.mockk:mockk:1.13.13"
|
||||||
test_konsist = "com.lemonappdev:konsist:0.16.1"
|
test_konsist = "com.lemonappdev:konsist:0.16.1"
|
||||||
test_turbine = "app.cash.turbine:turbine:1.1.0"
|
test_turbine = "app.cash.turbine:turbine:1.1.0"
|
||||||
test_truth = "com.google.truth:truth:1.4.4"
|
test_truth = "com.google.truth:truth:1.4.4"
|
||||||
|
|
@ -189,13 +190,13 @@ kotlinpoet = "com.squareup:kotlinpoet:1.18.1"
|
||||||
zxing_cpp = "io.github.zxing-cpp:android:2.2.0"
|
zxing_cpp = "io.github.zxing-cpp:android:2.2.0"
|
||||||
|
|
||||||
# Analytics
|
# Analytics
|
||||||
posthog = "com.posthog:posthog-android:3.8.0"
|
posthog = "com.posthog:posthog-android:3.8.1"
|
||||||
sentry = "io.sentry:sentry-android:7.14.0"
|
sentry = "io.sentry:sentry-android:7.14.0"
|
||||||
# main branch can be tested replacing the version with main-SNAPSHOT
|
# main branch can be tested replacing the version with main-SNAPSHOT
|
||||||
matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:0.25.0"
|
matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:0.25.0"
|
||||||
|
|
||||||
# Emojibase
|
# Emojibase
|
||||||
matrix_emojibase_bindings = "io.element.android:emojibase-bindings:1.1.3"
|
matrix_emojibase_bindings = "io.element.android:emojibase-bindings:1.3.3"
|
||||||
sigpwned_emoji4j = "com.sigpwned:emoji4j-core:15.1.2"
|
sigpwned_emoji4j = "com.sigpwned:emoji4j-core:15.1.2"
|
||||||
|
|
||||||
# Di
|
# Di
|
||||||
|
|
@ -238,3 +239,4 @@ firebaseAppDistribution = { id = "com.google.firebase.appdistribution", version.
|
||||||
knit = { id = "org.jetbrains.kotlinx.knit", version = "0.5.0" }
|
knit = { id = "org.jetbrains.kotlinx.knit", version = "0.5.0" }
|
||||||
sonarqube = "org.sonarqube:5.1.0.4882"
|
sonarqube = "org.sonarqube:5.1.0.4882"
|
||||||
licensee = "app.cash.licensee:1.11.0"
|
licensee = "app.cash.licensee:1.11.0"
|
||||||
|
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.StrokeCap
|
import androidx.compose.ui.graphics.StrokeCap
|
||||||
import androidx.compose.ui.platform.LocalInspectionMode
|
import androidx.compose.ui.platform.LocalInspectionMode
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
|
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewGroup
|
import io.element.android.libraries.designsystem.preview.PreviewGroup
|
||||||
|
|
@ -24,6 +25,7 @@ import io.element.android.libraries.designsystem.preview.PreviewGroup
|
||||||
fun LinearProgressIndicator(
|
fun LinearProgressIndicator(
|
||||||
progress: () -> Float,
|
progress: () -> Float,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
gapSize: Dp = 0.dp,
|
||||||
color: Color = ProgressIndicatorDefaults.linearColor,
|
color: Color = ProgressIndicatorDefaults.linearColor,
|
||||||
trackColor: Color = ProgressIndicatorDefaults.linearTrackColor,
|
trackColor: Color = ProgressIndicatorDefaults.linearTrackColor,
|
||||||
strokeCap: StrokeCap = ProgressIndicatorDefaults.LinearStrokeCap,
|
strokeCap: StrokeCap = ProgressIndicatorDefaults.LinearStrokeCap,
|
||||||
|
|
@ -31,9 +33,11 @@ fun LinearProgressIndicator(
|
||||||
androidx.compose.material3.LinearProgressIndicator(
|
androidx.compose.material3.LinearProgressIndicator(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
progress = progress,
|
progress = progress,
|
||||||
|
gapSize = gapSize,
|
||||||
color = color,
|
color = color,
|
||||||
trackColor = trackColor,
|
trackColor = trackColor,
|
||||||
strokeCap = strokeCap,
|
strokeCap = strokeCap,
|
||||||
|
drawStopIndicator = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -41,6 +45,7 @@ fun LinearProgressIndicator(
|
||||||
fun LinearProgressIndicator(
|
fun LinearProgressIndicator(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
color: Color = ProgressIndicatorDefaults.linearColor,
|
color: Color = ProgressIndicatorDefaults.linearColor,
|
||||||
|
gapSize: Dp = 0.dp,
|
||||||
trackColor: Color = ProgressIndicatorDefaults.linearTrackColor,
|
trackColor: Color = ProgressIndicatorDefaults.linearTrackColor,
|
||||||
strokeCap: StrokeCap = ProgressIndicatorDefaults.LinearStrokeCap,
|
strokeCap: StrokeCap = ProgressIndicatorDefaults.LinearStrokeCap,
|
||||||
) {
|
) {
|
||||||
|
|
@ -49,14 +54,17 @@ fun LinearProgressIndicator(
|
||||||
androidx.compose.material3.LinearProgressIndicator(
|
androidx.compose.material3.LinearProgressIndicator(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
progress = { 0.75F },
|
progress = { 0.75F },
|
||||||
|
gapSize = gapSize,
|
||||||
color = color,
|
color = color,
|
||||||
trackColor = trackColor,
|
trackColor = trackColor,
|
||||||
strokeCap = strokeCap,
|
strokeCap = strokeCap,
|
||||||
|
drawStopIndicator = {},
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
androidx.compose.material3.LinearProgressIndicator(
|
androidx.compose.material3.LinearProgressIndicator(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
color = color,
|
color = color,
|
||||||
|
gapSize = gapSize,
|
||||||
trackColor = trackColor,
|
trackColor = trackColor,
|
||||||
strokeCap = strokeCap,
|
strokeCap = strokeCap,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@
|
||||||
|
|
||||||
package io.element.android.libraries.designsystem.theme.components.bottomsheet
|
package io.element.android.libraries.designsystem.theme.components.bottomsheet
|
||||||
|
|
||||||
import androidx.compose.animation.core.DecayAnimationSpec
|
|
||||||
import androidx.compose.animation.core.SpringSpec
|
import androidx.compose.animation.core.SpringSpec
|
||||||
import androidx.compose.animation.core.exponentialDecay
|
import androidx.compose.animation.core.exponentialDecay
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
|
|
@ -296,13 +295,9 @@ internal object AnchoredDraggableDefaults {
|
||||||
/**
|
/**
|
||||||
* The default animation used by [AnchoredDraggableState].
|
* The default animation used by [AnchoredDraggableState].
|
||||||
*/
|
*/
|
||||||
@get:ExperimentalMaterial3Api
|
|
||||||
@Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
|
|
||||||
@ExperimentalMaterial3Api
|
@ExperimentalMaterial3Api
|
||||||
val SnapAnimationSpec = SpringSpec<Float>()
|
val SnapAnimationSpec = SpringSpec<Float>()
|
||||||
|
|
||||||
@get:ExperimentalMaterial3Api
|
|
||||||
@Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
|
|
||||||
@ExperimentalMaterial3Api
|
@ExperimentalMaterial3Api
|
||||||
val DecayAnimationSpec = exponentialDecay<Float>()
|
val DecayAnimationSpec = exponentialDecay<Float>()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ package io.element.android.libraries.featureflag.api
|
||||||
|
|
||||||
import io.element.android.appconfig.OnBoardingConfig
|
import io.element.android.appconfig.OnBoardingConfig
|
||||||
import io.element.android.libraries.core.meta.BuildMeta
|
import io.element.android.libraries.core.meta.BuildMeta
|
||||||
import io.element.android.libraries.core.meta.BuildType
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* To enable or disable a FeatureFlags, change the `defaultValue` value.
|
* To enable or disable a FeatureFlags, change the `defaultValue` value.
|
||||||
|
|
@ -93,13 +92,7 @@ enum class FeatureFlags(
|
||||||
key = "feature.qrCodeLogin",
|
key = "feature.qrCodeLogin",
|
||||||
title = "Enable login using QR code",
|
title = "Enable login using QR code",
|
||||||
description = "Allow the user to login using the QR code flow",
|
description = "Allow the user to login using the QR code flow",
|
||||||
defaultValue = { buildMeta ->
|
defaultValue = { OnBoardingConfig.CAN_LOGIN_WITH_QR_CODE },
|
||||||
when (buildMeta.buildType) {
|
|
||||||
// TODO remove once the feature is ready to publish
|
|
||||||
BuildType.RELEASE -> false
|
|
||||||
else -> OnBoardingConfig.CAN_LOGIN_WITH_QR_CODE
|
|
||||||
}
|
|
||||||
},
|
|
||||||
isFinished = false,
|
isFinished = false,
|
||||||
),
|
),
|
||||||
IncomingShare(
|
IncomingShare(
|
||||||
|
|
@ -132,4 +125,17 @@ enum class FeatureFlags(
|
||||||
defaultValue = { false },
|
defaultValue = { false },
|
||||||
isFinished = false,
|
isFinished = false,
|
||||||
),
|
),
|
||||||
|
IdentityPinningViolationNotifications(
|
||||||
|
key = "feature.identityPinningViolationNotifications",
|
||||||
|
title = "Identity pinning violation notifications",
|
||||||
|
description = null,
|
||||||
|
defaultValue = { buildMeta ->
|
||||||
|
when (buildMeta.buildType) {
|
||||||
|
// Do not enable this feature in release builds
|
||||||
|
BuildType.RELEASE -> false
|
||||||
|
else -> true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isFinished = false,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,8 @@ import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
import java.io.Closeable
|
import java.io.Closeable
|
||||||
import java.util.Optional
|
import java.util.Optional
|
||||||
|
|
||||||
|
|
@ -94,7 +96,13 @@ interface MatrixClient : Closeable {
|
||||||
suspend fun getAccountManagementUrl(action: AccountManagementAction?): Result<String?>
|
suspend fun getAccountManagementUrl(action: AccountManagementAction?): Result<String?>
|
||||||
suspend fun uploadMedia(mimeType: String, data: ByteArray, progressCallback: ProgressCallback?): Result<String>
|
suspend fun uploadMedia(mimeType: String, data: ByteArray, progressCallback: ProgressCallback?): Result<String>
|
||||||
fun roomMembershipObserver(): RoomMembershipObserver
|
fun roomMembershipObserver(): RoomMembershipObserver
|
||||||
fun getRoomInfoFlow(roomId: RoomId): Flow<Optional<MatrixRoomInfo>>
|
|
||||||
|
/**
|
||||||
|
* Get a room summary flow for a given room ID or alias.
|
||||||
|
* The flow will emit a new value whenever the room summary is updated.
|
||||||
|
* The flow will emit Optional.empty item if the room is not found.
|
||||||
|
*/
|
||||||
|
fun getRoomSummaryFlow(roomIdOrAlias: RoomIdOrAlias): Flow<Optional<RoomSummary>>
|
||||||
|
|
||||||
fun isMe(userId: UserId?) = userId == sessionId
|
fun isMe(userId: UserId?) = userId == sessionId
|
||||||
|
|
||||||
|
|
@ -142,3 +150,14 @@ interface MatrixClient : Closeable {
|
||||||
fun canDeactivateAccount(): Boolean
|
fun canDeactivateAccount(): Boolean
|
||||||
suspend fun deactivateAccount(password: String, eraseData: Boolean): Result<Unit>
|
suspend fun deactivateAccount(password: String, eraseData: Boolean): Result<Unit>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a room info flow for a given room ID or alias.
|
||||||
|
* The flow will emit a new value whenever the room info is updated.
|
||||||
|
* The flow will emit Optional.empty item if the room is not found.
|
||||||
|
*/
|
||||||
|
fun MatrixClient.getRoomInfoFlow(roomIdOrAlias: RoomIdOrAlias): Flow<Optional<MatrixRoomInfo>> {
|
||||||
|
return getRoomSummaryFlow(roomIdOrAlias)
|
||||||
|
.map { roomSummary -> roomSummary.map { it.info } }
|
||||||
|
.distinctUntilChanged()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,13 @@ data class MatrixRoomInfo(
|
||||||
val canonicalAlias: RoomAlias?,
|
val canonicalAlias: RoomAlias?,
|
||||||
val alternativeAliases: ImmutableList<RoomAlias>,
|
val alternativeAliases: ImmutableList<RoomAlias>,
|
||||||
val currentUserMembership: CurrentUserMembership,
|
val currentUserMembership: CurrentUserMembership,
|
||||||
|
/**
|
||||||
|
* Member who invited the current user to a room that's in the invited
|
||||||
|
* state.
|
||||||
|
*
|
||||||
|
* Can be missing if the room membership invite event is missing from the
|
||||||
|
* store.
|
||||||
|
*/
|
||||||
val inviter: RoomMember?,
|
val inviter: RoomMember?,
|
||||||
val activeMembersCount: Long,
|
val activeMembersCount: Long,
|
||||||
val invitedMembersCount: Long,
|
val invitedMembersCount: Long,
|
||||||
|
|
@ -43,7 +50,26 @@ data class MatrixRoomInfo(
|
||||||
val userDefinedNotificationMode: RoomNotificationMode?,
|
val userDefinedNotificationMode: RoomNotificationMode?,
|
||||||
val hasRoomCall: Boolean,
|
val hasRoomCall: Boolean,
|
||||||
val activeRoomCallParticipants: ImmutableList<UserId>,
|
val activeRoomCallParticipants: ImmutableList<UserId>,
|
||||||
|
val isMarkedUnread: Boolean,
|
||||||
|
/**
|
||||||
|
* "Interesting" messages received in that room, independently of the
|
||||||
|
* notification settings.
|
||||||
|
*/
|
||||||
|
val numUnreadMessages: Long,
|
||||||
|
/**
|
||||||
|
* Events that will notify the user, according to their
|
||||||
|
* notification settings.
|
||||||
|
*/
|
||||||
|
val numUnreadNotifications: Long,
|
||||||
|
/**
|
||||||
|
* Events causing mentions/highlights for the user, according to their
|
||||||
|
* notification settings.
|
||||||
|
*/
|
||||||
|
val numUnreadMentions: Long,
|
||||||
val heroes: ImmutableList<MatrixUser>,
|
val heroes: ImmutableList<MatrixUser>,
|
||||||
val pinnedEventIds: ImmutableList<EventId>,
|
val pinnedEventIds: ImmutableList<EventId>,
|
||||||
val creator: UserId?,
|
val creator: UserId?,
|
||||||
)
|
) {
|
||||||
|
val aliases: List<RoomAlias>
|
||||||
|
get() = listOfNotNull(canonicalAlias) + alternativeAliases
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,35 +7,14 @@
|
||||||
|
|
||||||
package io.element.android.libraries.matrix.api.roomlist
|
package io.element.android.libraries.matrix.api.roomlist
|
||||||
|
|
||||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
import io.element.android.libraries.matrix.api.room.MatrixRoomInfo
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
|
||||||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
|
||||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
|
||||||
import io.element.android.libraries.matrix.api.room.message.RoomMessage
|
import io.element.android.libraries.matrix.api.room.message.RoomMessage
|
||||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
|
||||||
|
|
||||||
data class RoomSummary(
|
data class RoomSummary(
|
||||||
val roomId: RoomId,
|
val info: MatrixRoomInfo,
|
||||||
val name: String?,
|
|
||||||
val canonicalAlias: RoomAlias?,
|
|
||||||
val alternativeAliases: List<RoomAlias>,
|
|
||||||
val isDirect: Boolean,
|
|
||||||
val avatarUrl: String?,
|
|
||||||
val lastMessage: RoomMessage?,
|
val lastMessage: RoomMessage?,
|
||||||
val numUnreadMessages: Int,
|
|
||||||
val numUnreadMentions: Int,
|
|
||||||
val numUnreadNotifications: Int,
|
|
||||||
val isMarkedUnread: Boolean,
|
|
||||||
val inviter: RoomMember?,
|
|
||||||
val userDefinedNotificationMode: RoomNotificationMode?,
|
|
||||||
val hasRoomCall: Boolean,
|
|
||||||
val isDm: Boolean,
|
|
||||||
val isFavorite: Boolean,
|
|
||||||
val currentUserMembership: CurrentUserMembership,
|
|
||||||
val heroes: List<MatrixUser>,
|
|
||||||
) {
|
) {
|
||||||
|
val roomId = info.id
|
||||||
val lastMessageTimestamp = lastMessage?.originServerTs
|
val lastMessageTimestamp = lastMessage?.originServerTs
|
||||||
val aliases: List<RoomAlias>
|
val isOneToOne get() = info.activeMembersCount == 2L
|
||||||
get() = listOfNotNull(canonicalAlias) + alternativeAliases
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,6 @@ import io.element.android.libraries.matrix.api.pusher.PushersService
|
||||||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||||
import io.element.android.libraries.matrix.api.room.InvitedRoom
|
import io.element.android.libraries.matrix.api.room.InvitedRoom
|
||||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||||
import io.element.android.libraries.matrix.api.room.MatrixRoomInfo
|
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
|
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
|
||||||
import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias
|
import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias
|
||||||
import io.element.android.libraries.matrix.api.room.preview.RoomPreview
|
import io.element.android.libraries.matrix.api.room.preview.RoomPreview
|
||||||
|
|
@ -84,8 +83,8 @@ import kotlinx.coroutines.flow.buffer
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.filter
|
import kotlinx.coroutines.flow.filter
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.flow
|
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.mapNotNull
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
|
@ -105,8 +104,8 @@ import org.matrix.rustcomponents.sdk.use
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.Optional
|
import java.util.Optional
|
||||||
|
import kotlin.jvm.optionals.getOrNull
|
||||||
import kotlin.time.Duration
|
import kotlin.time.Duration
|
||||||
import kotlin.time.Duration.Companion.INFINITE
|
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
import org.matrix.rustcomponents.sdk.CreateRoomParameters as RustCreateRoomParameters
|
import org.matrix.rustcomponents.sdk.CreateRoomParameters as RustCreateRoomParameters
|
||||||
import org.matrix.rustcomponents.sdk.RoomPreset as RustRoomPreset
|
import org.matrix.rustcomponents.sdk.RoomPreset as RustRoomPreset
|
||||||
|
|
@ -262,21 +261,14 @@ class RustMatrixClient(
|
||||||
* @param timeout the timeout to wait for the room to be available
|
* @param timeout the timeout to wait for the room to be available
|
||||||
* @throws TimeoutCancellationException if the room is not available after the timeout
|
* @throws TimeoutCancellationException if the room is not available after the timeout
|
||||||
*/
|
*/
|
||||||
private suspend fun awaitJoinedRoom(roomIdOrAlias: RoomIdOrAlias, timeout: Duration): RoomSummary {
|
private suspend fun awaitJoinedRoom(
|
||||||
val predicate: (List<RoomSummary>) -> Boolean = when (roomIdOrAlias) {
|
roomIdOrAlias: RoomIdOrAlias,
|
||||||
is RoomIdOrAlias.Alias -> { roomSummaries: List<RoomSummary> ->
|
timeout: Duration
|
||||||
val found = roomSummaries.find { it.aliases.contains(roomIdOrAlias.roomAlias) }
|
): RoomSummary {
|
||||||
found != null && found.currentUserMembership == CurrentUserMembership.JOINED
|
|
||||||
}
|
|
||||||
is RoomIdOrAlias.Id -> { roomSummaries: List<RoomSummary> ->
|
|
||||||
val found = roomSummaries.find { it.roomId == roomIdOrAlias.roomId }
|
|
||||||
found != null && found.currentUserMembership == CurrentUserMembership.JOINED
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return withTimeout(timeout) {
|
return withTimeout(timeout) {
|
||||||
roomListService.allRooms.summaries
|
getRoomSummaryFlow(roomIdOrAlias)
|
||||||
.filter(predicate)
|
.mapNotNull { optionalRoomSummary -> optionalRoomSummary.getOrNull() }
|
||||||
.first()
|
.filter { roomSummary -> roomSummary.info.currentUserMembership == CurrentUserMembership.JOINED }
|
||||||
.first()
|
.first()
|
||||||
// Ensure that the room is ready
|
// Ensure that the room is ready
|
||||||
.also { client.awaitRoomRemoteEcho(it.roomId.value) }
|
.also { client.awaitRoomRemoteEcho(it.roomId.value) }
|
||||||
|
|
@ -568,20 +560,21 @@ class RustMatrixClient(
|
||||||
|
|
||||||
override fun roomMembershipObserver(): RoomMembershipObserver = roomMembershipObserver
|
override fun roomMembershipObserver(): RoomMembershipObserver = roomMembershipObserver
|
||||||
|
|
||||||
override fun getRoomInfoFlow(roomId: RoomId): Flow<Optional<MatrixRoomInfo>> {
|
override fun getRoomSummaryFlow(roomIdOrAlias: RoomIdOrAlias): Flow<Optional<RoomSummary>> {
|
||||||
return flow {
|
val predicate: (RoomSummary) -> Boolean = when (roomIdOrAlias) {
|
||||||
var room = getRoom(roomId)
|
is RoomIdOrAlias.Alias -> { roomSummary ->
|
||||||
if (room == null) {
|
roomSummary.info.aliases.contains(roomIdOrAlias.roomAlias)
|
||||||
emit(Optional.empty())
|
|
||||||
awaitJoinedRoom(roomId.toRoomIdOrAlias(), INFINITE)
|
|
||||||
room = getRoom(roomId)
|
|
||||||
}
|
}
|
||||||
room?.use {
|
is RoomIdOrAlias.Id -> { roomSummary ->
|
||||||
room.roomInfoFlow
|
roomSummary.roomId == roomIdOrAlias.roomId
|
||||||
.map { roomInfo -> Optional.of(roomInfo) }
|
|
||||||
.collect(this)
|
|
||||||
}
|
}
|
||||||
}.distinctUntilChanged()
|
}
|
||||||
|
return roomListService.allRooms.summaries
|
||||||
|
.map { roomSummaries ->
|
||||||
|
val roomSummary = roomSummaries.firstOrNull(predicate)
|
||||||
|
Optional.ofNullable(roomSummary)
|
||||||
|
}
|
||||||
|
.distinctUntilChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun setAllSendQueuesEnabled(enabled: Boolean) = withContext(sessionDispatcher) {
|
override suspend fun setAllSendQueuesEnabled(enabled: Boolean) = withContext(sessionDispatcher) {
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,10 @@ class MatrixRoomInfoMapper {
|
||||||
activeRoomCallParticipants = it.activeRoomCallParticipants.map(::UserId).toImmutableList(),
|
activeRoomCallParticipants = it.activeRoomCallParticipants.map(::UserId).toImmutableList(),
|
||||||
heroes = it.elementHeroes().toImmutableList(),
|
heroes = it.elementHeroes().toImmutableList(),
|
||||||
pinnedEventIds = it.pinnedEventIds.map(::EventId).toImmutableList(),
|
pinnedEventIds = it.pinnedEventIds.map(::EventId).toImmutableList(),
|
||||||
|
isMarkedUnread = it.isMarkedUnread,
|
||||||
|
numUnreadMessages = it.numUnreadMessages.toLong(),
|
||||||
|
numUnreadMentions = it.numUnreadMentions.toLong(),
|
||||||
|
numUnreadNotifications = it.numUnreadNotifications.toLong(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ internal class RoomListFactory(
|
||||||
private val innerRoomListService: RoomListService,
|
private val innerRoomListService: RoomListService,
|
||||||
private val sessionCoroutineScope: CoroutineScope,
|
private val sessionCoroutineScope: CoroutineScope,
|
||||||
) {
|
) {
|
||||||
private val roomSummaryDetailsFactory: RoomSummaryDetailsFactory = RoomSummaryDetailsFactory()
|
private val roomSummaryDetailsFactory: RoomSummaryFactory = RoomSummaryFactory()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a room list that can be used to load more rooms and filter them dynamically.
|
* Creates a room list that can be used to load more rooms and filter them dynamically.
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
package io.element.android.libraries.matrix.impl.roomlist
|
package io.element.android.libraries.matrix.impl.roomlist
|
||||||
|
|
||||||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||||
|
import io.element.android.libraries.matrix.api.room.isDm
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomListFilter
|
import io.element.android.libraries.matrix.api.roomlist.RoomListFilter
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
||||||
|
|
||||||
|
|
@ -17,19 +18,19 @@ val RoomListFilter.predicate
|
||||||
is RoomListFilter.Any -> { _: RoomSummary -> true }
|
is RoomListFilter.Any -> { _: RoomSummary -> true }
|
||||||
RoomListFilter.None -> { _: RoomSummary -> false }
|
RoomListFilter.None -> { _: RoomSummary -> false }
|
||||||
RoomListFilter.Category.Group -> { roomSummary: RoomSummary ->
|
RoomListFilter.Category.Group -> { roomSummary: RoomSummary ->
|
||||||
!roomSummary.isDm && !roomSummary.isInvited()
|
!roomSummary.info.isDm && !roomSummary.isInvited()
|
||||||
}
|
}
|
||||||
RoomListFilter.Category.People -> { roomSummary: RoomSummary ->
|
RoomListFilter.Category.People -> { roomSummary: RoomSummary ->
|
||||||
roomSummary.isDm && !roomSummary.isInvited()
|
roomSummary.info.isDm && !roomSummary.isInvited()
|
||||||
}
|
}
|
||||||
RoomListFilter.Favorite -> { roomSummary: RoomSummary ->
|
RoomListFilter.Favorite -> { roomSummary: RoomSummary ->
|
||||||
roomSummary.isFavorite && !roomSummary.isInvited()
|
roomSummary.info.isFavorite && !roomSummary.isInvited()
|
||||||
}
|
}
|
||||||
RoomListFilter.Unread -> { roomSummary: RoomSummary ->
|
RoomListFilter.Unread -> { roomSummary: RoomSummary ->
|
||||||
!roomSummary.isInvited() && (roomSummary.numUnreadNotifications > 0 || roomSummary.isMarkedUnread)
|
!roomSummary.isInvited() && (roomSummary.info.numUnreadNotifications > 0 || roomSummary.info.isMarkedUnread)
|
||||||
}
|
}
|
||||||
is RoomListFilter.NormalizedMatchRoomName -> { roomSummary: RoomSummary ->
|
is RoomListFilter.NormalizedMatchRoomName -> { roomSummary: RoomSummary ->
|
||||||
roomSummary.name.orEmpty().contains(pattern, ignoreCase = true)
|
roomSummary.info.name.orEmpty().contains(pattern, ignoreCase = true)
|
||||||
}
|
}
|
||||||
RoomListFilter.Invite -> { roomSummary: RoomSummary ->
|
RoomListFilter.Invite -> { roomSummary: RoomSummary ->
|
||||||
roomSummary.isInvited()
|
roomSummary.isInvited()
|
||||||
|
|
@ -50,4 +51,4 @@ fun List<RoomSummary>.filter(filter: RoomListFilter): List<RoomSummary> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun RoomSummary.isInvited() = currentUserMembership == CurrentUserMembership.INVITED
|
private fun RoomSummary.isInvited() = info.currentUserMembership == CurrentUserMembership.INVITED
|
||||||
|
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2023, 2024 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
* Please see LICENSE in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.element.android.libraries.matrix.impl.roomlist
|
|
||||||
|
|
||||||
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.room.isDm
|
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
|
||||||
import io.element.android.libraries.matrix.impl.notificationsettings.RoomNotificationSettingsMapper
|
|
||||||
import io.element.android.libraries.matrix.impl.room.elementHeroes
|
|
||||||
import io.element.android.libraries.matrix.impl.room.map
|
|
||||||
import io.element.android.libraries.matrix.impl.room.member.RoomMemberMapper
|
|
||||||
import io.element.android.libraries.matrix.impl.room.message.RoomMessageFactory
|
|
||||||
import org.matrix.rustcomponents.sdk.RoomListItem
|
|
||||||
import org.matrix.rustcomponents.sdk.use
|
|
||||||
|
|
||||||
class RoomSummaryDetailsFactory(
|
|
||||||
private val roomMessageFactory: RoomMessageFactory = RoomMessageFactory(),
|
|
||||||
) {
|
|
||||||
suspend fun create(roomListItem: RoomListItem): RoomSummary {
|
|
||||||
val roomInfo = roomListItem.roomInfo()
|
|
||||||
val latestRoomMessage = roomListItem.latestEvent().use { event ->
|
|
||||||
roomMessageFactory.create(event)
|
|
||||||
}
|
|
||||||
return RoomSummary(
|
|
||||||
roomId = RoomId(roomInfo.id),
|
|
||||||
name = roomInfo.displayName,
|
|
||||||
canonicalAlias = roomInfo.canonicalAlias?.let(::RoomAlias),
|
|
||||||
alternativeAliases = roomInfo.alternativeAliases.map(::RoomAlias),
|
|
||||||
isDirect = roomInfo.isDirect,
|
|
||||||
avatarUrl = roomInfo.avatarUrl,
|
|
||||||
numUnreadMentions = roomInfo.numUnreadMentions.toInt(),
|
|
||||||
numUnreadMessages = roomInfo.numUnreadMessages.toInt(),
|
|
||||||
numUnreadNotifications = roomInfo.numUnreadNotifications.toInt(),
|
|
||||||
isMarkedUnread = roomInfo.isMarkedUnread,
|
|
||||||
lastMessage = latestRoomMessage,
|
|
||||||
inviter = roomInfo.inviter?.let(RoomMemberMapper::map),
|
|
||||||
userDefinedNotificationMode = roomInfo.cachedUserDefinedNotificationMode?.let(RoomNotificationSettingsMapper::mapMode),
|
|
||||||
hasRoomCall = roomInfo.hasRoomCall,
|
|
||||||
isDm = isDm(isDirect = roomInfo.isDirect, activeMembersCount = roomInfo.activeMembersCount.toInt()),
|
|
||||||
isFavorite = roomInfo.isFavourite,
|
|
||||||
currentUserMembership = roomInfo.membership.map(),
|
|
||||||
heroes = roomInfo.elementHeroes(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023, 2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
* Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.matrix.impl.roomlist
|
||||||
|
|
||||||
|
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
||||||
|
import io.element.android.libraries.matrix.impl.room.MatrixRoomInfoMapper
|
||||||
|
import io.element.android.libraries.matrix.impl.room.message.RoomMessageFactory
|
||||||
|
import org.matrix.rustcomponents.sdk.RoomListItem
|
||||||
|
import org.matrix.rustcomponents.sdk.use
|
||||||
|
|
||||||
|
class RoomSummaryFactory(
|
||||||
|
private val roomMessageFactory: RoomMessageFactory = RoomMessageFactory(),
|
||||||
|
private val roomInfoMapper: MatrixRoomInfoMapper = MatrixRoomInfoMapper(),
|
||||||
|
) {
|
||||||
|
suspend fun create(roomListItem: RoomListItem): RoomSummary {
|
||||||
|
val roomInfo = roomListItem.roomInfo().let(roomInfoMapper::map)
|
||||||
|
val latestRoomMessage = roomListItem.latestEvent().use { event ->
|
||||||
|
roomMessageFactory.create(event)
|
||||||
|
}
|
||||||
|
return RoomSummary(
|
||||||
|
info = roomInfo,
|
||||||
|
lastMessage = latestRoomMessage,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -23,9 +23,8 @@ class RoomSummaryListProcessor(
|
||||||
private val roomSummaries: MutableSharedFlow<List<RoomSummary>>,
|
private val roomSummaries: MutableSharedFlow<List<RoomSummary>>,
|
||||||
private val roomListService: RoomListServiceInterface,
|
private val roomListService: RoomListServiceInterface,
|
||||||
private val coroutineContext: CoroutineContext,
|
private val coroutineContext: CoroutineContext,
|
||||||
private val roomSummaryDetailsFactory: RoomSummaryDetailsFactory = RoomSummaryDetailsFactory(),
|
private val roomSummaryDetailsFactory: RoomSummaryFactory = RoomSummaryFactory(),
|
||||||
) {
|
) {
|
||||||
private val roomSummariesByIdentifier = HashMap<String, RoomSummary>()
|
|
||||||
private val mutex = Mutex()
|
private val mutex = Mutex()
|
||||||
|
|
||||||
suspend fun postUpdate(updates: List<RoomListEntriesUpdate>) {
|
suspend fun postUpdate(updates: List<RoomListEntriesUpdate>) {
|
||||||
|
|
@ -40,7 +39,7 @@ class RoomSummaryListProcessor(
|
||||||
suspend fun rebuildRoomSummaries() {
|
suspend fun rebuildRoomSummaries() {
|
||||||
updateRoomSummaries {
|
updateRoomSummaries {
|
||||||
forEachIndexed { i, summary ->
|
forEachIndexed { i, summary ->
|
||||||
val result = buildAndCacheRoomSummaryForIdentifier(summary.roomId.value)
|
val result = buildRoomSummaryForIdentifier(summary.roomId.value)
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
this[i] = result
|
this[i] = result
|
||||||
}
|
}
|
||||||
|
|
@ -97,23 +96,17 @@ class RoomSummaryListProcessor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun buildSummaryForRoomListEntry(entry: RoomListItem): RoomSummary {
|
private suspend fun buildSummaryForRoomListEntry(entry: RoomListItem): RoomSummary {
|
||||||
return buildAndCacheRoomSummaryForRoomListItem(entry)
|
return buildRoomSummaryForRoomListItem(entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun buildAndCacheRoomSummaryForIdentifier(identifier: String): RoomSummary? {
|
private suspend fun buildRoomSummaryForIdentifier(identifier: String): RoomSummary? {
|
||||||
val builtRoomSummary = roomListService.roomOrNull(identifier)?.use { roomListItem ->
|
return roomListService.roomOrNull(identifier)?.use { roomListItem ->
|
||||||
buildAndCacheRoomSummaryForRoomListItem(roomListItem)
|
buildRoomSummaryForRoomListItem(roomListItem)
|
||||||
}
|
}
|
||||||
if (builtRoomSummary == null) {
|
|
||||||
roomSummariesByIdentifier.remove(identifier)
|
|
||||||
}
|
|
||||||
return builtRoomSummary
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun buildAndCacheRoomSummaryForRoomListItem(roomListItem: RoomListItem): RoomSummary {
|
private suspend fun buildRoomSummaryForRoomListItem(roomListItem: RoomListItem): RoomSummary {
|
||||||
val builtRoomSummary = roomSummaryDetailsFactory.create(roomListItem = roomListItem)
|
return roomSummaryDetailsFactory.create(roomListItem = roomListItem)
|
||||||
roomSummariesByIdentifier[builtRoomSummary.roomId.value] = builtRoomSummary
|
|
||||||
return builtRoomSummary
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun updateRoomSummaries(block: suspend MutableList<RoomSummary>.() -> Unit) = withContext(coroutineContext) {
|
private suspend fun updateRoomSummaries(block: suspend MutableList<RoomSummary>.() -> Unit) = withContext(coroutineContext) {
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,10 @@ class MatrixRoomInfoMapperTest {
|
||||||
).toImmutableList(),
|
).toImmutableList(),
|
||||||
pinnedEventIds = listOf(AN_EVENT_ID).toPersistentList(),
|
pinnedEventIds = listOf(AN_EVENT_ID).toPersistentList(),
|
||||||
creator = A_USER_ID,
|
creator = A_USER_ID,
|
||||||
|
isMarkedUnread = false,
|
||||||
|
numUnreadMessages = 12L,
|
||||||
|
numUnreadNotifications = 13L,
|
||||||
|
numUnreadMentions = 14L,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -174,6 +178,10 @@ class MatrixRoomInfoMapperTest {
|
||||||
heroes = emptyList<MatrixUser>().toImmutableList(),
|
heroes = emptyList<MatrixUser>().toImmutableList(),
|
||||||
pinnedEventIds = emptyList<EventId>().toPersistentList(),
|
pinnedEventIds = emptyList<EventId>().toPersistentList(),
|
||||||
creator = null,
|
creator = null,
|
||||||
|
isMarkedUnread = true,
|
||||||
|
numUnreadMessages = 12L,
|
||||||
|
numUnreadNotifications = 13L,
|
||||||
|
numUnreadMentions = 14L,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||||
import io.element.android.libraries.matrix.test.A_SERVER_LIST
|
import io.element.android.libraries.matrix.test.A_SERVER_LIST
|
||||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||||
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
||||||
import io.element.android.libraries.matrix.test.room.aRoomSummaryFilled
|
import io.element.android.libraries.matrix.test.room.aRoomSummary
|
||||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||||
import io.element.android.tests.testutils.lambda.value
|
import io.element.android.tests.testutils.lambda.value
|
||||||
|
|
@ -29,7 +29,7 @@ import org.junit.Test
|
||||||
class DefaultJoinRoomTest {
|
class DefaultJoinRoomTest {
|
||||||
@Test
|
@Test
|
||||||
fun `when using roomId and there is no server names, the classic join room API is used`() = runTest {
|
fun `when using roomId and there is no server names, the classic join room API is used`() = runTest {
|
||||||
val roomSummary = aRoomSummaryFilled()
|
val roomSummary = aRoomSummary()
|
||||||
val joinRoomLambda = lambdaRecorder { _: RoomId -> Result.success(roomSummary) }
|
val joinRoomLambda = lambdaRecorder { _: RoomId -> Result.success(roomSummary) }
|
||||||
val joinRoomByIdOrAliasLambda = lambdaRecorder { _: RoomIdOrAlias, _: List<String> -> Result.success(roomSummary) }
|
val joinRoomByIdOrAliasLambda = lambdaRecorder { _: RoomIdOrAlias, _: List<String> -> Result.success(roomSummary) }
|
||||||
val roomResult = FakeMatrixRoom()
|
val roomResult = FakeMatrixRoom()
|
||||||
|
|
@ -64,7 +64,7 @@ class DefaultJoinRoomTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when using roomId and server names are available, joinRoomByIdOrAlias API is used`() = runTest {
|
fun `when using roomId and server names are available, joinRoomByIdOrAlias API is used`() = runTest {
|
||||||
val roomSummary = aRoomSummaryFilled()
|
val roomSummary = aRoomSummary()
|
||||||
val joinRoomLambda = lambdaRecorder { _: RoomId -> Result.success(roomSummary) }
|
val joinRoomLambda = lambdaRecorder { _: RoomId -> Result.success(roomSummary) }
|
||||||
val joinRoomByIdOrAliasLambda = lambdaRecorder { _: RoomIdOrAlias, _: List<String> -> Result.success(roomSummary) }
|
val joinRoomByIdOrAliasLambda = lambdaRecorder { _: RoomIdOrAlias, _: List<String> -> Result.success(roomSummary) }
|
||||||
val roomResult = FakeMatrixRoom()
|
val roomResult = FakeMatrixRoom()
|
||||||
|
|
@ -100,7 +100,7 @@ class DefaultJoinRoomTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when using roomAlias, joinRoomByIdOrAlias API is used`() = runTest {
|
fun `when using roomAlias, joinRoomByIdOrAlias API is used`() = runTest {
|
||||||
val roomSummary = aRoomSummaryFilled()
|
val roomSummary = aRoomSummary()
|
||||||
val joinRoomLambda = lambdaRecorder { _: RoomId -> Result.success(roomSummary) }
|
val joinRoomLambda = lambdaRecorder { _: RoomId -> Result.success(roomSummary) }
|
||||||
val joinRoomByIdOrAliasLambda = lambdaRecorder { _: RoomIdOrAlias, _: List<String> -> Result.success(roomSummary) }
|
val joinRoomByIdOrAliasLambda = lambdaRecorder { _: RoomIdOrAlias, _: List<String> -> Result.success(roomSummary) }
|
||||||
val roomResult = FakeMatrixRoom()
|
val roomResult = FakeMatrixRoom()
|
||||||
|
|
|
||||||
|
|
@ -16,10 +16,11 @@ import org.junit.Test
|
||||||
|
|
||||||
class RoomListFilterTest {
|
class RoomListFilterTest {
|
||||||
private val regularRoom = aRoomSummary(
|
private val regularRoom = aRoomSummary(
|
||||||
isDm = false
|
isDirect = false,
|
||||||
)
|
)
|
||||||
private val dmRoom = aRoomSummary(
|
private val dmRoom = aRoomSummary(
|
||||||
isDm = true
|
isDirect = true,
|
||||||
|
activeMembersCount = 2
|
||||||
)
|
)
|
||||||
private val favoriteRoom = aRoomSummary(
|
private val favoriteRoom = aRoomSummary(
|
||||||
isFavorite = true
|
isFavorite = true
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@ import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||||
import io.element.android.libraries.matrix.test.A_ROOM_ID_2
|
import io.element.android.libraries.matrix.test.A_ROOM_ID_2
|
||||||
import io.element.android.libraries.matrix.test.A_ROOM_ID_3
|
import io.element.android.libraries.matrix.test.A_ROOM_ID_3
|
||||||
import io.element.android.libraries.matrix.test.room.aRoomSummary
|
import io.element.android.libraries.matrix.test.room.aRoomSummary
|
||||||
import io.element.android.libraries.matrix.test.room.aRoomSummaryFilled
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.test.StandardTestDispatcher
|
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||||
import kotlinx.coroutines.test.TestScope
|
import kotlinx.coroutines.test.TestScope
|
||||||
|
|
@ -40,7 +39,7 @@ class RoomSummaryListProcessorTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `PushBack adds a new entry at the end of the list`() = runTest {
|
fun `PushBack adds a new entry at the end of the list`() = runTest {
|
||||||
summaries.value = listOf(aRoomSummaryFilled())
|
summaries.value = listOf(aRoomSummary())
|
||||||
val processor = createProcessor()
|
val processor = createProcessor()
|
||||||
processor.postUpdate(listOf(RoomListEntriesUpdate.PushBack(FakeRustRoomListItem(A_ROOM_ID_2))))
|
processor.postUpdate(listOf(RoomListEntriesUpdate.PushBack(FakeRustRoomListItem(A_ROOM_ID_2))))
|
||||||
|
|
||||||
|
|
@ -50,7 +49,7 @@ class RoomSummaryListProcessorTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `PushFront inserts a new entry at the start of the list`() = runTest {
|
fun `PushFront inserts a new entry at the start of the list`() = runTest {
|
||||||
summaries.value = listOf(aRoomSummaryFilled())
|
summaries.value = listOf(aRoomSummary())
|
||||||
val processor = createProcessor()
|
val processor = createProcessor()
|
||||||
processor.postUpdate(listOf(RoomListEntriesUpdate.PushFront(FakeRustRoomListItem(A_ROOM_ID_2))))
|
processor.postUpdate(listOf(RoomListEntriesUpdate.PushFront(FakeRustRoomListItem(A_ROOM_ID_2))))
|
||||||
|
|
||||||
|
|
@ -60,7 +59,7 @@ class RoomSummaryListProcessorTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `Set replaces an entry at some index`() = runTest {
|
fun `Set replaces an entry at some index`() = runTest {
|
||||||
summaries.value = listOf(aRoomSummaryFilled())
|
summaries.value = listOf(aRoomSummary())
|
||||||
val processor = createProcessor()
|
val processor = createProcessor()
|
||||||
val index = 0
|
val index = 0
|
||||||
|
|
||||||
|
|
@ -72,7 +71,7 @@ class RoomSummaryListProcessorTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `Insert inserts a new entry at the provided index`() = runTest {
|
fun `Insert inserts a new entry at the provided index`() = runTest {
|
||||||
summaries.value = listOf(aRoomSummaryFilled())
|
summaries.value = listOf(aRoomSummary())
|
||||||
val processor = createProcessor()
|
val processor = createProcessor()
|
||||||
val index = 0
|
val index = 0
|
||||||
|
|
||||||
|
|
@ -84,7 +83,10 @@ class RoomSummaryListProcessorTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `Remove removes an entry at some index`() = runTest {
|
fun `Remove removes an entry at some index`() = runTest {
|
||||||
summaries.value = listOf(aRoomSummaryFilled(roomId = A_ROOM_ID), aRoomSummaryFilled(A_ROOM_ID_2))
|
summaries.value = listOf(
|
||||||
|
aRoomSummary(roomId = A_ROOM_ID),
|
||||||
|
aRoomSummary(A_ROOM_ID_2)
|
||||||
|
)
|
||||||
val processor = createProcessor()
|
val processor = createProcessor()
|
||||||
val index = 0
|
val index = 0
|
||||||
|
|
||||||
|
|
@ -96,7 +98,10 @@ class RoomSummaryListProcessorTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `PopBack removes an entry at the end of the list`() = runTest {
|
fun `PopBack removes an entry at the end of the list`() = runTest {
|
||||||
summaries.value = listOf(aRoomSummaryFilled(roomId = A_ROOM_ID), aRoomSummaryFilled(A_ROOM_ID_2))
|
summaries.value = listOf(
|
||||||
|
aRoomSummary(roomId = A_ROOM_ID),
|
||||||
|
aRoomSummary(A_ROOM_ID_2)
|
||||||
|
)
|
||||||
val processor = createProcessor()
|
val processor = createProcessor()
|
||||||
val index = 0
|
val index = 0
|
||||||
|
|
||||||
|
|
@ -108,7 +113,10 @@ class RoomSummaryListProcessorTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `PopFront removes an entry at the start of the list`() = runTest {
|
fun `PopFront removes an entry at the start of the list`() = runTest {
|
||||||
summaries.value = listOf(aRoomSummaryFilled(roomId = A_ROOM_ID), aRoomSummaryFilled(A_ROOM_ID_2))
|
summaries.value = listOf(
|
||||||
|
aRoomSummary(roomId = A_ROOM_ID),
|
||||||
|
aRoomSummary(A_ROOM_ID_2)
|
||||||
|
)
|
||||||
val processor = createProcessor()
|
val processor = createProcessor()
|
||||||
val index = 0
|
val index = 0
|
||||||
|
|
||||||
|
|
@ -120,7 +128,10 @@ class RoomSummaryListProcessorTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `Clear removes all the entries`() = runTest {
|
fun `Clear removes all the entries`() = runTest {
|
||||||
summaries.value = listOf(aRoomSummaryFilled(roomId = A_ROOM_ID), aRoomSummaryFilled(A_ROOM_ID_2))
|
summaries.value = listOf(
|
||||||
|
aRoomSummary(roomId = A_ROOM_ID),
|
||||||
|
aRoomSummary(A_ROOM_ID_2)
|
||||||
|
)
|
||||||
val processor = createProcessor()
|
val processor = createProcessor()
|
||||||
|
|
||||||
processor.postUpdate(listOf(RoomListEntriesUpdate.Clear))
|
processor.postUpdate(listOf(RoomListEntriesUpdate.Clear))
|
||||||
|
|
@ -130,7 +141,10 @@ class RoomSummaryListProcessorTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `Truncate removes all entries after the provided length`() = runTest {
|
fun `Truncate removes all entries after the provided length`() = runTest {
|
||||||
summaries.value = listOf(aRoomSummaryFilled(roomId = A_ROOM_ID), aRoomSummaryFilled(A_ROOM_ID_2))
|
summaries.value = listOf(
|
||||||
|
aRoomSummary(roomId = A_ROOM_ID),
|
||||||
|
aRoomSummary(A_ROOM_ID_2)
|
||||||
|
)
|
||||||
val processor = createProcessor()
|
val processor = createProcessor()
|
||||||
val index = 0
|
val index = 0
|
||||||
|
|
||||||
|
|
@ -142,7 +156,10 @@ class RoomSummaryListProcessorTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `Reset removes all entries and add the provided ones`() = runTest {
|
fun `Reset removes all entries and add the provided ones`() = runTest {
|
||||||
summaries.value = listOf(aRoomSummaryFilled(roomId = A_ROOM_ID), aRoomSummaryFilled(A_ROOM_ID_2))
|
summaries.value = listOf(
|
||||||
|
aRoomSummary(roomId = A_ROOM_ID),
|
||||||
|
aRoomSummary(A_ROOM_ID_2)
|
||||||
|
)
|
||||||
val processor = createProcessor()
|
val processor = createProcessor()
|
||||||
val index = 0
|
val index = 0
|
||||||
|
|
||||||
|
|
@ -156,6 +173,6 @@ class RoomSummaryListProcessorTest {
|
||||||
summaries,
|
summaries,
|
||||||
FakeRustRoomListService(),
|
FakeRustRoomListService(),
|
||||||
coroutineContext = StandardTestDispatcher(testScheduler),
|
coroutineContext = StandardTestDispatcher(testScheduler),
|
||||||
roomSummaryDetailsFactory = RoomSummaryDetailsFactory(),
|
roomSummaryDetailsFactory = RoomSummaryFactory(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ import io.element.android.libraries.matrix.api.oidc.AccountManagementAction
|
||||||
import io.element.android.libraries.matrix.api.pusher.PushersService
|
import io.element.android.libraries.matrix.api.pusher.PushersService
|
||||||
import io.element.android.libraries.matrix.api.room.InvitedRoom
|
import io.element.android.libraries.matrix.api.room.InvitedRoom
|
||||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||||
import io.element.android.libraries.matrix.api.room.MatrixRoomInfo
|
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
|
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
|
||||||
import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias
|
import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias
|
||||||
import io.element.android.libraries.matrix.api.room.preview.RoomPreview
|
import io.element.android.libraries.matrix.api.room.preview.RoomPreview
|
||||||
|
|
@ -118,8 +117,8 @@ class FakeMatrixClient(
|
||||||
var knockRoomLambda: (RoomId) -> Result<Unit> = {
|
var knockRoomLambda: (RoomId) -> Result<Unit> = {
|
||||||
Result.success(Unit)
|
Result.success(Unit)
|
||||||
}
|
}
|
||||||
var getRoomInfoFlowLambda = { _: RoomId ->
|
var getRoomSummaryFlowLambda = { _: RoomIdOrAlias ->
|
||||||
flowOf<Optional<MatrixRoomInfo>>(Optional.empty())
|
flowOf<Optional<RoomSummary>>(Optional.empty())
|
||||||
}
|
}
|
||||||
var logoutLambda: (Boolean, Boolean) -> String? = { _, _ ->
|
var logoutLambda: (Boolean, Boolean) -> String? = { _, _ ->
|
||||||
null
|
null
|
||||||
|
|
@ -316,7 +315,7 @@ class FakeMatrixClient(
|
||||||
return Result.success(visitedRoomsId)
|
return Result.success(visitedRoomsId)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getRoomInfoFlow(roomId: RoomId) = getRoomInfoFlowLambda(roomId)
|
override fun getRoomSummaryFlow(roomIdOrAlias: RoomIdOrAlias) = getRoomSummaryFlowLambda(roomIdOrAlias)
|
||||||
|
|
||||||
var setAllSendQueuesEnabledLambda = lambdaRecorder(ensureNeverCalled = true) { _: Boolean ->
|
var setAllSendQueuesEnabledLambda = lambdaRecorder(ensureNeverCalled = true) { _: Boolean ->
|
||||||
// no-op
|
// no-op
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ import io.element.android.libraries.matrix.api.media.MediaUploadHandler
|
||||||
import io.element.android.libraries.matrix.api.media.VideoInfo
|
import io.element.android.libraries.matrix.api.media.VideoInfo
|
||||||
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
|
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
|
||||||
import io.element.android.libraries.matrix.api.poll.PollKind
|
import io.element.android.libraries.matrix.api.poll.PollKind
|
||||||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
|
||||||
import io.element.android.libraries.matrix.api.room.IntentionalMention
|
import io.element.android.libraries.matrix.api.room.IntentionalMention
|
||||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||||
import io.element.android.libraries.matrix.api.room.MatrixRoomInfo
|
import io.element.android.libraries.matrix.api.room.MatrixRoomInfo
|
||||||
|
|
@ -32,7 +31,6 @@ import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
|
||||||
import io.element.android.libraries.matrix.api.room.MatrixRoomNotificationSettingsState
|
import io.element.android.libraries.matrix.api.room.MatrixRoomNotificationSettingsState
|
||||||
import io.element.android.libraries.matrix.api.room.MessageEventType
|
import io.element.android.libraries.matrix.api.room.MessageEventType
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
|
||||||
import io.element.android.libraries.matrix.api.room.StateEventType
|
import io.element.android.libraries.matrix.api.room.StateEventType
|
||||||
import io.element.android.libraries.matrix.api.room.draft.ComposerDraft
|
import io.element.android.libraries.matrix.api.room.draft.ComposerDraft
|
||||||
import io.element.android.libraries.matrix.api.room.location.AssetType
|
import io.element.android.libraries.matrix.api.room.location.AssetType
|
||||||
|
|
@ -40,21 +38,15 @@ import io.element.android.libraries.matrix.api.room.powerlevels.MatrixRoomPowerL
|
||||||
import io.element.android.libraries.matrix.api.room.powerlevels.UserRoleChange
|
import io.element.android.libraries.matrix.api.room.powerlevels.UserRoleChange
|
||||||
import io.element.android.libraries.matrix.api.timeline.ReceiptType
|
import io.element.android.libraries.matrix.api.timeline.ReceiptType
|
||||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
|
||||||
import io.element.android.libraries.matrix.api.widget.MatrixWidgetDriver
|
import io.element.android.libraries.matrix.api.widget.MatrixWidgetDriver
|
||||||
import io.element.android.libraries.matrix.api.widget.MatrixWidgetSettings
|
import io.element.android.libraries.matrix.api.widget.MatrixWidgetSettings
|
||||||
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
|
|
||||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||||
import io.element.android.libraries.matrix.test.A_ROOM_NAME
|
|
||||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
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.media.FakeMediaUploadHandler
|
||||||
import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService
|
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.timeline.FakeTimeline
|
||||||
import io.element.android.tests.testutils.lambda.lambdaError
|
import io.element.android.tests.testutils.lambda.lambdaError
|
||||||
import io.element.android.tests.testutils.simulateLongTask
|
import io.element.android.tests.testutils.simulateLongTask
|
||||||
import kotlinx.collections.immutable.ImmutableMap
|
|
||||||
import kotlinx.collections.immutable.persistentMapOf
|
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
|
@ -527,62 +519,6 @@ class FakeMatrixRoom(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun aRoomInfo(
|
|
||||||
id: RoomId = A_ROOM_ID,
|
|
||||||
name: String? = A_ROOM_NAME,
|
|
||||||
rawName: String? = name,
|
|
||||||
topic: String? = "A topic",
|
|
||||||
avatarUrl: String? = AN_AVATAR_URL,
|
|
||||||
isDirect: Boolean = false,
|
|
||||||
isPublic: Boolean = true,
|
|
||||||
isSpace: Boolean = false,
|
|
||||||
isTombstoned: Boolean = false,
|
|
||||||
isFavorite: Boolean = false,
|
|
||||||
canonicalAlias: RoomAlias? = null,
|
|
||||||
alternativeAliases: List<RoomAlias> = emptyList(),
|
|
||||||
currentUserMembership: CurrentUserMembership = CurrentUserMembership.JOINED,
|
|
||||||
inviter: RoomMember? = null,
|
|
||||||
activeMembersCount: Long = 1,
|
|
||||||
invitedMembersCount: Long = 0,
|
|
||||||
joinedMembersCount: Long = 1,
|
|
||||||
highlightCount: Long = 0,
|
|
||||||
notificationCount: Long = 0,
|
|
||||||
userDefinedNotificationMode: RoomNotificationMode? = null,
|
|
||||||
hasRoomCall: Boolean = false,
|
|
||||||
userPowerLevels: ImmutableMap<UserId, Long> = persistentMapOf(),
|
|
||||||
activeRoomCallParticipants: List<UserId> = emptyList(),
|
|
||||||
heroes: List<MatrixUser> = emptyList(),
|
|
||||||
pinnedEventIds: List<EventId> = emptyList(),
|
|
||||||
roomCreator: UserId? = null,
|
|
||||||
) = MatrixRoomInfo(
|
|
||||||
id = id,
|
|
||||||
name = name,
|
|
||||||
rawName = rawName,
|
|
||||||
topic = topic,
|
|
||||||
avatarUrl = avatarUrl,
|
|
||||||
isDirect = isDirect,
|
|
||||||
isPublic = isPublic,
|
|
||||||
isSpace = isSpace,
|
|
||||||
isTombstoned = isTombstoned,
|
|
||||||
isFavorite = isFavorite,
|
|
||||||
canonicalAlias = canonicalAlias,
|
|
||||||
alternativeAliases = alternativeAliases.toImmutableList(),
|
|
||||||
currentUserMembership = currentUserMembership,
|
|
||||||
inviter = inviter,
|
|
||||||
activeMembersCount = activeMembersCount,
|
|
||||||
invitedMembersCount = invitedMembersCount,
|
|
||||||
joinedMembersCount = joinedMembersCount,
|
|
||||||
highlightCount = highlightCount,
|
|
||||||
notificationCount = notificationCount,
|
|
||||||
userDefinedNotificationMode = userDefinedNotificationMode,
|
|
||||||
hasRoomCall = hasRoomCall,
|
|
||||||
userPowerLevels = userPowerLevels,
|
|
||||||
activeRoomCallParticipants = activeRoomCallParticipants.toImmutableList(),
|
|
||||||
heroes = heroes.toImmutableList(),
|
|
||||||
pinnedEventIds = pinnedEventIds.toImmutableList(),
|
|
||||||
creator = roomCreator,
|
|
||||||
)
|
|
||||||
|
|
||||||
fun defaultRoomPowerLevels() = MatrixRoomPowerLevels(
|
fun defaultRoomPowerLevels() = MatrixRoomPowerLevels(
|
||||||
ban = 50,
|
ban = 50,
|
||||||
invite = 0,
|
invite = 0,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
* Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.matrix.test.room
|
||||||
|
|
||||||
|
import io.element.android.libraries.matrix.api.core.EventId
|
||||||
|
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||||
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
|
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||||
|
import io.element.android.libraries.matrix.api.room.MatrixRoomInfo
|
||||||
|
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||||
|
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||||
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
|
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
|
||||||
|
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||||
|
import io.element.android.libraries.matrix.test.A_ROOM_NAME
|
||||||
|
import io.element.android.libraries.matrix.test.A_ROOM_RAW_NAME
|
||||||
|
import io.element.android.libraries.matrix.test.A_ROOM_TOPIC
|
||||||
|
import kotlinx.collections.immutable.ImmutableMap
|
||||||
|
import kotlinx.collections.immutable.persistentMapOf
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
||||||
|
fun aRoomInfo(
|
||||||
|
id: RoomId = A_ROOM_ID,
|
||||||
|
name: String? = A_ROOM_NAME,
|
||||||
|
rawName: String? = A_ROOM_RAW_NAME,
|
||||||
|
topic: String? = A_ROOM_TOPIC,
|
||||||
|
avatarUrl: String? = AN_AVATAR_URL,
|
||||||
|
isDirect: Boolean = false,
|
||||||
|
isPublic: Boolean = true,
|
||||||
|
isSpace: Boolean = false,
|
||||||
|
isTombstoned: Boolean = false,
|
||||||
|
isFavorite: Boolean = false,
|
||||||
|
canonicalAlias: RoomAlias? = null,
|
||||||
|
alternativeAliases: List<RoomAlias> = emptyList(),
|
||||||
|
currentUserMembership: CurrentUserMembership = CurrentUserMembership.JOINED,
|
||||||
|
inviter: RoomMember? = null,
|
||||||
|
activeMembersCount: Long = 1,
|
||||||
|
invitedMembersCount: Long = 0,
|
||||||
|
joinedMembersCount: Long = 1,
|
||||||
|
highlightCount: Long = 0,
|
||||||
|
notificationCount: Long = 0,
|
||||||
|
userDefinedNotificationMode: RoomNotificationMode? = null,
|
||||||
|
hasRoomCall: Boolean = false,
|
||||||
|
userPowerLevels: ImmutableMap<UserId, Long> = persistentMapOf(),
|
||||||
|
activeRoomCallParticipants: List<UserId> = emptyList(),
|
||||||
|
heroes: List<MatrixUser> = emptyList(),
|
||||||
|
pinnedEventIds: List<EventId> = emptyList(),
|
||||||
|
roomCreator: UserId? = null,
|
||||||
|
isMarkedUnread: Boolean = false,
|
||||||
|
numUnreadMessages: Long = 0,
|
||||||
|
numUnreadNotifications: Long = 0,
|
||||||
|
numUnreadMentions: Long = 0,
|
||||||
|
) = MatrixRoomInfo(
|
||||||
|
id = id,
|
||||||
|
name = name,
|
||||||
|
rawName = rawName,
|
||||||
|
topic = topic,
|
||||||
|
avatarUrl = avatarUrl,
|
||||||
|
isDirect = isDirect,
|
||||||
|
isPublic = isPublic,
|
||||||
|
isSpace = isSpace,
|
||||||
|
isTombstoned = isTombstoned,
|
||||||
|
isFavorite = isFavorite,
|
||||||
|
canonicalAlias = canonicalAlias,
|
||||||
|
alternativeAliases = alternativeAliases.toImmutableList(),
|
||||||
|
currentUserMembership = currentUserMembership,
|
||||||
|
inviter = inviter,
|
||||||
|
activeMembersCount = activeMembersCount,
|
||||||
|
invitedMembersCount = invitedMembersCount,
|
||||||
|
joinedMembersCount = joinedMembersCount,
|
||||||
|
highlightCount = highlightCount,
|
||||||
|
notificationCount = notificationCount,
|
||||||
|
userDefinedNotificationMode = userDefinedNotificationMode,
|
||||||
|
hasRoomCall = hasRoomCall,
|
||||||
|
userPowerLevels = userPowerLevels,
|
||||||
|
activeRoomCallParticipants = activeRoomCallParticipants.toImmutableList(),
|
||||||
|
heroes = heroes.toImmutableList(),
|
||||||
|
pinnedEventIds = pinnedEventIds.toImmutableList(),
|
||||||
|
creator = roomCreator,
|
||||||
|
isMarkedUnread = isMarkedUnread,
|
||||||
|
numUnreadMessages = numUnreadMessages,
|
||||||
|
numUnreadNotifications = numUnreadNotifications,
|
||||||
|
numUnreadMentions = numUnreadMentions,
|
||||||
|
)
|
||||||
|
|
@ -12,6 +12,7 @@ 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.RoomId
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||||
|
import io.element.android.libraries.matrix.api.room.MatrixRoomInfo
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||||
import io.element.android.libraries.matrix.api.room.message.RoomMessage
|
import io.element.android.libraries.matrix.api.room.message.RoomMessage
|
||||||
|
|
@ -21,69 +22,88 @@ import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
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_ROOM_ID
|
||||||
import io.element.android.libraries.matrix.test.A_ROOM_NAME
|
import io.element.android.libraries.matrix.test.A_ROOM_NAME
|
||||||
|
import io.element.android.libraries.matrix.test.A_ROOM_RAW_NAME
|
||||||
|
import io.element.android.libraries.matrix.test.A_ROOM_TOPIC
|
||||||
import io.element.android.libraries.matrix.test.A_USER_ID
|
import io.element.android.libraries.matrix.test.A_USER_ID
|
||||||
import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem
|
import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem
|
||||||
|
import kotlinx.collections.immutable.ImmutableMap
|
||||||
|
import kotlinx.collections.immutable.persistentMapOf
|
||||||
|
import kotlinx.collections.immutable.toPersistentList
|
||||||
|
|
||||||
fun aRoomSummaryFilled(
|
fun aRoomSummary(
|
||||||
roomId: RoomId = A_ROOM_ID,
|
info: MatrixRoomInfo = aRoomInfo(),
|
||||||
name: String = A_ROOM_NAME,
|
|
||||||
isDirect: Boolean = false,
|
|
||||||
avatarUrl: String? = null,
|
|
||||||
lastMessage: RoomMessage? = aRoomMessage(),
|
lastMessage: RoomMessage? = aRoomMessage(),
|
||||||
numUnreadMentions: Int = 0,
|
) = RoomSummary(
|
||||||
numUnreadMessages: Int = 0,
|
info = info,
|
||||||
notificationMode: RoomNotificationMode? = null,
|
|
||||||
currentUserMembership: CurrentUserMembership = CurrentUserMembership.JOINED,
|
|
||||||
) = aRoomSummary(
|
|
||||||
roomId = roomId,
|
|
||||||
name = name,
|
|
||||||
isDirect = isDirect,
|
|
||||||
avatarUrl = avatarUrl,
|
|
||||||
lastMessage = lastMessage,
|
lastMessage = lastMessage,
|
||||||
numUnreadMentions = numUnreadMentions,
|
|
||||||
numUnreadMessages = numUnreadMessages,
|
|
||||||
notificationMode = notificationMode,
|
|
||||||
currentUserMembership = currentUserMembership,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
fun aRoomSummary(
|
fun aRoomSummary(
|
||||||
roomId: RoomId = A_ROOM_ID,
|
roomId: RoomId = A_ROOM_ID,
|
||||||
name: String? = A_ROOM_NAME,
|
name: String? = A_ROOM_NAME,
|
||||||
isDirect: Boolean = false,
|
rawName: String? = A_ROOM_RAW_NAME,
|
||||||
|
topic: String? = A_ROOM_TOPIC,
|
||||||
avatarUrl: String? = null,
|
avatarUrl: String? = null,
|
||||||
lastMessage: RoomMessage? = aRoomMessage(),
|
isDirect: Boolean = false,
|
||||||
numUnreadMentions: Int = 0,
|
isPublic: Boolean = true,
|
||||||
numUnreadMessages: Int = 0,
|
isSpace: Boolean = false,
|
||||||
numUnreadNotifications: Int = 0,
|
isTombstoned: Boolean = false,
|
||||||
isMarkedUnread: Boolean = false,
|
isFavorite: Boolean = false,
|
||||||
notificationMode: RoomNotificationMode? = null,
|
|
||||||
inviter: RoomMember? = null,
|
|
||||||
canonicalAlias: RoomAlias? = null,
|
canonicalAlias: RoomAlias? = null,
|
||||||
alternativeAliases: List<RoomAlias> = emptyList(),
|
alternativeAliases: List<RoomAlias> = emptyList(),
|
||||||
hasRoomCall: Boolean = false,
|
|
||||||
isDm: Boolean = false,
|
|
||||||
isFavorite: Boolean = false,
|
|
||||||
currentUserMembership: CurrentUserMembership = CurrentUserMembership.JOINED,
|
currentUserMembership: CurrentUserMembership = CurrentUserMembership.JOINED,
|
||||||
|
inviter: RoomMember? = null,
|
||||||
|
activeMembersCount: Long = 1,
|
||||||
|
invitedMembersCount: Long = 0,
|
||||||
|
joinedMembersCount: Long = 1,
|
||||||
|
highlightCount: Long = 0,
|
||||||
|
notificationCount: Long = 0,
|
||||||
|
userDefinedNotificationMode: RoomNotificationMode? = null,
|
||||||
|
hasRoomCall: Boolean = false,
|
||||||
|
userPowerLevels: ImmutableMap<UserId, Long> = persistentMapOf(),
|
||||||
|
activeRoomCallParticipants: List<UserId> = emptyList(),
|
||||||
heroes: List<MatrixUser> = emptyList(),
|
heroes: List<MatrixUser> = emptyList(),
|
||||||
|
pinnedEventIds: List<EventId> = emptyList(),
|
||||||
|
roomCreator: UserId? = null,
|
||||||
|
isMarkedUnread: Boolean = false,
|
||||||
|
numUnreadMessages: Long = 0,
|
||||||
|
numUnreadNotifications: Long = 0,
|
||||||
|
numUnreadMentions: Long = 0,
|
||||||
|
lastMessage: RoomMessage? = aRoomMessage(),
|
||||||
) = RoomSummary(
|
) = RoomSummary(
|
||||||
roomId = roomId,
|
info = MatrixRoomInfo(
|
||||||
name = name,
|
id = roomId,
|
||||||
isDirect = isDirect,
|
name = name,
|
||||||
avatarUrl = avatarUrl,
|
rawName = rawName,
|
||||||
|
topic = topic,
|
||||||
|
avatarUrl = avatarUrl,
|
||||||
|
isDirect = isDirect,
|
||||||
|
isPublic = isPublic,
|
||||||
|
isSpace = isSpace,
|
||||||
|
isTombstoned = isTombstoned,
|
||||||
|
isFavorite = isFavorite,
|
||||||
|
canonicalAlias = canonicalAlias,
|
||||||
|
alternativeAliases = alternativeAliases.toPersistentList(),
|
||||||
|
currentUserMembership = currentUserMembership,
|
||||||
|
inviter = inviter,
|
||||||
|
activeMembersCount = activeMembersCount,
|
||||||
|
invitedMembersCount = invitedMembersCount,
|
||||||
|
joinedMembersCount = joinedMembersCount,
|
||||||
|
userPowerLevels = userPowerLevels,
|
||||||
|
highlightCount = highlightCount,
|
||||||
|
notificationCount = notificationCount,
|
||||||
|
userDefinedNotificationMode = userDefinedNotificationMode,
|
||||||
|
hasRoomCall = hasRoomCall,
|
||||||
|
activeRoomCallParticipants = activeRoomCallParticipants.toPersistentList(),
|
||||||
|
heroes = heroes.toPersistentList(),
|
||||||
|
pinnedEventIds = pinnedEventIds.toPersistentList(),
|
||||||
|
creator = roomCreator,
|
||||||
|
isMarkedUnread = isMarkedUnread,
|
||||||
|
numUnreadMessages = numUnreadMessages,
|
||||||
|
numUnreadNotifications = numUnreadNotifications,
|
||||||
|
numUnreadMentions = numUnreadMentions,
|
||||||
|
),
|
||||||
lastMessage = lastMessage,
|
lastMessage = lastMessage,
|
||||||
numUnreadMentions = numUnreadMentions,
|
|
||||||
numUnreadMessages = numUnreadMessages,
|
|
||||||
numUnreadNotifications = numUnreadNotifications,
|
|
||||||
isMarkedUnread = isMarkedUnread,
|
|
||||||
userDefinedNotificationMode = notificationMode,
|
|
||||||
inviter = inviter,
|
|
||||||
canonicalAlias = canonicalAlias,
|
|
||||||
alternativeAliases = alternativeAliases,
|
|
||||||
hasRoomCall = hasRoomCall,
|
|
||||||
isDm = isDm,
|
|
||||||
isFavorite = isFavorite,
|
|
||||||
currentUserMembership = currentUserMembership,
|
|
||||||
heroes = heroes,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
fun aRoomMessage(
|
fun aRoomMessage(
|
||||||
|
|
|
||||||
|
|
@ -1,66 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2024 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
* Please see LICENSE in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.element.android.libraries.matrix.ui.components
|
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
|
||||||
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.room.CurrentUserMembership
|
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
|
||||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
|
||||||
import io.element.android.libraries.matrix.api.room.message.RoomMessage
|
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
|
||||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
|
||||||
|
|
||||||
open class RoomSummaryDetailsProvider : PreviewParameterProvider<RoomSummary> {
|
|
||||||
override val values: Sequence<RoomSummary>
|
|
||||||
get() = sequenceOf(
|
|
||||||
aRoomSummaryDetails(),
|
|
||||||
aRoomSummaryDetails(name = null),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun aRoomSummaryDetails(
|
|
||||||
roomId: RoomId = RoomId("!room:domain"),
|
|
||||||
name: String? = "roomName",
|
|
||||||
canonicalAlias: RoomAlias? = null,
|
|
||||||
alternativeAliases: List<RoomAlias> = emptyList(),
|
|
||||||
isDirect: Boolean = true,
|
|
||||||
avatarUrl: String? = null,
|
|
||||||
lastMessage: RoomMessage? = null,
|
|
||||||
inviter: RoomMember? = null,
|
|
||||||
notificationMode: RoomNotificationMode? = null,
|
|
||||||
hasRoomCall: Boolean = false,
|
|
||||||
isDm: Boolean = false,
|
|
||||||
numUnreadMentions: Int = 0,
|
|
||||||
numUnreadMessages: Int = 0,
|
|
||||||
numUnreadNotifications: Int = 0,
|
|
||||||
isMarkedUnread: Boolean = false,
|
|
||||||
isFavorite: Boolean = false,
|
|
||||||
currentUserMembership: CurrentUserMembership = CurrentUserMembership.JOINED,
|
|
||||||
heroes: List<MatrixUser> = emptyList(),
|
|
||||||
) = RoomSummary(
|
|
||||||
roomId = roomId,
|
|
||||||
name = name,
|
|
||||||
canonicalAlias = canonicalAlias,
|
|
||||||
alternativeAliases = alternativeAliases,
|
|
||||||
isDirect = isDirect,
|
|
||||||
avatarUrl = avatarUrl,
|
|
||||||
lastMessage = lastMessage,
|
|
||||||
inviter = inviter,
|
|
||||||
userDefinedNotificationMode = notificationMode,
|
|
||||||
hasRoomCall = hasRoomCall,
|
|
||||||
isDm = isDm,
|
|
||||||
numUnreadMentions = numUnreadMentions,
|
|
||||||
numUnreadMessages = numUnreadMessages,
|
|
||||||
numUnreadNotifications = numUnreadNotifications,
|
|
||||||
isMarkedUnread = isMarkedUnread,
|
|
||||||
isFavorite = isFavorite,
|
|
||||||
currentUserMembership = currentUserMembership,
|
|
||||||
heroes = heroes,
|
|
||||||
)
|
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
* Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.matrix.ui.components
|
||||||
|
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
|
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.user.MatrixUser
|
||||||
|
import io.element.android.libraries.matrix.ui.model.SelectRoomInfo
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
||||||
|
class SelectRoomInfoProvider : PreviewParameterProvider<SelectRoomInfo> {
|
||||||
|
override val values: Sequence<SelectRoomInfo>
|
||||||
|
get() = sequenceOf(
|
||||||
|
aSelectRoomInfo(roomId = RoomId("!room1:domain")),
|
||||||
|
aSelectRoomInfo(roomId = RoomId("!room2:domain"), name = "Room with a name"),
|
||||||
|
aSelectRoomInfo(roomId = RoomId("!room3:domain"), name = "Room with a name and alias", canonicalAlias = RoomAlias("#alias:domain")),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun aSelectRoomInfo(
|
||||||
|
roomId: RoomId,
|
||||||
|
name: String? = null,
|
||||||
|
canonicalAlias: RoomAlias? = null,
|
||||||
|
avatarUrl: String? = null,
|
||||||
|
heroes: ImmutableList<MatrixUser> = persistentListOf(),
|
||||||
|
) = SelectRoomInfo(
|
||||||
|
roomId = roomId,
|
||||||
|
name = name,
|
||||||
|
canonicalAlias = canonicalAlias,
|
||||||
|
avatarUrl = avatarUrl,
|
||||||
|
heroes = heroes,
|
||||||
|
)
|
||||||
|
|
@ -34,15 +34,15 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||||
import io.element.android.libraries.designsystem.theme.components.Surface
|
import io.element.android.libraries.designsystem.theme.components.Surface
|
||||||
import io.element.android.libraries.designsystem.theme.components.Text
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
import io.element.android.libraries.matrix.ui.model.SelectRoomInfo
|
||||||
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SelectedRoom(
|
fun SelectedRoom(
|
||||||
roomSummary: RoomSummary,
|
roomInfo: SelectRoomInfo,
|
||||||
onRemoveRoom: (RoomSummary) -> Unit,
|
onRemoveRoom: (SelectRoomInfo) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
|
|
@ -53,14 +53,12 @@ fun SelectedRoom(
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
) {
|
) {
|
||||||
CompositeAvatar(
|
CompositeAvatar(
|
||||||
avatarData = roomSummary.getAvatarData(size = AvatarSize.SelectedRoom),
|
avatarData = roomInfo.getAvatarData(AvatarSize.SelectedRoom),
|
||||||
heroes = roomSummary.heroes.map { user ->
|
heroes = roomInfo.heroes.map { it.getAvatarData(AvatarSize.SelectedRoom) }.toImmutableList(),
|
||||||
user.getAvatarData(size = AvatarSize.SelectedRoom)
|
|
||||||
}.toImmutableList()
|
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
// If name is null, we do not have space to render "No room name", so just use `#` here.
|
// If name is null, we do not have space to render "No room name", so just use `#` here.
|
||||||
text = roomSummary.name ?: "#",
|
text = roomInfo.name ?: "#",
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
|
@ -69,14 +67,14 @@ fun SelectedRoom(
|
||||||
Surface(
|
Surface(
|
||||||
color = MaterialTheme.colorScheme.primary,
|
color = MaterialTheme.colorScheme.primary,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clip(CircleShape)
|
.clip(CircleShape)
|
||||||
.size(20.dp)
|
.size(20.dp)
|
||||||
.align(Alignment.TopEnd)
|
.align(Alignment.TopEnd)
|
||||||
.clickable(
|
.clickable(
|
||||||
indication = ripple(),
|
indication = ripple(),
|
||||||
interactionSource = remember { MutableInteractionSource() },
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
onClick = { onRemoveRoom(roomSummary) }
|
onClick = { onRemoveRoom(roomInfo) }
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = CompoundIcons.Close(),
|
imageVector = CompoundIcons.Close(),
|
||||||
|
|
@ -91,10 +89,10 @@ fun SelectedRoom(
|
||||||
@PreviewsDayNight
|
@PreviewsDayNight
|
||||||
@Composable
|
@Composable
|
||||||
internal fun SelectedRoomPreview(
|
internal fun SelectedRoomPreview(
|
||||||
@PreviewParameter(RoomSummaryDetailsProvider::class) roomSummary: RoomSummary
|
@PreviewParameter(SelectRoomInfoProvider::class) roomInfo: SelectRoomInfo
|
||||||
) = ElementPreview {
|
) = ElementPreview {
|
||||||
SelectedRoom(
|
SelectedRoom(
|
||||||
roomSummary = roomSummary,
|
roomInfo = roomInfo,
|
||||||
onRemoveRoom = {},
|
onRemoveRoom = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@
|
||||||
|
|
||||||
package io.element.android.libraries.matrix.ui.media
|
package io.element.android.libraries.matrix.ui.media
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import coil.ImageLoader
|
import coil.ImageLoader
|
||||||
import coil.fetch.Fetcher
|
import coil.fetch.Fetcher
|
||||||
import coil.request.Options
|
import coil.request.Options
|
||||||
|
|
@ -15,7 +14,6 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
|
|
||||||
internal class AvatarDataFetcherFactory(
|
internal class AvatarDataFetcherFactory(
|
||||||
private val context: Context,
|
|
||||||
private val client: MatrixClient
|
private val client: MatrixClient
|
||||||
) : Fetcher.Factory<AvatarData> {
|
) : Fetcher.Factory<AvatarData> {
|
||||||
override fun create(
|
override fun create(
|
||||||
|
|
@ -24,7 +22,6 @@ internal class AvatarDataFetcherFactory(
|
||||||
imageLoader: ImageLoader
|
imageLoader: ImageLoader
|
||||||
): Fetcher {
|
): Fetcher {
|
||||||
return CoilMediaFetcher(
|
return CoilMediaFetcher(
|
||||||
scalingFunction = { context.resources.displayMetrics.density * it },
|
|
||||||
mediaLoader = client.mediaLoader,
|
mediaLoader = client.mediaLoader,
|
||||||
mediaData = data.toMediaRequestData(),
|
mediaData = data.toMediaRequestData(),
|
||||||
options = options
|
options = options
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,25 @@ package io.element.android.libraries.matrix.ui.media
|
||||||
|
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||||
import io.element.android.libraries.matrix.api.media.MediaSource
|
import io.element.android.libraries.matrix.api.media.MediaSource
|
||||||
import kotlin.math.roundToLong
|
|
||||||
|
/**
|
||||||
|
* The size in pixel of the thumbnail to generate for the avatar.
|
||||||
|
* This is not the size of the avatar displayed in the UI but the size to get from the servers.
|
||||||
|
* Servers SHOULD produce thumbnails with the following dimensions and methods:
|
||||||
|
*
|
||||||
|
* 32x32, crop
|
||||||
|
* 96x96, crop
|
||||||
|
* 320x240, scale
|
||||||
|
* 640x480, scale
|
||||||
|
* 800x600, scale
|
||||||
|
*
|
||||||
|
* Let's always use the same size so coil caching works properly.
|
||||||
|
*/
|
||||||
|
const val AVATAR_THUMBNAIL_SIZE_IN_PIXEL = 240L
|
||||||
|
|
||||||
internal fun AvatarData.toMediaRequestData(): MediaRequestData {
|
internal fun AvatarData.toMediaRequestData(): MediaRequestData {
|
||||||
return MediaRequestData(
|
return MediaRequestData(
|
||||||
source = url?.let { MediaSource(it) },
|
source = url?.let { MediaSource(it) },
|
||||||
kind = MediaRequestData.Kind.Thumbnail(size.dp.value.roundToLong())
|
kind = MediaRequestData.Kind.Thumbnail(AVATAR_THUMBNAIL_SIZE_IN_PIXEL)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,8 @@ import okio.Buffer
|
||||||
import okio.Path.Companion.toOkioPath
|
import okio.Path.Companion.toOkioPath
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import kotlin.math.roundToLong
|
|
||||||
|
|
||||||
internal class CoilMediaFetcher(
|
internal class CoilMediaFetcher(
|
||||||
private val scalingFunction: (Float) -> Float,
|
|
||||||
private val mediaLoader: MatrixMediaLoader,
|
private val mediaLoader: MatrixMediaLoader,
|
||||||
private val mediaData: MediaRequestData,
|
private val mediaData: MediaRequestData,
|
||||||
private val options: Options
|
private val options: Options
|
||||||
|
|
@ -74,8 +72,8 @@ internal class CoilMediaFetcher(
|
||||||
private suspend fun fetchThumbnail(mediaSource: MediaSource, kind: MediaRequestData.Kind.Thumbnail, options: Options): FetchResult? {
|
private suspend fun fetchThumbnail(mediaSource: MediaSource, kind: MediaRequestData.Kind.Thumbnail, options: Options): FetchResult? {
|
||||||
return mediaLoader.loadMediaThumbnail(
|
return mediaLoader.loadMediaThumbnail(
|
||||||
source = mediaSource,
|
source = mediaSource,
|
||||||
width = scalingFunction(kind.width.toFloat()).roundToLong(),
|
width = kind.width,
|
||||||
height = scalingFunction(kind.height.toFloat()).roundToLong(),
|
height = kind.height,
|
||||||
).map { byteArray ->
|
).map { byteArray ->
|
||||||
byteArray.asSourceResult(options)
|
byteArray.asSourceResult(options)
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
|
|
|
||||||
|
|
@ -43,8 +43,8 @@ class DefaultLoggedInImageLoaderFactory @Inject constructor(
|
||||||
}
|
}
|
||||||
add(AvatarDataKeyer())
|
add(AvatarDataKeyer())
|
||||||
add(MediaRequestDataKeyer())
|
add(MediaRequestDataKeyer())
|
||||||
add(AvatarDataFetcherFactory(context, matrixClient))
|
add(AvatarDataFetcherFactory(matrixClient))
|
||||||
add(MediaRequestDataFetcherFactory(context, matrixClient))
|
add(MediaRequestDataFetcherFactory(matrixClient))
|
||||||
}
|
}
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,14 +7,12 @@
|
||||||
|
|
||||||
package io.element.android.libraries.matrix.ui.media
|
package io.element.android.libraries.matrix.ui.media
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import coil.ImageLoader
|
import coil.ImageLoader
|
||||||
import coil.fetch.Fetcher
|
import coil.fetch.Fetcher
|
||||||
import coil.request.Options
|
import coil.request.Options
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
|
|
||||||
internal class MediaRequestDataFetcherFactory(
|
internal class MediaRequestDataFetcherFactory(
|
||||||
private val context: Context,
|
|
||||||
private val client: MatrixClient
|
private val client: MatrixClient
|
||||||
) : Fetcher.Factory<MediaRequestData> {
|
) : Fetcher.Factory<MediaRequestData> {
|
||||||
override fun create(
|
override fun create(
|
||||||
|
|
@ -23,7 +21,6 @@ internal class MediaRequestDataFetcherFactory(
|
||||||
imageLoader: ImageLoader
|
imageLoader: ImageLoader
|
||||||
): Fetcher {
|
): Fetcher {
|
||||||
return CoilMediaFetcher(
|
return CoilMediaFetcher(
|
||||||
scalingFunction = { context.resources.displayMetrics.density * it },
|
|
||||||
mediaLoader = client.mediaLoader,
|
mediaLoader = client.mediaLoader,
|
||||||
mediaData = data,
|
mediaData = data,
|
||||||
options = options
|
options = options
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,10 @@ package io.element.android.libraries.matrix.ui.model
|
||||||
|
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
import io.element.android.libraries.matrix.api.room.MatrixRoomInfo
|
||||||
|
|
||||||
fun RoomSummary.getAvatarData(size: AvatarSize) = AvatarData(
|
fun MatrixRoomInfo.getAvatarData(size: AvatarSize) = AvatarData(
|
||||||
id = roomId.value,
|
id = id.value,
|
||||||
name = name,
|
name = name,
|
||||||
url = avatarUrl,
|
url = avatarUrl,
|
||||||
size = size,
|
size = size,
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
* Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.matrix.ui.model
|
||||||
|
|
||||||
|
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||||
|
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||||
|
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.roomlist.RoomSummary
|
||||||
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
||||||
|
data class SelectRoomInfo(
|
||||||
|
val roomId: RoomId,
|
||||||
|
val name: String?,
|
||||||
|
val canonicalAlias: RoomAlias?,
|
||||||
|
val avatarUrl: String?,
|
||||||
|
val heroes: ImmutableList<MatrixUser>,
|
||||||
|
) {
|
||||||
|
fun getAvatarData(size: AvatarSize) = AvatarData(
|
||||||
|
id = roomId.value,
|
||||||
|
name = name,
|
||||||
|
url = avatarUrl,
|
||||||
|
size = size,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun RoomSummary.toSelectRoomInfo() = SelectRoomInfo(
|
||||||
|
roomId = roomId,
|
||||||
|
name = info.name,
|
||||||
|
avatarUrl = info.avatarUrl,
|
||||||
|
heroes = info.heroes,
|
||||||
|
canonicalAlias = info.canonicalAlias,
|
||||||
|
)
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import extension.setupAnvil
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright 2023, 2024 New Vector Ltd.
|
* Copyright 2023, 2024 New Vector Ltd.
|
||||||
*
|
*
|
||||||
|
|
@ -7,9 +9,10 @@
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("io.element.android-compose-library")
|
id("io.element.android-compose-library")
|
||||||
alias(libs.plugins.anvil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setupAnvil()
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "io.element.android.libraries.mediapickers.api"
|
namespace = "io.element.android.libraries.mediapickers.api"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import extension.setupAnvil
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright 2023, 2024 New Vector Ltd.
|
* Copyright 2023, 2024 New Vector Ltd.
|
||||||
*
|
*
|
||||||
|
|
@ -7,9 +9,10 @@
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("io.element.android-compose-library")
|
id("io.element.android-compose-library")
|
||||||
alias(libs.plugins.anvil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setupAnvil()
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "io.element.android.libraries.mediapickers.test"
|
namespace = "io.element.android.libraries.mediapickers.test"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import com.squareup.anvil.annotations.ContributesBinding
|
||||||
import io.element.android.libraries.di.AppScope
|
import io.element.android.libraries.di.AppScope
|
||||||
import io.element.android.libraries.di.ApplicationContext
|
import io.element.android.libraries.di.ApplicationContext
|
||||||
import io.element.android.libraries.matrix.api.media.MediaSource
|
import io.element.android.libraries.matrix.api.media.MediaSource
|
||||||
|
import io.element.android.libraries.matrix.ui.media.AVATAR_THUMBNAIL_SIZE_IN_PIXEL
|
||||||
import io.element.android.libraries.matrix.ui.media.MediaRequestData
|
import io.element.android.libraries.matrix.ui.media.MediaRequestData
|
||||||
import io.element.android.libraries.push.api.notifications.NotificationBitmapLoader
|
import io.element.android.libraries.push.api.notifications.NotificationBitmapLoader
|
||||||
import io.element.android.services.toolbox.api.sdk.BuildVersionSdkIntProvider
|
import io.element.android.services.toolbox.api.sdk.BuildVersionSdkIntProvider
|
||||||
|
|
@ -45,7 +46,7 @@ class DefaultNotificationBitmapLoader @Inject constructor(
|
||||||
private suspend fun loadRoomBitmap(path: String, imageLoader: ImageLoader): Bitmap? {
|
private suspend fun loadRoomBitmap(path: String, imageLoader: ImageLoader): Bitmap? {
|
||||||
return try {
|
return try {
|
||||||
val imageRequest = ImageRequest.Builder(context)
|
val imageRequest = ImageRequest.Builder(context)
|
||||||
.data(MediaRequestData(MediaSource(path), MediaRequestData.Kind.Thumbnail(1024)))
|
.data(MediaRequestData(MediaSource(path), MediaRequestData.Kind.Thumbnail(AVATAR_THUMBNAIL_SIZE_IN_PIXEL)))
|
||||||
.transformations(CircleCropTransformation())
|
.transformations(CircleCropTransformation())
|
||||||
.build()
|
.build()
|
||||||
val result = imageLoader.execute(imageRequest)
|
val result = imageLoader.execute(imageRequest)
|
||||||
|
|
@ -73,7 +74,7 @@ class DefaultNotificationBitmapLoader @Inject constructor(
|
||||||
private suspend fun loadUserIcon(path: String, imageLoader: ImageLoader): IconCompat? {
|
private suspend fun loadUserIcon(path: String, imageLoader: ImageLoader): IconCompat? {
|
||||||
return try {
|
return try {
|
||||||
val imageRequest = ImageRequest.Builder(context)
|
val imageRequest = ImageRequest.Builder(context)
|
||||||
.data(MediaRequestData(MediaSource(path), MediaRequestData.Kind.Thumbnail(1024)))
|
.data(MediaRequestData(MediaSource(path), MediaRequestData.Kind.Thumbnail(AVATAR_THUMBNAIL_SIZE_IN_PIXEL)))
|
||||||
.transformations(CircleCropTransformation())
|
.transformations(CircleCropTransformation())
|
||||||
.build()
|
.build()
|
||||||
val result = imageLoader.execute(imageRequest)
|
val result = imageLoader.execute(imageRequest)
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import io.element.android.libraries.matrix.api.media.MediaSource
|
||||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||||
import io.element.android.libraries.matrix.test.A_TIMESTAMP
|
import io.element.android.libraries.matrix.test.A_TIMESTAMP
|
||||||
import io.element.android.libraries.matrix.ui.components.aMatrixUser
|
import io.element.android.libraries.matrix.ui.components.aMatrixUser
|
||||||
|
import io.element.android.libraries.matrix.ui.media.AVATAR_THUMBNAIL_SIZE_IN_PIXEL
|
||||||
import io.element.android.libraries.matrix.ui.media.MediaRequestData
|
import io.element.android.libraries.matrix.ui.media.MediaRequestData
|
||||||
import io.element.android.libraries.push.impl.notifications.factories.createNotificationCreator
|
import io.element.android.libraries.push.impl.notifications.factories.createNotificationCreator
|
||||||
import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent
|
import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent
|
||||||
|
|
@ -84,7 +85,7 @@ class DefaultRoomGroupMessageCreatorTest {
|
||||||
expectedCoilRequests = listOf(
|
expectedCoilRequests = listOf(
|
||||||
MediaRequestData(
|
MediaRequestData(
|
||||||
source = MediaSource(url = A_ROOM_AVATAR),
|
source = MediaSource(url = A_ROOM_AVATAR),
|
||||||
kind = MediaRequestData.Kind.Thumbnail(1024)
|
kind = MediaRequestData.Kind.Thumbnail(AVATAR_THUMBNAIL_SIZE_IN_PIXEL)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -98,15 +99,15 @@ class DefaultRoomGroupMessageCreatorTest {
|
||||||
expectedCoilRequests = listOf(
|
expectedCoilRequests = listOf(
|
||||||
MediaRequestData(
|
MediaRequestData(
|
||||||
source = MediaSource(url = A_USER_AVATAR_1),
|
source = MediaSource(url = A_USER_AVATAR_1),
|
||||||
kind = MediaRequestData.Kind.Thumbnail(1024)
|
kind = MediaRequestData.Kind.Thumbnail(AVATAR_THUMBNAIL_SIZE_IN_PIXEL)
|
||||||
),
|
),
|
||||||
MediaRequestData(
|
MediaRequestData(
|
||||||
source = MediaSource(url = A_USER_AVATAR_2),
|
source = MediaSource(url = A_USER_AVATAR_2),
|
||||||
kind = MediaRequestData.Kind.Thumbnail(1024)
|
kind = MediaRequestData.Kind.Thumbnail(AVATAR_THUMBNAIL_SIZE_IN_PIXEL)
|
||||||
),
|
),
|
||||||
MediaRequestData(
|
MediaRequestData(
|
||||||
source = MediaSource(url = A_ROOM_AVATAR),
|
source = MediaSource(url = A_ROOM_AVATAR),
|
||||||
kind = MediaRequestData.Kind.Thumbnail(1024)
|
kind = MediaRequestData.Kind.Thumbnail(AVATAR_THUMBNAIL_SIZE_IN_PIXEL)
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ package io.element.android.libraries.pushproviders.unifiedpush
|
||||||
import io.element.android.tests.testutils.lambda.lambdaError
|
import io.element.android.tests.testutils.lambda.lambdaError
|
||||||
|
|
||||||
class FakeUnifiedPushNewGatewayHandler(
|
class FakeUnifiedPushNewGatewayHandler(
|
||||||
private val handleResult: suspend (String, String, String) -> Result<Unit> = { _, _, _ -> lambdaError() },
|
private val handleResult: (String, String, String) -> Result<Unit> = { _, _, _ -> lambdaError() },
|
||||||
) : UnifiedPushNewGatewayHandler {
|
) : UnifiedPushNewGatewayHandler {
|
||||||
override suspend fun handle(endpoint: String, pushGateway: String, clientSecret: String): Result<Unit> {
|
override suspend fun handle(endpoint: String, pushGateway: String, clientSecret: String): Result<Unit> {
|
||||||
return handleResult(endpoint, pushGateway, clientSecret)
|
return handleResult(endpoint, pushGateway, clientSecret)
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,10 @@
|
||||||
|
|
||||||
package io.element.android.libraries.roomselect.impl
|
package io.element.android.libraries.roomselect.impl
|
||||||
|
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
import io.element.android.libraries.matrix.ui.model.SelectRoomInfo
|
||||||
|
|
||||||
sealed interface RoomSelectEvents {
|
sealed interface RoomSelectEvents {
|
||||||
data class SetSelectedRoom(val room: RoomSummary) : RoomSelectEvents
|
data class SetSelectedRoom(val room: SelectRoomInfo) : RoomSelectEvents
|
||||||
|
|
||||||
// TODO remove to restore multi-selection
|
// TODO remove to restore multi-selection
|
||||||
data object RemoveSelectedRoom : RoomSelectEvents
|
data object RemoveSelectedRoom : RoomSelectEvents
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ package io.element.android.libraries.roomselect.impl
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
|
@ -20,8 +21,9 @@ import dagger.assisted.AssistedFactory
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
import io.element.android.libraries.matrix.ui.model.SelectRoomInfo
|
||||||
import io.element.android.libraries.roomselect.api.RoomSelectMode
|
import io.element.android.libraries.roomselect.api.RoomSelectMode
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
||||||
|
|
@ -36,7 +38,7 @@ class RoomSelectPresenter @AssistedInject constructor(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): RoomSelectState {
|
override fun present(): RoomSelectState {
|
||||||
var selectedRooms by remember { mutableStateOf(persistentListOf<RoomSummary>()) }
|
var selectedRooms by remember { mutableStateOf(persistentListOf<SelectRoomInfo>()) }
|
||||||
var searchQuery by remember { mutableStateOf("") }
|
var searchQuery by remember { mutableStateOf("") }
|
||||||
var isSearchActive by remember { mutableStateOf(false) }
|
var isSearchActive by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
|
@ -48,9 +50,9 @@ class RoomSelectPresenter @AssistedInject constructor(
|
||||||
dataSource.setSearchQuery(searchQuery)
|
dataSource.setSearchQuery(searchQuery)
|
||||||
}
|
}
|
||||||
|
|
||||||
val roomSummaryDetailsList by dataSource.roomSummaries.collectAsState(initial = persistentListOf())
|
val roomSummaryDetailsList by dataSource.roomInfoList.collectAsState(initial = persistentListOf())
|
||||||
|
|
||||||
val searchResults by remember {
|
val searchResults by remember<State<SearchBarResultState<ImmutableList<SelectRoomInfo>>>> {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
when {
|
when {
|
||||||
roomSummaryDetailsList.isNotEmpty() -> SearchBarResultState.Results(roomSummaryDetailsList.toImmutableList())
|
roomSummaryDetailsList.isNotEmpty() -> SearchBarResultState.Results(roomSummaryDetailsList.toImmutableList())
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,9 @@ import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomList
|
import io.element.android.libraries.matrix.api.roomlist.RoomList
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomListFilter
|
import io.element.android.libraries.matrix.api.roomlist.RoomListFilter
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
|
||||||
import io.element.android.libraries.matrix.api.roomlist.loadAllIncrementally
|
import io.element.android.libraries.matrix.api.roomlist.loadAllIncrementally
|
||||||
|
import io.element.android.libraries.matrix.ui.model.SelectRoomInfo
|
||||||
|
import io.element.android.libraries.matrix.ui.model.toSelectRoomInfo
|
||||||
import kotlinx.collections.immutable.PersistentList
|
import kotlinx.collections.immutable.PersistentList
|
||||||
import kotlinx.collections.immutable.toPersistentList
|
import kotlinx.collections.immutable.toPersistentList
|
||||||
import kotlinx.coroutines.coroutineScope
|
import kotlinx.coroutines.coroutineScope
|
||||||
|
|
@ -38,11 +39,12 @@ class RoomSelectSearchDataSource @Inject constructor(
|
||||||
source = RoomList.Source.All,
|
source = RoomList.Source.All,
|
||||||
)
|
)
|
||||||
|
|
||||||
val roomSummaries: Flow<PersistentList<RoomSummary>> = roomList.filteredSummaries
|
val roomInfoList: Flow<PersistentList<SelectRoomInfo>> = roomList.filteredSummaries
|
||||||
.map { roomSummaries ->
|
.map { roomSummaries ->
|
||||||
roomSummaries
|
roomSummaries
|
||||||
.filter { it.currentUserMembership == CurrentUserMembership.JOINED }
|
.filter { it.info.currentUserMembership == CurrentUserMembership.JOINED }
|
||||||
.distinctBy { it.roomId } // This should be removed once we're sure no duplicate Rooms can be received
|
.distinctBy { it.roomId } // This should be removed once we're sure no duplicate Rooms can be received
|
||||||
|
.map { roomSummary -> roomSummary.toSelectRoomInfo() }
|
||||||
.toPersistentList()
|
.toPersistentList()
|
||||||
}
|
}
|
||||||
.flowOn(coroutineDispatchers.computation)
|
.flowOn(coroutineDispatchers.computation)
|
||||||
|
|
|
||||||
|
|
@ -8,15 +8,15 @@
|
||||||
package io.element.android.libraries.roomselect.impl
|
package io.element.android.libraries.roomselect.impl
|
||||||
|
|
||||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
import io.element.android.libraries.matrix.ui.model.SelectRoomInfo
|
||||||
import io.element.android.libraries.roomselect.api.RoomSelectMode
|
import io.element.android.libraries.roomselect.api.RoomSelectMode
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
||||||
data class RoomSelectState(
|
data class RoomSelectState(
|
||||||
val mode: RoomSelectMode,
|
val mode: RoomSelectMode,
|
||||||
val resultState: SearchBarResultState<ImmutableList<RoomSummary>>,
|
val resultState: SearchBarResultState<ImmutableList<SelectRoomInfo>>,
|
||||||
val query: String,
|
val query: String,
|
||||||
val isSearchActive: Boolean,
|
val isSearchActive: Boolean,
|
||||||
val selectedRooms: ImmutableList<RoomSummary>,
|
val selectedRooms: ImmutableList<SelectRoomInfo>,
|
||||||
val eventSink: (RoomSelectEvents) -> Unit
|
val eventSink: (RoomSelectEvents) -> Unit
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,8 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
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.RoomId
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
import io.element.android.libraries.matrix.ui.components.aSelectRoomInfo
|
||||||
import io.element.android.libraries.matrix.ui.components.aRoomSummaryDetails
|
import io.element.android.libraries.matrix.ui.model.SelectRoomInfo
|
||||||
import io.element.android.libraries.roomselect.api.RoomSelectMode
|
import io.element.android.libraries.roomselect.api.RoomSelectMode
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
@ -32,7 +32,7 @@ open class RoomSelectStateProvider : PreviewParameterProvider<RoomSelectState> {
|
||||||
resultState = SearchBarResultState.Results(aRoomSelectRoomList()),
|
resultState = SearchBarResultState.Results(aRoomSelectRoomList()),
|
||||||
query = "Test",
|
query = "Test",
|
||||||
isSearchActive = true,
|
isSearchActive = true,
|
||||||
selectedRooms = persistentListOf(aRoomSummaryDetails(roomId = RoomId("!room2:domain")))
|
selectedRooms = aRoomSelectRoomList().subList(0, 1),
|
||||||
),
|
),
|
||||||
aRoomSelectState(
|
aRoomSelectState(
|
||||||
mode = RoomSelectMode.Share,
|
mode = RoomSelectMode.Share,
|
||||||
|
|
@ -43,10 +43,10 @@ open class RoomSelectStateProvider : PreviewParameterProvider<RoomSelectState> {
|
||||||
|
|
||||||
private fun aRoomSelectState(
|
private fun aRoomSelectState(
|
||||||
mode: RoomSelectMode = RoomSelectMode.Forward,
|
mode: RoomSelectMode = RoomSelectMode.Forward,
|
||||||
resultState: SearchBarResultState<ImmutableList<RoomSummary>> = SearchBarResultState.Initial(),
|
resultState: SearchBarResultState<ImmutableList<SelectRoomInfo>> = SearchBarResultState.Initial(),
|
||||||
query: String = "",
|
query: String = "",
|
||||||
isSearchActive: Boolean = false,
|
isSearchActive: Boolean = false,
|
||||||
selectedRooms: ImmutableList<RoomSummary> = persistentListOf(),
|
selectedRooms: ImmutableList<SelectRoomInfo> = persistentListOf(),
|
||||||
) = RoomSelectState(
|
) = RoomSelectState(
|
||||||
mode = mode,
|
mode = mode,
|
||||||
resultState = resultState,
|
resultState = resultState,
|
||||||
|
|
@ -57,14 +57,16 @@ private fun aRoomSelectState(
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun aRoomSelectRoomList() = persistentListOf(
|
private fun aRoomSelectRoomList() = persistentListOf(
|
||||||
aRoomSummaryDetails(),
|
aSelectRoomInfo(
|
||||||
aRoomSummaryDetails(
|
roomId = RoomId("!room1:domain"),
|
||||||
|
name = "Room with name",
|
||||||
|
),
|
||||||
|
aSelectRoomInfo(
|
||||||
roomId = RoomId("!room2:domain"),
|
roomId = RoomId("!room2:domain"),
|
||||||
name = "Room with alias",
|
name = "Room with alias",
|
||||||
canonicalAlias = RoomAlias("#alias:example.org"),
|
canonicalAlias = RoomAlias("#alias:example.org"),
|
||||||
),
|
),
|
||||||
aRoomSummaryDetails(
|
aSelectRoomInfo(
|
||||||
roomId = RoomId("!room3:domain"),
|
roomId = RoomId("!room3:domain"),
|
||||||
name = null,
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -47,8 +47,8 @@ import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
import io.element.android.libraries.designsystem.theme.components.TextButton
|
import io.element.android.libraries.designsystem.theme.components.TextButton
|
||||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
|
||||||
import io.element.android.libraries.matrix.ui.components.SelectedRoom
|
import io.element.android.libraries.matrix.ui.components.SelectedRoom
|
||||||
|
import io.element.android.libraries.matrix.ui.model.SelectRoomInfo
|
||||||
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
||||||
import io.element.android.libraries.roomselect.api.RoomSelectMode
|
import io.element.android.libraries.roomselect.api.RoomSelectMode
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
@ -65,13 +65,13 @@ fun RoomSelectView(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
@Suppress("UNUSED_PARAMETER")
|
@Suppress("UNUSED_PARAMETER")
|
||||||
fun onRoomRemoved(roomSummary: RoomSummary) {
|
fun onRoomRemoved(roomInfo: SelectRoomInfo) {
|
||||||
// TODO toggle selection when multi-selection is enabled
|
// TODO toggle selection when multi-selection is enabled
|
||||||
state.eventSink(RoomSelectEvents.RemoveSelectedRoom)
|
state.eventSink(RoomSelectEvents.RemoveSelectedRoom)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SelectedRoomsHelper(isForwarding: Boolean, selectedRooms: ImmutableList<RoomSummary>) {
|
fun SelectedRoomsHelper(isForwarding: Boolean, selectedRooms: ImmutableList<SelectRoomInfo>) {
|
||||||
if (isForwarding) return
|
if (isForwarding) return
|
||||||
SelectedRooms(
|
SelectedRooms(
|
||||||
selectedRooms = selectedRooms,
|
selectedRooms = selectedRooms,
|
||||||
|
|
@ -185,8 +185,8 @@ fun RoomSelectView(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun SelectedRooms(
|
private fun SelectedRooms(
|
||||||
selectedRooms: ImmutableList<RoomSummary>,
|
selectedRooms: ImmutableList<SelectRoomInfo>,
|
||||||
onRemoveRoom: (RoomSummary) -> Unit,
|
onRemoveRoom: (SelectRoomInfo) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
LazyRow(
|
LazyRow(
|
||||||
|
|
@ -194,29 +194,29 @@ private fun SelectedRooms(
|
||||||
contentPadding = PaddingValues(horizontal = 16.dp),
|
contentPadding = PaddingValues(horizontal = 16.dp),
|
||||||
horizontalArrangement = Arrangement.spacedBy(32.dp)
|
horizontalArrangement = Arrangement.spacedBy(32.dp)
|
||||||
) {
|
) {
|
||||||
items(selectedRooms, key = { it.roomId.value }) { roomSummary ->
|
items(selectedRooms, key = { it.roomId.value }) { selectRoomInfo ->
|
||||||
SelectedRoom(roomSummary = roomSummary, onRemoveRoom = onRemoveRoom)
|
SelectedRoom(roomInfo = selectRoomInfo, onRemoveRoom = onRemoveRoom)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun RoomSummaryView(
|
private fun RoomSummaryView(
|
||||||
summary: RoomSummary,
|
roomInfo: SelectRoomInfo,
|
||||||
isSelected: Boolean,
|
isSelected: Boolean,
|
||||||
onSelection: (RoomSummary) -> Unit,
|
onSelection: (SelectRoomInfo) -> Unit,
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clickable { onSelection(summary) }
|
.clickable { onSelection(roomInfo) }
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(start = 16.dp, end = 4.dp)
|
.padding(start = 16.dp, end = 4.dp)
|
||||||
.heightIn(56.dp),
|
.heightIn(56.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
CompositeAvatar(
|
CompositeAvatar(
|
||||||
avatarData = summary.getAvatarData(size = AvatarSize.RoomSelectRoomListItem),
|
avatarData = roomInfo.getAvatarData(size = AvatarSize.RoomSelectRoomListItem),
|
||||||
heroes = summary.heroes.map { user ->
|
heroes = roomInfo.heroes.map { user ->
|
||||||
user.getAvatarData(size = AvatarSize.RoomSelectRoomListItem)
|
user.getAvatarData(size = AvatarSize.RoomSelectRoomListItem)
|
||||||
}.toPersistentList()
|
}.toPersistentList()
|
||||||
)
|
)
|
||||||
|
|
@ -228,14 +228,14 @@ private fun RoomSummaryView(
|
||||||
// Name
|
// Name
|
||||||
Text(
|
Text(
|
||||||
style = ElementTheme.typography.fontBodyLgRegular,
|
style = ElementTheme.typography.fontBodyLgRegular,
|
||||||
text = summary.name ?: stringResource(id = CommonStrings.common_no_room_name),
|
text = roomInfo.name ?: stringResource(id = CommonStrings.common_no_room_name),
|
||||||
fontStyle = FontStyle.Italic.takeIf { summary.name == null },
|
fontStyle = FontStyle.Italic.takeIf { roomInfo.name == null },
|
||||||
color = ElementTheme.colors.textPrimary,
|
color = ElementTheme.colors.textPrimary,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis
|
overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
// Alias
|
// Alias
|
||||||
summary.canonicalAlias?.let { alias ->
|
roomInfo.canonicalAlias?.let { alias ->
|
||||||
Text(
|
Text(
|
||||||
text = alias.value,
|
text = alias.value,
|
||||||
color = ElementTheme.colors.textSecondary,
|
color = ElementTheme.colors.textSecondary,
|
||||||
|
|
@ -245,7 +245,7 @@ private fun RoomSummaryView(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RadioButton(selected = isSelected, onClick = { onSelection(summary) })
|
RadioButton(selected = isSelected, onClick = { onSelection(roomInfo) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import io.element.android.libraries.matrix.api.roomlist.RoomListFilter
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||||
import io.element.android.libraries.matrix.test.room.aRoomSummary
|
import io.element.android.libraries.matrix.test.room.aRoomSummary
|
||||||
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
|
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
|
||||||
|
import io.element.android.libraries.matrix.ui.model.toSelectRoomInfo
|
||||||
import io.element.android.libraries.roomselect.api.RoomSelectMode
|
import io.element.android.libraries.roomselect.api.RoomSelectMode
|
||||||
import io.element.android.tests.testutils.WarmUpRule
|
import io.element.android.tests.testutils.WarmUpRule
|
||||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||||
|
|
@ -58,8 +59,9 @@ class RoomSelectPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - update query`() = runTest {
|
fun `present - update query`() = runTest {
|
||||||
|
val roomSummary = aRoomSummary()
|
||||||
val roomListService = FakeRoomListService().apply {
|
val roomListService = FakeRoomListService().apply {
|
||||||
postAllRooms(listOf(aRoomSummary()))
|
postAllRooms(listOf(roomSummary))
|
||||||
}
|
}
|
||||||
val presenter = createRoomSelectPresenter(
|
val presenter = createRoomSelectPresenter(
|
||||||
roomListService = roomListService
|
roomListService = roomListService
|
||||||
|
|
@ -68,19 +70,10 @@ class RoomSelectPresenterTest {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
val expectedRoomSummary = aRoomSummary()
|
val expectedRoomInfo = roomSummary.toSelectRoomInfo()
|
||||||
// Do not compare the lambda because they will be different. So copy the lambda from expectedRoomSummary to result
|
// Do not compare the lambda because they will be different. So copy the lambda from expectedRoomSummary to result
|
||||||
val result = (awaitItem().resultState as SearchBarResultState.Results).results.map { roomSummary ->
|
val result = (awaitItem().resultState as SearchBarResultState.Results).results
|
||||||
roomSummary.copy(
|
assertThat(result).isEqualTo(listOf(expectedRoomInfo))
|
||||||
lastMessage = roomSummary.lastMessage!!.copy(
|
|
||||||
event = roomSummary.lastMessage!!.event.copy(
|
|
||||||
debugInfoProvider = expectedRoomSummary.lastMessage!!.event.debugInfoProvider,
|
|
||||||
messageShieldProvider = expectedRoomSummary.lastMessage!!.event.messageShieldProvider,
|
|
||||||
)
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
assertThat(result).isEqualTo(listOf(expectedRoomSummary))
|
|
||||||
initialState.eventSink(RoomSelectEvents.ToggleSearchActive)
|
initialState.eventSink(RoomSelectEvents.ToggleSearchActive)
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
initialState.eventSink(RoomSelectEvents.UpdateQuery("string not contained"))
|
initialState.eventSink(RoomSelectEvents.UpdateQuery("string not contained"))
|
||||||
|
|
@ -99,8 +92,9 @@ class RoomSelectPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - select and remove a room`() = runTest {
|
fun `present - select and remove a room`() = runTest {
|
||||||
|
val roomSummary = aRoomSummary()
|
||||||
val roomListService = FakeRoomListService().apply {
|
val roomListService = FakeRoomListService().apply {
|
||||||
postAllRooms(listOf(aRoomSummary()))
|
postAllRooms(listOf(roomSummary))
|
||||||
}
|
}
|
||||||
val presenter = createRoomSelectPresenter(
|
val presenter = createRoomSelectPresenter(
|
||||||
roomListService = roomListService,
|
roomListService = roomListService,
|
||||||
|
|
@ -109,9 +103,9 @@ class RoomSelectPresenterTest {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
val summary = aRoomSummary()
|
val roomInfo = roomSummary.toSelectRoomInfo()
|
||||||
initialState.eventSink(RoomSelectEvents.SetSelectedRoom(summary))
|
initialState.eventSink(RoomSelectEvents.SetSelectedRoom(roomInfo))
|
||||||
assertThat(awaitItem().selectedRooms).isEqualTo(persistentListOf(summary))
|
assertThat(awaitItem().selectedRooms).isEqualTo(persistentListOf(roomInfo))
|
||||||
initialState.eventSink(RoomSelectEvents.RemoveSelectedRoom)
|
initialState.eventSink(RoomSelectEvents.RemoveSelectedRoom)
|
||||||
assertThat(awaitItem().selectedRooms).isEmpty()
|
assertThat(awaitItem().selectedRooms).isEmpty()
|
||||||
cancel()
|
cancel()
|
||||||
|
|
|
||||||
|
|
@ -8,13 +8,27 @@
|
||||||
package io.element.android.libraries.textcomposer.mentions
|
package io.element.android.libraries.textcomposer.mentions
|
||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
|
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||||
|
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
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.room.RoomMember
|
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
sealed interface ResolvedSuggestion {
|
sealed interface ResolvedSuggestion {
|
||||||
data object AtRoom : ResolvedSuggestion
|
data object AtRoom : ResolvedSuggestion
|
||||||
data class Member(val roomMember: RoomMember) : ResolvedSuggestion
|
data class Member(val roomMember: RoomMember) : ResolvedSuggestion
|
||||||
data class Alias(val roomAlias: RoomAlias, val roomSummary: RoomSummary) : ResolvedSuggestion
|
data class Alias(
|
||||||
|
val roomAlias: RoomAlias,
|
||||||
|
val roomId: RoomId,
|
||||||
|
val roomName: String?,
|
||||||
|
val roomAvatarUrl: String?,
|
||||||
|
) : ResolvedSuggestion {
|
||||||
|
fun getAvatarData(size: AvatarSize) = AvatarData(
|
||||||
|
id = roomId.value,
|
||||||
|
name = roomName,
|
||||||
|
url = roomAvatarUrl,
|
||||||
|
size = size,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,10 +16,10 @@ import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
|
||||||
import io.element.android.libraries.matrix.api.permalink.PermalinkData
|
import io.element.android.libraries.matrix.api.permalink.PermalinkData
|
||||||
import io.element.android.libraries.matrix.api.room.IntentionalMention
|
import io.element.android.libraries.matrix.api.room.IntentionalMention
|
||||||
import io.element.android.libraries.matrix.test.A_ROOM_ALIAS
|
import io.element.android.libraries.matrix.test.A_ROOM_ALIAS
|
||||||
|
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||||
import io.element.android.libraries.matrix.test.permalink.FakePermalinkBuilder
|
import io.element.android.libraries.matrix.test.permalink.FakePermalinkBuilder
|
||||||
import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser
|
import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser
|
||||||
import io.element.android.libraries.matrix.test.room.aRoomMember
|
import io.element.android.libraries.matrix.test.room.aRoomMember
|
||||||
import io.element.android.libraries.matrix.test.room.aRoomSummary
|
|
||||||
import io.element.android.libraries.textcomposer.mentions.MentionSpan
|
import io.element.android.libraries.textcomposer.mentions.MentionSpan
|
||||||
import io.element.android.libraries.textcomposer.mentions.MentionSpanProvider
|
import io.element.android.libraries.textcomposer.mentions.MentionSpanProvider
|
||||||
import io.element.android.libraries.textcomposer.mentions.ResolvedSuggestion
|
import io.element.android.libraries.textcomposer.mentions.ResolvedSuggestion
|
||||||
|
|
@ -34,7 +34,7 @@ class MarkdownTextEditorStateTest {
|
||||||
@Test
|
@Test
|
||||||
fun `insertMention - room alias - getMentions return empty list`() {
|
fun `insertMention - room alias - getMentions return empty list`() {
|
||||||
val state = MarkdownTextEditorState(initialText = "Hello @", initialFocus = true)
|
val state = MarkdownTextEditorState(initialText = "Hello @", initialFocus = true)
|
||||||
val suggestion = ResolvedSuggestion.Alias(A_ROOM_ALIAS, aRoomSummary(canonicalAlias = A_ROOM_ALIAS))
|
val suggestion = aRoomAliasSuggestion()
|
||||||
val permalinkBuilder = FakePermalinkBuilder()
|
val permalinkBuilder = FakePermalinkBuilder()
|
||||||
val mentionSpanProvider = aMentionSpanProvider()
|
val mentionSpanProvider = aMentionSpanProvider()
|
||||||
state.insertSuggestion(suggestion, mentionSpanProvider, permalinkBuilder)
|
state.insertSuggestion(suggestion, mentionSpanProvider, permalinkBuilder)
|
||||||
|
|
@ -46,7 +46,7 @@ class MarkdownTextEditorStateTest {
|
||||||
val state = MarkdownTextEditorState(initialText = "Hello #", initialFocus = true).apply {
|
val state = MarkdownTextEditorState(initialText = "Hello #", initialFocus = true).apply {
|
||||||
currentSuggestion = Suggestion(start = 6, end = 7, type = SuggestionType.Room, text = "")
|
currentSuggestion = Suggestion(start = 6, end = 7, type = SuggestionType.Room, text = "")
|
||||||
}
|
}
|
||||||
val suggestion = ResolvedSuggestion.Alias(A_ROOM_ALIAS, aRoomSummary(canonicalAlias = A_ROOM_ALIAS))
|
val suggestion = aRoomAliasSuggestion()
|
||||||
val permalinkParser = FakePermalinkParser(result = { PermalinkData.RoomLink(A_ROOM_ALIAS.toRoomIdOrAlias()) })
|
val permalinkParser = FakePermalinkParser(result = { PermalinkData.RoomLink(A_ROOM_ALIAS.toRoomIdOrAlias()) })
|
||||||
val permalinkBuilder = FakePermalinkBuilder(permalinkForRoomAliasLambda = { Result.failure(IllegalStateException("Failed")) })
|
val permalinkBuilder = FakePermalinkBuilder(permalinkForRoomAliasLambda = { Result.failure(IllegalStateException("Failed")) })
|
||||||
val mentionSpanProvider = aMentionSpanProvider(permalinkParser = permalinkParser)
|
val mentionSpanProvider = aMentionSpanProvider(permalinkParser = permalinkParser)
|
||||||
|
|
@ -58,7 +58,7 @@ class MarkdownTextEditorStateTest {
|
||||||
val state = MarkdownTextEditorState(initialText = "Hello #", initialFocus = true).apply {
|
val state = MarkdownTextEditorState(initialText = "Hello #", initialFocus = true).apply {
|
||||||
currentSuggestion = Suggestion(start = 6, end = 7, type = SuggestionType.Room, text = "")
|
currentSuggestion = Suggestion(start = 6, end = 7, type = SuggestionType.Room, text = "")
|
||||||
}
|
}
|
||||||
val suggestion = ResolvedSuggestion.Alias(A_ROOM_ALIAS, aRoomSummary(canonicalAlias = A_ROOM_ALIAS))
|
val suggestion = aRoomAliasSuggestion()
|
||||||
val permalinkParser = FakePermalinkParser(result = { PermalinkData.RoomLink(A_ROOM_ALIAS.toRoomIdOrAlias()) })
|
val permalinkParser = FakePermalinkParser(result = { PermalinkData.RoomLink(A_ROOM_ALIAS.toRoomIdOrAlias()) })
|
||||||
val permalinkBuilder = FakePermalinkBuilder(permalinkForRoomAliasLambda = { Result.success("https://matrix.to/#/${A_ROOM_ALIAS.value}") })
|
val permalinkBuilder = FakePermalinkBuilder(permalinkForRoomAliasLambda = { Result.success("https://matrix.to/#/${A_ROOM_ALIAS.value}") })
|
||||||
val mentionSpanProvider = aMentionSpanProvider(permalinkParser = permalinkParser)
|
val mentionSpanProvider = aMentionSpanProvider(permalinkParser = permalinkParser)
|
||||||
|
|
@ -202,4 +202,13 @@ class MarkdownTextEditorStateTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun aRoomAliasSuggestion(): ResolvedSuggestion.Alias {
|
||||||
|
return ResolvedSuggestion.Alias(
|
||||||
|
roomAlias = A_ROOM_ALIAS,
|
||||||
|
roomId = A_ROOM_ID,
|
||||||
|
roomName = null,
|
||||||
|
roomAvatarUrl = null
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,4 +24,5 @@ dependencies {
|
||||||
implementation(libs.autonomousapps.dependencyanalysis.plugin)
|
implementation(libs.autonomousapps.dependencyanalysis.plugin)
|
||||||
implementation(libs.anvil.gradle.plugin)
|
implementation(libs.anvil.gradle.plugin)
|
||||||
implementation(libs.ksp.gradle.plugin)
|
implementation(libs.ksp.gradle.plugin)
|
||||||
|
implementation(libs.compose.compiler.plugin)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
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