mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-07 01:19:17 +00:00
Merge branch 'develop' into sync-localazy
This commit is contained in:
@@ -51,7 +51,7 @@ jobs:
|
|||||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }}
|
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }}
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Use JDK 21
|
- name: Use JDK 21
|
||||||
uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0
|
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ jobs:
|
|||||||
- name: Clone submodules
|
- name: Clone submodules
|
||||||
run: git submodule update --init --recursive
|
run: git submodule update --init --recursive
|
||||||
- name: Use JDK 21
|
- name: Use JDK 21
|
||||||
uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0
|
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ jobs:
|
|||||||
- name: ⏬ Checkout with LFS
|
- name: ⏬ Checkout with LFS
|
||||||
uses: nschloe/action-cached-lfs-checkout@385a8ecc719e50b8c71af6ab01a624b486b7c3bc # v1.2.5
|
uses: nschloe/action-cached-lfs-checkout@385a8ecc719e50b8c71af6ab01a624b486b7c3bc # v1.2.5
|
||||||
- name: Use JDK 21
|
- name: Use JDK 21
|
||||||
uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0
|
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ jobs:
|
|||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0
|
- uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
|
||||||
name: Use JDK 21
|
name: Use JDK 21
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ jobs:
|
|||||||
# https://github.com/actions/checkout/issues/881
|
# https://github.com/actions/checkout/issues/881
|
||||||
ref: ${{ github.ref }}
|
ref: ${{ github.ref }}
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0
|
- uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
|
||||||
name: Use JDK 21
|
name: Use JDK 21
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Use JDK 21
|
- name: Use JDK 21
|
||||||
uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0
|
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ jobs:
|
|||||||
uses: nschloe/action-cached-lfs-checkout@385a8ecc719e50b8c71af6ab01a624b486b7c3bc # v1.2.5
|
uses: nschloe/action-cached-lfs-checkout@385a8ecc719e50b8c71af6ab01a624b486b7c3bc # v1.2.5
|
||||||
|
|
||||||
- name: Use JDK 21
|
- name: Use JDK 21
|
||||||
uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0
|
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
@@ -80,7 +80,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Use JDK 21
|
- name: Use JDK 21
|
||||||
uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0
|
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Use JDK 21
|
- name: Use JDK 21
|
||||||
uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0
|
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
@@ -108,7 +108,7 @@ jobs:
|
|||||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
||||||
run: git submodule update --init --recursive
|
run: git submodule update --init --recursive
|
||||||
- name: Use JDK 21
|
- name: Use JDK 21
|
||||||
uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0
|
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
@@ -149,7 +149,7 @@ jobs:
|
|||||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
||||||
run: git submodule update --init --recursive
|
run: git submodule update --init --recursive
|
||||||
- name: Use JDK 21
|
- name: Use JDK 21
|
||||||
uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0
|
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
@@ -183,7 +183,7 @@ jobs:
|
|||||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
||||||
run: git submodule update --init --recursive
|
run: git submodule update --init --recursive
|
||||||
- name: Use JDK 21
|
- name: Use JDK 21
|
||||||
uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0
|
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
@@ -228,7 +228,7 @@ jobs:
|
|||||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
||||||
run: git submodule update --init --recursive
|
run: git submodule update --init --recursive
|
||||||
- name: Use JDK 21
|
- name: Use JDK 21
|
||||||
uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0
|
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
@@ -269,7 +269,7 @@ jobs:
|
|||||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
||||||
run: git submodule update --init --recursive
|
run: git submodule update --init --recursive
|
||||||
- name: Use JDK 21
|
- name: Use JDK 21
|
||||||
uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0
|
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: ☕️ Use JDK 21
|
- name: ☕️ Use JDK 21
|
||||||
uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0
|
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Use JDK 21
|
- name: Use JDK 21
|
||||||
uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0
|
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
@@ -88,7 +88,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Use JDK 21
|
- name: Use JDK 21
|
||||||
uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0
|
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ jobs:
|
|||||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }}
|
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }}
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Use JDK 21
|
- name: Use JDK 21
|
||||||
uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0
|
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Use JDK 21
|
- name: Use JDK 21
|
||||||
uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0
|
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ jobs:
|
|||||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
||||||
run: git submodule update --init --recursive
|
run: git submodule update --init --recursive
|
||||||
- name: ☕️ Use JDK 21
|
- name: ☕️ Use JDK 21
|
||||||
uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0
|
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
@@ -78,6 +78,9 @@ jobs:
|
|||||||
- name: ⚙️ Run screenshot tests
|
- name: ⚙️ Run screenshot tests
|
||||||
run: ./gradlew :tests:uitests:verifyPaparazziDebug $CI_GRADLE_ARG_PROPERTIES
|
run: ./gradlew :tests:uitests:verifyPaparazziDebug $CI_GRADLE_ARG_PROPERTIES
|
||||||
|
|
||||||
|
- name: ⚙️ Run Compound screenshot tests
|
||||||
|
run: ./gradlew :libraries:compound:verifyRoborazziDebug $CI_GRADLE_ARG_PROPERTIES
|
||||||
|
|
||||||
- name: ⚙️ Generate kover coverage report
|
- name: ⚙️ Generate kover coverage report
|
||||||
run: ./gradlew :koverXmlReportMerged :koverHtmlReportMerged :koverVerifyAll -x testDebugUnitTest $CI_GRADLE_ARG_PROPERTIES
|
run: ./gradlew :koverXmlReportMerged :koverHtmlReportMerged :koverVerifyAll -x testDebugUnitTest $CI_GRADLE_ARG_PROPERTIES
|
||||||
|
|
||||||
|
|||||||
@@ -9,15 +9,8 @@
|
|||||||
package io.element.android.appconfig
|
package io.element.android.appconfig
|
||||||
|
|
||||||
object AuthenticationConfig {
|
object AuthenticationConfig {
|
||||||
const val MATRIX_ORG_URL = "https://matrix.org"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* URL with some docs that explain what's sliding sync and how to add it to your home server.
|
* URL with some docs that explain what's sliding sync and how to add it to your home server.
|
||||||
*/
|
*/
|
||||||
const val SLIDING_SYNC_READ_MORE_URL = "https://github.com/matrix-org/sliding-sync/blob/main/docs/Landing.md"
|
const val SLIDING_SYNC_READ_MORE_URL = "https://github.com/matrix-org/sliding-sync/blob/main/docs/Landing.md"
|
||||||
|
|
||||||
/**
|
|
||||||
* Force a sliding sync proxy url, if not null, the proxy url in the .well-known file will be ignored.
|
|
||||||
*/
|
|
||||||
val SLIDING_SYNC_PROXY_URL: String? = null
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,11 +52,11 @@ import io.element.android.libraries.architecture.BaseFlowNode
|
|||||||
import io.element.android.libraries.architecture.appyx.rememberDelegateTransitionHandler
|
import io.element.android.libraries.architecture.appyx.rememberDelegateTransitionHandler
|
||||||
import io.element.android.libraries.architecture.createNode
|
import io.element.android.libraries.architecture.createNode
|
||||||
import io.element.android.libraries.architecture.waitForChildAttached
|
import io.element.android.libraries.architecture.waitForChildAttached
|
||||||
import io.element.android.libraries.core.uri.ensureProtocol
|
|
||||||
import io.element.android.libraries.deeplink.api.DeeplinkData
|
import io.element.android.libraries.deeplink.api.DeeplinkData
|
||||||
import io.element.android.libraries.di.annotations.AppCoroutineScope
|
import io.element.android.libraries.di.annotations.AppCoroutineScope
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||||
|
import io.element.android.libraries.matrix.api.accountprovider.AccountProvider
|
||||||
import io.element.android.libraries.matrix.api.core.EventId
|
import io.element.android.libraries.matrix.api.core.EventId
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
import io.element.android.libraries.matrix.api.core.ThreadId
|
import io.element.android.libraries.matrix.api.core.ThreadId
|
||||||
@@ -410,7 +410,8 @@ class RootFlowNode(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun onLoginLink(params: LoginParams) {
|
private suspend fun onLoginLink(params: LoginParams) {
|
||||||
if (accountProviderAccessControl.isAllowedToConnectToAccountProvider(params.accountProvider.ensureProtocol())) {
|
val accountProvider = AccountProvider.Generic(params.accountProvider)
|
||||||
|
if (accountProviderAccessControl.isAllowedToConnectToAccountProvider(accountProvider)) {
|
||||||
// Is there a session already?
|
// Is there a session already?
|
||||||
val sessions = sessionStore.getAllSessions()
|
val sessions = sessionStore.getAllSessions()
|
||||||
if (sessions.isNotEmpty()) {
|
if (sessions.isNotEmpty()) {
|
||||||
|
|||||||
@@ -153,7 +153,10 @@ allprojects {
|
|||||||
maxParallelForks = (Runtime.getRuntime().availableProcessors() / 2).coerceAtLeast(1)
|
maxParallelForks = (Runtime.getRuntime().availableProcessors() / 2).coerceAtLeast(1)
|
||||||
|
|
||||||
val isScreenshotTest = project.gradle.startParameter.taskNames.any { it.contains("paparazzi", ignoreCase = true) }
|
val isScreenshotTest = project.gradle.startParameter.taskNames.any { it.contains("paparazzi", ignoreCase = true) }
|
||||||
|
val isRoborazziTest = project.gradle.startParameter.taskNames.any { it.contains("roborazzi", ignoreCase = true) }
|
||||||
if (isScreenshotTest) {
|
if (isScreenshotTest) {
|
||||||
|
// Paparazzi tests benefit from parallelisation, so we can use half the available cores to run them in parallel.
|
||||||
|
maxParallelForks = (Runtime.getRuntime().availableProcessors() / 2).coerceAtLeast(1)
|
||||||
// Increase heap size for screenshot tests
|
// Increase heap size for screenshot tests
|
||||||
maxHeapSize = "2g"
|
maxHeapSize = "2g"
|
||||||
// Record all the languages?
|
// Record all the languages?
|
||||||
@@ -165,9 +168,22 @@ allprojects {
|
|||||||
exclude("translations/*.class")
|
exclude("translations/*.class")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Robolectric and Compose pay a 5 to 30 seconds bootstrap cost per test JVM (instrumenting
|
||||||
|
// android-all, then warming up the Compose runtime). That cost is paid once per JVM and then
|
||||||
|
// amortised over every test class the JVM runs, so splitting a module across several forks
|
||||||
|
// re-pays it for each fork instead of saving time. Keep a single fork per module and let
|
||||||
|
// Gradle parallelise by running many modules' test tasks concurrently instead.
|
||||||
|
maxParallelForks = 1
|
||||||
|
|
||||||
// Disable screenshot tests by default
|
// Disable screenshot tests by default
|
||||||
exclude("ui/*.class")
|
exclude("ui/*.class")
|
||||||
exclude("translations/*.class")
|
exclude("translations/*.class")
|
||||||
|
if (isRoborazziTest.not()) {
|
||||||
|
// Roborazzi screenshot tests (:libraries:compound) live in a `screenshot` package, which
|
||||||
|
// the two patterns above do not match, so they used to run on every plain unit test run.
|
||||||
|
// They are verified by the dedicated `verifyRoborazziDebug` task instead.
|
||||||
|
exclude("**/screenshot/**")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
Submodule enterprise updated: f463003f68...6b422f4c8e
+12
-18
@@ -11,6 +11,7 @@ package io.element.android.features.enterprise.api
|
|||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import io.element.android.compound.colors.SemanticColorsLightDark
|
import io.element.android.compound.colors.SemanticColorsLightDark
|
||||||
import io.element.android.libraries.matrix.api.ClientUrlContentFetcher
|
import io.element.android.libraries.matrix.api.ClientUrlContentFetcher
|
||||||
|
import io.element.android.libraries.matrix.api.accountprovider.AccountProvider
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
@@ -36,18 +37,21 @@ interface EnterpriseService {
|
|||||||
suspend fun tweakMasUrl(url: String, urlContentFetcher: ClientUrlContentFetcher): String
|
suspend fun tweakMasUrl(url: String, urlContentFetcher: ClientUrlContentFetcher): String
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the list of homeservers the user is allowed to sign in to.
|
* Returns the list of account provider the user is allowed to sign in to.
|
||||||
*
|
|
||||||
* If the list is empty or contains the special value [ANY_ACCOUNT_PROVIDER], the user is allowed to sign in to any homeserver.
|
|
||||||
*/
|
*/
|
||||||
fun homeserverAllowList(): List<String>
|
fun accountProviderAllowList(): List<AccountProvider>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the user is allowed to sign in to a given homeserver, according to [homeserverAllowList].
|
* Whether the user is allowed to sign in to any account provider.
|
||||||
*
|
|
||||||
* @param homeserverUrl the server the user is trying to use.
|
|
||||||
*/
|
*/
|
||||||
suspend fun isAllowedToConnectToHomeserver(homeserverUrl: String): Boolean
|
fun canConnectToAnyAccountProvider(): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the user is allowed to sign in to a given homeserver, according to [accountProviderAllowList].
|
||||||
|
*
|
||||||
|
* @param accountProvider the account provider the user is trying to use.
|
||||||
|
*/
|
||||||
|
suspend fun isAllowedToConnectToAccountProvider(accountProvider: AccountProvider): Boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the given homeserver enforces the use of Element Pro or a derived app.
|
* Whether the given homeserver enforces the use of Element Pro or a derived app.
|
||||||
@@ -97,14 +101,4 @@ interface EnterpriseService {
|
|||||||
* @param sessionId the session whose channel is requested.
|
* @param sessionId the session whose channel is requested.
|
||||||
*/
|
*/
|
||||||
fun getNoisyNotificationChannelId(sessionId: SessionId): String?
|
fun getNoisyNotificationChannelId(sessionId: SessionId): String?
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val ANY_ACCOUNT_PROVIDER = "*"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun EnterpriseService.canConnectToAnyHomeserver(): Boolean {
|
|
||||||
return homeserverAllowList().let {
|
|
||||||
it.isEmpty() || it.contains(EnterpriseService.ANY_ACCOUNT_PROVIDER)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-2
@@ -19,6 +19,7 @@ import io.element.android.libraries.core.extensions.mapCatchingExceptions
|
|||||||
import io.element.android.libraries.core.uri.ensureProtocol
|
import io.element.android.libraries.core.uri.ensureProtocol
|
||||||
import io.element.android.libraries.matrix.api.ClientUrlContentFetcher
|
import io.element.android.libraries.matrix.api.ClientUrlContentFetcher
|
||||||
import io.element.android.libraries.matrix.api.TemporaryMatrixClientFactory
|
import io.element.android.libraries.matrix.api.TemporaryMatrixClientFactory
|
||||||
|
import io.element.android.libraries.matrix.api.accountprovider.AccountProvider
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.flowOf
|
import kotlinx.coroutines.flow.flowOf
|
||||||
@@ -33,8 +34,9 @@ class DefaultEnterpriseService(
|
|||||||
) : EnterpriseService {
|
) : EnterpriseService {
|
||||||
override suspend fun isEnterpriseUser(sessionId: SessionId) = false
|
override suspend fun isEnterpriseUser(sessionId: SessionId) = false
|
||||||
override suspend fun tweakMasUrl(url: String, urlContentFetcher: ClientUrlContentFetcher) = url
|
override suspend fun tweakMasUrl(url: String, urlContentFetcher: ClientUrlContentFetcher) = url
|
||||||
override fun homeserverAllowList(): List<String> = emptyList()
|
override fun accountProviderAllowList(): List<AccountProvider> = emptyList()
|
||||||
override suspend fun isAllowedToConnectToHomeserver(homeserverUrl: String) = true
|
override fun canConnectToAnyAccountProvider(): Boolean = true
|
||||||
|
override suspend fun isAllowedToConnectToAccountProvider(accountProvider: AccountProvider) = true
|
||||||
override suspend fun isElementProEnforced(serverName: String): Boolean {
|
override suspend fun isElementProEnforced(serverName: String): Boolean {
|
||||||
val temporaryMatrixClient = temporaryMatrixClientFactory.create(serverName).getOrElse { return false }
|
val temporaryMatrixClient = temporaryMatrixClientFactory.create(serverName).getOrElse { return false }
|
||||||
return temporaryMatrixClient.use { client ->
|
return temporaryMatrixClient.use { client ->
|
||||||
|
|||||||
+11
-4
@@ -18,6 +18,7 @@ import io.element.android.libraries.matrix.test.A_HOMESERVER_URL
|
|||||||
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.FakeTemporaryMatrixClient
|
import io.element.android.libraries.matrix.test.FakeTemporaryMatrixClient
|
||||||
import io.element.android.libraries.matrix.test.FakeTemporaryMatrixClientFactory
|
import io.element.android.libraries.matrix.test.FakeTemporaryMatrixClientFactory
|
||||||
|
import io.element.android.libraries.matrix.test.accountprovider.anAccountProviderGeneric
|
||||||
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
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
@@ -26,15 +27,21 @@ import org.junit.Test
|
|||||||
|
|
||||||
class DefaultEnterpriseServiceTest {
|
class DefaultEnterpriseServiceTest {
|
||||||
@Test
|
@Test
|
||||||
fun homeserverWhitelist() {
|
fun accountProviderAllowList() {
|
||||||
val defaultEnterpriseService = createDefaultEnterpriseService()
|
val defaultEnterpriseService = createDefaultEnterpriseService()
|
||||||
assertThat(defaultEnterpriseService.homeserverAllowList()).isEmpty()
|
assertThat(defaultEnterpriseService.accountProviderAllowList()).isEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `isAllowedToConnectToHomeserver is true for all homeserver urls`() = runTest {
|
fun `canConnectToAnyAccountProvider is always true`() {
|
||||||
val defaultEnterpriseService = createDefaultEnterpriseService()
|
val defaultEnterpriseService = createDefaultEnterpriseService()
|
||||||
assertThat(defaultEnterpriseService.isAllowedToConnectToHomeserver(A_HOMESERVER_URL)).isTrue()
|
assertThat(defaultEnterpriseService.canConnectToAnyAccountProvider()).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `isAllowedToConnectToAccountProvider is true for all account providers`() = runTest {
|
||||||
|
val defaultEnterpriseService = createDefaultEnterpriseService()
|
||||||
|
assertThat(defaultEnterpriseService.isAllowedToConnectToAccountProvider(anAccountProviderGeneric())).isTrue()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
+12
-6
@@ -13,6 +13,7 @@ import io.element.android.compound.colors.SemanticColorsLightDark
|
|||||||
import io.element.android.features.enterprise.api.BugReportUrl
|
import io.element.android.features.enterprise.api.BugReportUrl
|
||||||
import io.element.android.features.enterprise.api.EnterpriseService
|
import io.element.android.features.enterprise.api.EnterpriseService
|
||||||
import io.element.android.libraries.matrix.api.ClientUrlContentFetcher
|
import io.element.android.libraries.matrix.api.ClientUrlContentFetcher
|
||||||
|
import io.element.android.libraries.matrix.api.accountprovider.AccountProvider
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
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
|
||||||
@@ -22,8 +23,9 @@ import kotlinx.coroutines.flow.asStateFlow
|
|||||||
|
|
||||||
class FakeEnterpriseService(
|
class FakeEnterpriseService(
|
||||||
private val isEnterpriseUserResult: (SessionId) -> Boolean = { lambdaError() },
|
private val isEnterpriseUserResult: (SessionId) -> Boolean = { lambdaError() },
|
||||||
private val defaultHomeserverListResult: () -> List<String> = { emptyList() },
|
private val accountProviderAllowListResult: () -> List<AccountProvider> = { emptyList() },
|
||||||
private val isAllowedToConnectToHomeserverResult: (String) -> Boolean = { lambdaError() },
|
private val canConnectToAnyAccountProviderResult: () -> Boolean = { true },
|
||||||
|
private val isAllowedToConnectToAccountProviderResult: (AccountProvider) -> Boolean = { lambdaError() },
|
||||||
initialSemanticColors: SemanticColorsLightDark = SemanticColorsLightDark.default,
|
initialSemanticColors: SemanticColorsLightDark = SemanticColorsLightDark.default,
|
||||||
initialBrandColor: Color? = null,
|
initialBrandColor: Color? = null,
|
||||||
private val overrideBrandColorResult: (SessionId?, String?) -> Unit = { _, _ -> lambdaError() },
|
private val overrideBrandColorResult: (SessionId?, String?) -> Unit = { _, _ -> lambdaError() },
|
||||||
@@ -44,12 +46,16 @@ class FakeEnterpriseService(
|
|||||||
tweakMasUrlResult(url, urlContentFetcher)
|
tweakMasUrlResult(url, urlContentFetcher)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun homeserverAllowList(): List<String> {
|
override fun accountProviderAllowList(): List<AccountProvider> {
|
||||||
return defaultHomeserverListResult()
|
return accountProviderAllowListResult()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun isAllowedToConnectToHomeserver(homeserverUrl: String): Boolean = simulateLongTask {
|
override fun canConnectToAnyAccountProvider(): Boolean {
|
||||||
isAllowedToConnectToHomeserverResult(homeserverUrl)
|
return canConnectToAnyAccountProviderResult()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun isAllowedToConnectToAccountProvider(accountProvider: AccountProvider): Boolean = simulateLongTask {
|
||||||
|
isAllowedToConnectToAccountProviderResult(accountProvider)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun isElementProEnforced(serverName: String): Boolean = simulateLongTask {
|
override suspend fun isElementProEnforced(serverName: String): Boolean = simulateLongTask {
|
||||||
|
|||||||
-1
@@ -91,7 +91,6 @@ import org.junit.Rule
|
|||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
@Suppress("LargeClass")
|
|
||||||
class RoomListPresenterTest {
|
class RoomListPresenterTest {
|
||||||
@get:Rule
|
@get:Rule
|
||||||
val warmUpRule = WarmUpRule()
|
val warmUpRule = WarmUpRule()
|
||||||
|
|||||||
-1
@@ -59,7 +59,6 @@ import kotlinx.coroutines.test.runTest
|
|||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
@Suppress("LargeClass")
|
|
||||||
internal class DefaultInvitePeoplePresenterTest {
|
internal class DefaultInvitePeoplePresenterTest {
|
||||||
@get:Rule
|
@get:Rule
|
||||||
val warmUpRule = WarmUpRule()
|
val warmUpRule = WarmUpRule()
|
||||||
|
|||||||
+1
@@ -107,6 +107,7 @@ sealed interface LoadedDetails {
|
|||||||
) : LoadedDetails
|
) : LoadedDetails
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Immutable
|
||||||
sealed interface JoinAuthorisationStatus {
|
sealed interface JoinAuthorisationStatus {
|
||||||
data object None : JoinAuthorisationStatus
|
data object None : JoinAuthorisationStatus
|
||||||
data class IsInvited(val inviteData: InviteData, val inviteSender: InviteSender?) : JoinAuthorisationStatus
|
data class IsInvited(val inviteData: InviteData, val inviteSender: InviteSender?) : JoinAuthorisationStatus
|
||||||
|
|||||||
-1
@@ -70,7 +70,6 @@ import org.junit.Rule
|
|||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.util.Optional
|
import java.util.Optional
|
||||||
|
|
||||||
@Suppress("LargeClass")
|
|
||||||
class JoinRoomPresenterTest {
|
class JoinRoomPresenterTest {
|
||||||
@get:Rule
|
@get:Rule
|
||||||
val warmUpRule = WarmUpRule()
|
val warmUpRule = WarmUpRule()
|
||||||
|
|||||||
@@ -16,4 +16,5 @@ android {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(projects.libraries.architecture)
|
implementation(projects.libraries.architecture)
|
||||||
|
implementation(projects.libraries.matrix.api)
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-2
@@ -8,6 +8,8 @@
|
|||||||
|
|
||||||
package io.element.android.features.login.api.accesscontrol
|
package io.element.android.features.login.api.accesscontrol
|
||||||
|
|
||||||
|
import io.element.android.libraries.matrix.api.accountprovider.AccountProvider
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enforces the restriction an enterprise deployment can put on which account providers the user may sign in to.
|
* Enforces the restriction an enterprise deployment can put on which account providers the user may sign in to.
|
||||||
*/
|
*/
|
||||||
@@ -15,7 +17,7 @@ interface AccountProviderAccessControl {
|
|||||||
/**
|
/**
|
||||||
* Whether sign-in to this provider is permitted; `true` on a build with no such restriction.
|
* Whether sign-in to this provider is permitted; `true` on a build with no such restriction.
|
||||||
*
|
*
|
||||||
* @param accountProviderUrl the server the user is trying to use.
|
* @param accountProvider the account provider the user is trying to use.
|
||||||
*/
|
*/
|
||||||
suspend fun isAllowedToConnectToAccountProvider(accountProviderUrl: String): Boolean
|
suspend fun isAllowedToConnectToAccountProvider(accountProvider: AccountProvider): Boolean
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-10
@@ -14,16 +14,16 @@ import io.element.android.features.enterprise.api.EnterpriseService
|
|||||||
import io.element.android.features.enterprise.api.IsEnterpriseBuild
|
import io.element.android.features.enterprise.api.IsEnterpriseBuild
|
||||||
import io.element.android.features.login.api.accesscontrol.AccountProviderAccessControl
|
import io.element.android.features.login.api.accesscontrol.AccountProviderAccessControl
|
||||||
import io.element.android.features.login.impl.changeserver.AccountProviderAccessException
|
import io.element.android.features.login.impl.changeserver.AccountProviderAccessException
|
||||||
|
import io.element.android.libraries.matrix.api.accountprovider.AccountProvider
|
||||||
|
|
||||||
@ContributesBinding(AppScope::class)
|
@ContributesBinding(AppScope::class)
|
||||||
class DefaultAccountProviderAccessControl(
|
class DefaultAccountProviderAccessControl(
|
||||||
private val isEnterpriseBuild: IsEnterpriseBuild,
|
private val isEnterpriseBuild: IsEnterpriseBuild,
|
||||||
private val enterpriseService: EnterpriseService,
|
private val enterpriseService: EnterpriseService,
|
||||||
) : AccountProviderAccessControl {
|
) : AccountProviderAccessControl {
|
||||||
override suspend fun isAllowedToConnectToAccountProvider(accountProviderUrl: String) = try {
|
override suspend fun isAllowedToConnectToAccountProvider(accountProvider: AccountProvider) = try {
|
||||||
assertIsAllowedToConnectToAccountProvider(
|
assertIsAllowedToConnectToAccountProvider(
|
||||||
title = accountProviderUrl,
|
accountProvider = accountProvider,
|
||||||
accountProviderUrl = accountProviderUrl,
|
|
||||||
)
|
)
|
||||||
true
|
true
|
||||||
} catch (_: AccountProviderAccessException) {
|
} catch (_: AccountProviderAccessException) {
|
||||||
@@ -32,22 +32,22 @@ class DefaultAccountProviderAccessControl(
|
|||||||
|
|
||||||
@Throws(AccountProviderAccessException::class)
|
@Throws(AccountProviderAccessException::class)
|
||||||
suspend fun assertIsAllowedToConnectToAccountProvider(
|
suspend fun assertIsAllowedToConnectToAccountProvider(
|
||||||
title: String,
|
accountProvider: AccountProvider,
|
||||||
accountProviderUrl: String,
|
|
||||||
) {
|
) {
|
||||||
if (isEnterpriseBuild().not()) {
|
if (isEnterpriseBuild().not()) {
|
||||||
// Ensure that Element Pro is not required for this account provider
|
// Ensure that Element Pro is not required for this account provider
|
||||||
if (enterpriseService.isElementProEnforced(accountProviderUrl)) {
|
if (enterpriseService.isElementProEnforced(accountProvider.serverNameOrBaseUrl())) {
|
||||||
throw AccountProviderAccessException.NeedElementProException(
|
throw AccountProviderAccessException.NeedElementProException(
|
||||||
unauthorisedAccountProviderTitle = title,
|
unauthorisedAccountProviderTitle = accountProvider.friendlyServerName(),
|
||||||
applicationId = ELEMENT_PRO_APPLICATION_ID,
|
applicationId = ELEMENT_PRO_APPLICATION_ID,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (enterpriseService.isAllowedToConnectToHomeserver(accountProviderUrl).not()) {
|
if (enterpriseService.isAllowedToConnectToAccountProvider(accountProvider).not()) {
|
||||||
throw AccountProviderAccessException.UnauthorizedAccountProviderException(
|
throw AccountProviderAccessException.UnauthorizedAccountProviderException(
|
||||||
unauthorisedAccountProviderTitle = title,
|
unauthorisedAccountProviderTitle = accountProvider.friendlyServerName(),
|
||||||
authorisedAccountProviderTitles = enterpriseService.homeserverAllowList(),
|
authorisedAccountProviderTitles = enterpriseService.accountProviderAllowList()
|
||||||
|
.map { it.friendlyServerName() },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
-17
@@ -1,17 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2025 Element Creations Ltd.
|
|
||||||
* Copyright 2023-2025 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
|
||||||
* Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.element.android.features.login.impl.accountprovider
|
|
||||||
|
|
||||||
data class AccountProvider(
|
|
||||||
val url: String,
|
|
||||||
val title: String = url.removePrefix("https://").removePrefix("http://"),
|
|
||||||
val subtitle: String? = null,
|
|
||||||
val isPublic: Boolean = false,
|
|
||||||
val isMatrixOrg: Boolean = false,
|
|
||||||
)
|
|
||||||
+10
-21
@@ -11,10 +11,10 @@ package io.element.android.features.login.impl.accountprovider
|
|||||||
import dev.zacsweers.metro.AppScope
|
import dev.zacsweers.metro.AppScope
|
||||||
import dev.zacsweers.metro.Inject
|
import dev.zacsweers.metro.Inject
|
||||||
import dev.zacsweers.metro.SingleIn
|
import dev.zacsweers.metro.SingleIn
|
||||||
import io.element.android.appconfig.AuthenticationConfig
|
|
||||||
import io.element.android.features.enterprise.api.EnterpriseService
|
import io.element.android.features.enterprise.api.EnterpriseService
|
||||||
import io.element.android.features.enterprise.api.canConnectToAnyHomeserver
|
|
||||||
import io.element.android.libraries.di.annotations.AppCoroutineScope
|
import io.element.android.libraries.di.annotations.AppCoroutineScope
|
||||||
|
import io.element.android.libraries.matrix.api.accountprovider.AccountProvider
|
||||||
|
import io.element.android.libraries.matrix.api.accountprovider.matrixOrgAccountProvider
|
||||||
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
@@ -33,11 +33,9 @@ class AccountProviderDataSource(
|
|||||||
) {
|
) {
|
||||||
// The provider used when the user has not selected one: an enterprise/MDM-configured provider,
|
// The provider used when the user has not selected one: an enterprise/MDM-configured provider,
|
||||||
// else matrix.org. The most recently used provider (from history) can override it, see init.
|
// else matrix.org. The most recently used provider (from history) can override it, see init.
|
||||||
private val configuredAccountProvider = createAccountProvider(
|
private val configuredAccountProvider = enterpriseService.accountProviderAllowList()
|
||||||
url = enterpriseService.homeserverAllowList()
|
.firstOrNull()
|
||||||
.firstOrNull { it != EnterpriseService.ANY_ACCOUNT_PROVIDER }
|
?: matrixOrgAccountProvider
|
||||||
?: AuthenticationConfig.MATRIX_ORG_URL
|
|
||||||
)
|
|
||||||
|
|
||||||
private val accountProvider: MutableStateFlow<AccountProvider> = MutableStateFlow(configuredAccountProvider)
|
private val accountProvider: MutableStateFlow<AccountProvider> = MutableStateFlow(configuredAccountProvider)
|
||||||
|
|
||||||
@@ -46,7 +44,7 @@ class AccountProviderDataSource(
|
|||||||
// The account provider the user last explicitly selected (via [setAccountProvider] / [setUrl]).
|
// The account provider the user last explicitly selected (via [setAccountProvider] / [setUrl]).
|
||||||
// Unlike [flow], this is not recomputed by [reset], so it survives to be persisted to history on a
|
// Unlike [flow], this is not recomputed by [reset], so it survives to be persisted to history on a
|
||||||
// successful sign-in even when the login flow is torn down in between (e.g. across an OAuth round-trip).
|
// successful sign-in even when the login flow is torn down in between (e.g. across an OAuth round-trip).
|
||||||
var lastSelectedAccountProviderUrl: String? = null
|
var lastSelectedAccountProvider: AccountProvider? = null
|
||||||
private set
|
private set
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@@ -66,28 +64,19 @@ class AccountProviderDataSource(
|
|||||||
* connect to any provider, otherwise the enterprise/MDM-configured provider.
|
* connect to any provider, otherwise the enterprise/MDM-configured provider.
|
||||||
*/
|
*/
|
||||||
private suspend fun defaultAccountProvider(): AccountProvider {
|
private suspend fun defaultAccountProvider(): AccountProvider {
|
||||||
if (!enterpriseService.canConnectToAnyHomeserver()) {
|
if (!enterpriseService.canConnectToAnyAccountProvider()) {
|
||||||
return configuredAccountProvider
|
return configuredAccountProvider
|
||||||
}
|
}
|
||||||
val lastUsedProvider = appPreferencesStore.getHomeserverHistoryFlow().first().firstOrNull()
|
val lastUsedProvider = appPreferencesStore.getHomeserverHistoryFlow().first().firstOrNull()
|
||||||
return lastUsedProvider?.let { createAccountProvider(it) } ?: configuredAccountProvider
|
return lastUsedProvider?.let { AccountProvider.Generic(it) } ?: configuredAccountProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun setUrl(url: String) {
|
suspend fun setUrl(url: String) {
|
||||||
setAccountProvider(createAccountProvider(url))
|
setAccountProvider(AccountProvider.Generic(url))
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun setAccountProvider(data: AccountProvider) {
|
suspend fun setAccountProvider(data: AccountProvider) {
|
||||||
lastSelectedAccountProviderUrl = data.url
|
lastSelectedAccountProvider = data
|
||||||
accountProvider.emit(data)
|
accountProvider.emit(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createAccountProvider(url: String): AccountProvider {
|
|
||||||
return AccountProvider(
|
|
||||||
url = url,
|
|
||||||
subtitle = null,
|
|
||||||
isPublic = url == AuthenticationConfig.MATRIX_ORG_URL,
|
|
||||||
isMatrixOrg = url == AuthenticationConfig.MATRIX_ORG_URL,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-16
@@ -9,27 +9,27 @@
|
|||||||
package io.element.android.features.login.impl.accountprovider
|
package io.element.android.features.login.impl.accountprovider
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.appconfig.AuthenticationConfig
|
import io.element.android.libraries.matrix.api.accountprovider.AccountProvider
|
||||||
|
|
||||||
open class AccountProviderPreviewParam : PreviewParameterProvider<AccountProvider> {
|
open class AccountProviderPreviewParam : PreviewParameterProvider<AccountProvider> {
|
||||||
override val values: Sequence<AccountProvider>
|
override val values: Sequence<AccountProvider>
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
anAccountProvider(),
|
anAccountProviderManaged(),
|
||||||
anAccountProvider().copy(subtitle = null),
|
|
||||||
anAccountProvider().copy(subtitle = null, title = "invalid"),
|
|
||||||
anAccountProvider().copy(subtitle = null, title = "Other", isPublic = false, isMatrixOrg = false),
|
|
||||||
// Add other state here
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun anAccountProvider(
|
fun anAccountProviderManaged(
|
||||||
url: String = AuthenticationConfig.MATRIX_ORG_URL,
|
serverName: String = "matrix.org",
|
||||||
subtitle: String? = "Matrix.org is an open network for secure, decentralized communication.",
|
baseUrl: String = "https://matrix-client.matrix.org",
|
||||||
isPublic: Boolean = true,
|
canUseServerName: Boolean = true,
|
||||||
isMatrixOrg: Boolean = true,
|
) = AccountProvider.Managed(
|
||||||
) = AccountProvider(
|
serverName = serverName,
|
||||||
url = url,
|
baseUrl = baseUrl,
|
||||||
subtitle = subtitle,
|
canUseServerName = canUseServerName,
|
||||||
isPublic = isPublic,
|
)
|
||||||
isMatrixOrg = isMatrixOrg,
|
|
||||||
|
fun anAccountProviderGeneric(
|
||||||
|
serverName: String = "matrix.org",
|
||||||
|
) = AccountProvider.Generic(
|
||||||
|
serverName = serverName,
|
||||||
)
|
)
|
||||||
|
|||||||
+2
-31
@@ -14,17 +14,14 @@ import androidx.compose.foundation.layout.Row
|
|||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.heightIn
|
import androidx.compose.foundation.layout.heightIn
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.semantics.Role
|
import androidx.compose.ui.semantics.Role
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||||
import io.element.android.features.login.impl.R
|
|
||||||
import io.element.android.libraries.designsystem.atomic.atoms.RoundedIconAtom
|
import io.element.android.libraries.designsystem.atomic.atoms.RoundedIconAtom
|
||||||
import io.element.android.libraries.designsystem.atomic.atoms.RoundedIconAtomSize
|
import io.element.android.libraries.designsystem.atomic.atoms.RoundedIconAtomSize
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
@@ -32,6 +29,7 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
|||||||
import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
|
import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
|
||||||
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.matrix.api.accountprovider.AccountProvider
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* https://www.figma.com/file/o9p34zmiuEpZRyvZXJZAYL/FTUE?type=design&node-id=604-60817
|
* https://www.figma.com/file/o9p34zmiuEpZRyvZXJZAYL/FTUE?type=design&node-id=604-60817
|
||||||
@@ -60,37 +58,19 @@ fun AccountProviderView(
|
|||||||
.heightIn(min = 44.dp),
|
.heightIn(min = 44.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
if (item.isMatrixOrg) {
|
|
||||||
RoundedIconAtom(
|
|
||||||
size = RoundedIconAtomSize.Medium,
|
|
||||||
resourceId = R.drawable.ic_matrix,
|
|
||||||
tint = Color.Unspecified,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
RoundedIconAtom(
|
RoundedIconAtom(
|
||||||
size = RoundedIconAtomSize.Medium,
|
size = RoundedIconAtomSize.Medium,
|
||||||
imageVector = CompoundIcons.Host(),
|
imageVector = CompoundIcons.Host(),
|
||||||
tint = ElementTheme.colors.iconPrimary,
|
tint = ElementTheme.colors.iconPrimary,
|
||||||
)
|
)
|
||||||
}
|
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(start = 16.dp)
|
.padding(start = 16.dp)
|
||||||
.weight(1f),
|
.weight(1f),
|
||||||
text = item.title,
|
text = item.friendlyServerName(),
|
||||||
style = ElementTheme.typography.fontBodyLgMedium,
|
style = ElementTheme.typography.fontBodyLgMedium,
|
||||||
color = ElementTheme.colors.textPrimary,
|
color = ElementTheme.colors.textPrimary,
|
||||||
)
|
)
|
||||||
if (item.isPublic) {
|
|
||||||
Icon(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(start = 10.dp)
|
|
||||||
.size(16.dp),
|
|
||||||
imageVector = CompoundIcons.Public(),
|
|
||||||
contentDescription = null,
|
|
||||||
tint = ElementTheme.colors.iconSecondary,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (selected) {
|
if (selected) {
|
||||||
Icon(
|
Icon(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -101,15 +81,6 @@ fun AccountProviderView(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (item.subtitle != null) {
|
|
||||||
Text(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(start = 46.dp, bottom = 12.dp, end = 26.dp),
|
|
||||||
text = item.subtitle,
|
|
||||||
style = ElementTheme.typography.fontBodyMdRegular,
|
|
||||||
color = ElementTheme.colors.textSecondary,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-3
@@ -25,8 +25,11 @@ class SaveAccountProviderToHistory(
|
|||||||
suspend operator fun invoke() {
|
suspend operator fun invoke() {
|
||||||
// Persist the provider the user actually selected, not the currently-exposed default: the latter can be
|
// Persist the provider the user actually selected, not the currently-exposed default: the latter can be
|
||||||
// reset back to the history default while the login flow is torn down across an OAuth round-trip.
|
// reset back to the history default while the login flow is torn down across an OAuth round-trip.
|
||||||
val url = accountProviderDataSource.lastSelectedAccountProviderUrl
|
val accountProvider = accountProviderDataSource.lastSelectedAccountProvider
|
||||||
?: accountProviderDataSource.flow.value.url
|
?: accountProviderDataSource.flow.value
|
||||||
appPreferencesStore.addHomeserverToHistory(url)
|
// Persist the sanitized name the user knows the provider by rather than its base url, which can be an
|
||||||
|
// internal client API url (e.g. matrix.org resolves to matrix-client.matrix.org) the user would not
|
||||||
|
// recognise, and so that the same provider is not stored twice with and without its scheme.
|
||||||
|
appPreferencesStore.addHomeserverToHistory(accountProvider.serverNameOrBaseUrl())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
package io.element.android.features.login.impl.changeserver
|
package io.element.android.features.login.impl.changeserver
|
||||||
|
|
||||||
import io.element.android.features.login.impl.accountprovider.AccountProvider
|
import io.element.android.libraries.matrix.api.accountprovider.AccountProvider
|
||||||
|
|
||||||
sealed interface ChangeServerEvent {
|
sealed interface ChangeServerEvent {
|
||||||
data class ChangeServer(val accountProvider: AccountProvider) : ChangeServerEvent
|
data class ChangeServer(val accountProvider: AccountProvider) : ChangeServerEvent
|
||||||
|
|||||||
+4
-7
@@ -14,13 +14,13 @@ import androidx.compose.runtime.mutableStateOf
|
|||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import dev.zacsweers.metro.Inject
|
import dev.zacsweers.metro.Inject
|
||||||
import io.element.android.features.login.impl.accesscontrol.DefaultAccountProviderAccessControl
|
import io.element.android.features.login.impl.accesscontrol.DefaultAccountProviderAccessControl
|
||||||
import io.element.android.features.login.impl.accountprovider.AccountProvider
|
|
||||||
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
|
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
|
||||||
import io.element.android.features.login.impl.error.ChangeServerError
|
import io.element.android.features.login.impl.error.ChangeServerError
|
||||||
import io.element.android.features.login.impl.localnetwork.LocalNetworkPermissionGate
|
import io.element.android.features.login.impl.localnetwork.LocalNetworkPermissionGate
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
||||||
|
import io.element.android.libraries.matrix.api.accountprovider.AccountProvider
|
||||||
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
||||||
import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails
|
import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ class ChangeServerPresenter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val gateState = localNetworkPermissionGate.present<AccountProvider>(
|
val gateState = localNetworkPermissionGate.present<AccountProvider>(
|
||||||
urlOf = { provider -> provider.url },
|
urlOf = { provider -> provider.serverNameOrBaseUrl() },
|
||||||
onProceed = { provider -> changeServer(provider, changeServerAction) },
|
onProceed = { provider -> changeServer(provider, changeServerAction) },
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -63,11 +63,8 @@ class ChangeServerPresenter(
|
|||||||
changeServerAction: MutableState<AsyncData<MatrixHomeServerDetails>>,
|
changeServerAction: MutableState<AsyncData<MatrixHomeServerDetails>>,
|
||||||
) {
|
) {
|
||||||
suspend {
|
suspend {
|
||||||
defaultAccountProviderAccessControl.assertIsAllowedToConnectToAccountProvider(
|
defaultAccountProviderAccessControl.assertIsAllowedToConnectToAccountProvider(data)
|
||||||
title = data.title,
|
val details = authenticationService.setHomeserver(data.serverNameOrBaseUrl()).getOrThrow()
|
||||||
accountProviderUrl = data.url,
|
|
||||||
)
|
|
||||||
val details = authenticationService.setHomeserver(data.url).getOrThrow()
|
|
||||||
if (!details.isSupported) {
|
if (!details.isSupported) {
|
||||||
throw ChangeServerError.UnsupportedServer
|
throw ChangeServerError.UnsupportedServer
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
package io.element.android.features.login.impl.screens.chooseaccountprovider
|
package io.element.android.features.login.impl.screens.chooseaccountprovider
|
||||||
|
|
||||||
import io.element.android.features.login.impl.accountprovider.AccountProvider
|
import io.element.android.libraries.matrix.api.accountprovider.AccountProvider
|
||||||
|
|
||||||
sealed interface ChooseAccountProviderEvent {
|
sealed interface ChooseAccountProviderEvent {
|
||||||
data class SelectAccountProvider(val accountProvider: AccountProvider) : ChooseAccountProviderEvent
|
data class SelectAccountProvider(val accountProvider: AccountProvider) : ChooseAccountProviderEvent
|
||||||
|
|||||||
+4
-16
@@ -14,14 +14,12 @@ import androidx.compose.runtime.mutableStateOf
|
|||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import dev.zacsweers.metro.Inject
|
import dev.zacsweers.metro.Inject
|
||||||
import io.element.android.appconfig.AuthenticationConfig
|
|
||||||
import io.element.android.features.enterprise.api.EnterpriseService
|
import io.element.android.features.enterprise.api.EnterpriseService
|
||||||
import io.element.android.features.login.impl.accountprovider.AccountProvider
|
|
||||||
import io.element.android.features.login.impl.login.LoginModeEvent
|
import io.element.android.features.login.impl.login.LoginModeEvent
|
||||||
import io.element.android.features.login.impl.login.LoginModeState
|
import io.element.android.features.login.impl.login.LoginModeState
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.core.uri.ensureProtocol
|
import io.element.android.libraries.matrix.api.accountprovider.AccountProvider
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@@ -41,7 +39,7 @@ class ChooseAccountProviderPresenter(
|
|||||||
loginModeState.eventSink(
|
loginModeState.eventSink(
|
||||||
LoginModeEvent.Submit(
|
LoginModeEvent.Submit(
|
||||||
isAccountCreation = false,
|
isAccountCreation = false,
|
||||||
homeserverUrl = provider.url,
|
homeserverUrl = provider.serverNameOrBaseUrl(),
|
||||||
resolvedHomeserverUrl = null,
|
resolvedHomeserverUrl = null,
|
||||||
loginHint = null,
|
loginHint = null,
|
||||||
)
|
)
|
||||||
@@ -59,18 +57,8 @@ class ChooseAccountProviderPresenter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val staticAccountProviderList = remember {
|
val staticAccountProviderList = remember {
|
||||||
// The list cannot contains ANY_ACCOUNT_PROVIDER ("*") and cannot be empty at this point
|
// The list cannot be empty at this point
|
||||||
enterpriseService.homeserverAllowList()
|
enterpriseService.accountProviderAllowList().toImmutableList()
|
||||||
.map { it.ensureProtocol() }
|
|
||||||
.map { url ->
|
|
||||||
AccountProvider(
|
|
||||||
url = url,
|
|
||||||
subtitle = null,
|
|
||||||
isPublic = url == AuthenticationConfig.MATRIX_ORG_URL,
|
|
||||||
isMatrixOrg = url == AuthenticationConfig.MATRIX_ORG_URL,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.toImmutableList()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ChooseAccountProviderState(
|
return ChooseAccountProviderState(
|
||||||
|
|||||||
+1
-1
@@ -8,9 +8,9 @@
|
|||||||
|
|
||||||
package io.element.android.features.login.impl.screens.chooseaccountprovider
|
package io.element.android.features.login.impl.screens.chooseaccountprovider
|
||||||
|
|
||||||
import io.element.android.features.login.impl.accountprovider.AccountProvider
|
|
||||||
import io.element.android.features.login.impl.login.LoginModeState
|
import io.element.android.features.login.impl.login.LoginModeState
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
|
import io.element.android.libraries.matrix.api.accountprovider.AccountProvider
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
||||||
data class ChooseAccountProviderState(
|
data class ChooseAccountProviderState(
|
||||||
|
|||||||
+9
-19
@@ -9,31 +9,22 @@
|
|||||||
package io.element.android.features.login.impl.screens.chooseaccountprovider
|
package io.element.android.features.login.impl.screens.chooseaccountprovider
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.features.login.impl.accountprovider.AccountProvider
|
import io.element.android.features.login.impl.accountprovider.anAccountProviderManaged
|
||||||
import io.element.android.features.login.impl.accountprovider.anAccountProvider
|
|
||||||
import io.element.android.features.login.impl.login.LoginModeState
|
import io.element.android.features.login.impl.login.LoginModeState
|
||||||
import io.element.android.features.login.impl.login.aLoginModeState
|
import io.element.android.features.login.impl.login.aLoginModeState
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
|
import io.element.android.libraries.matrix.api.accountprovider.AccountProvider
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
||||||
open class ChooseAccountProviderStatePreviewParam : PreviewParameterProvider<ChooseAccountProviderState> {
|
open class ChooseAccountProviderStatePreviewParam : PreviewParameterProvider<ChooseAccountProviderState> {
|
||||||
private val server1 = anAccountProvider(
|
private val server1 = anAccountProviderManaged(
|
||||||
url = "https://server1.io",
|
baseUrl = "https://server1.io",
|
||||||
subtitle = null,
|
|
||||||
isPublic = false,
|
|
||||||
isMatrixOrg = false,
|
|
||||||
)
|
)
|
||||||
private val server2 = anAccountProvider(
|
private val server2 = anAccountProviderManaged(
|
||||||
url = "https://server2.io",
|
baseUrl = "https://server2.io",
|
||||||
subtitle = null,
|
|
||||||
isPublic = false,
|
|
||||||
isMatrixOrg = false,
|
|
||||||
)
|
)
|
||||||
private val server3 = anAccountProvider(
|
private val server3 = anAccountProviderManaged(
|
||||||
url = "https://server3.io",
|
baseUrl = "https://server3.io",
|
||||||
subtitle = null,
|
|
||||||
isPublic = false,
|
|
||||||
isMatrixOrg = false,
|
|
||||||
)
|
)
|
||||||
override val values: Sequence<ChooseAccountProviderState>
|
override val values: Sequence<ChooseAccountProviderState>
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
@@ -61,13 +52,12 @@ open class ChooseAccountProviderStatePreviewParam : PreviewParameterProvider<Cho
|
|||||||
selectedAccountProvider = server2,
|
selectedAccountProvider = server2,
|
||||||
loginModeState = aLoginModeState(loginMode = AsyncData.Loading()),
|
loginModeState = aLoginModeState(loginMode = AsyncData.Loading()),
|
||||||
),
|
),
|
||||||
// Add other state here
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun aChooseAccountProviderState(
|
fun aChooseAccountProviderState(
|
||||||
accountProviders: List<AccountProvider> = listOf(
|
accountProviders: List<AccountProvider> = listOf(
|
||||||
anAccountProvider()
|
anAccountProviderManaged()
|
||||||
),
|
),
|
||||||
selectedAccountProvider: AccountProvider? = null,
|
selectedAccountProvider: AccountProvider? = null,
|
||||||
loginModeState: LoginModeState = aLoginModeState(),
|
loginModeState: LoginModeState = aLoginModeState(),
|
||||||
|
|||||||
+1
-9
@@ -96,16 +96,8 @@ fun ChooseAccountProviderView(
|
|||||||
)
|
)
|
||||||
|
|
||||||
state.accountProviders.forEach { item ->
|
state.accountProviders.forEach { item ->
|
||||||
val alteredItem = if (item.isMatrixOrg) {
|
|
||||||
// Set the subtitle from the resource
|
|
||||||
item.copy(
|
|
||||||
subtitle = stringResource(id = R.string.screen_change_account_provider_matrix_org_subtitle),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
item
|
|
||||||
}
|
|
||||||
AccountProviderView(
|
AccountProviderView(
|
||||||
item = alteredItem,
|
item = item,
|
||||||
selected = item == state.selectedAccountProvider,
|
selected = item == state.selectedAccountProvider,
|
||||||
onClick = {
|
onClick = {
|
||||||
state.eventSink(ChooseAccountProviderEvent.SelectAccountProvider(item))
|
state.eventSink(ChooseAccountProviderEvent.SelectAccountProvider(item))
|
||||||
|
|||||||
+13
-16
@@ -20,9 +20,7 @@ import androidx.compose.runtime.setValue
|
|||||||
import dev.zacsweers.metro.Assisted
|
import dev.zacsweers.metro.Assisted
|
||||||
import dev.zacsweers.metro.AssistedFactory
|
import dev.zacsweers.metro.AssistedFactory
|
||||||
import dev.zacsweers.metro.AssistedInject
|
import dev.zacsweers.metro.AssistedInject
|
||||||
import io.element.android.appconfig.AuthenticationConfig
|
|
||||||
import io.element.android.features.enterprise.api.EnterpriseService
|
import io.element.android.features.enterprise.api.EnterpriseService
|
||||||
import io.element.android.features.login.impl.accountprovider.AccountProvider
|
|
||||||
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
|
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
|
||||||
import io.element.android.features.login.impl.changeserver.ChangeServerEvent
|
import io.element.android.features.login.impl.changeserver.ChangeServerEvent
|
||||||
import io.element.android.features.login.impl.changeserver.ChangeServerState
|
import io.element.android.features.login.impl.changeserver.ChangeServerState
|
||||||
@@ -30,8 +28,11 @@ import io.element.android.features.login.impl.login.LoginModeEvent
|
|||||||
import io.element.android.features.login.impl.login.LoginModeState
|
import io.element.android.features.login.impl.login.LoginModeState
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.core.uri.ensureProtocol
|
import io.element.android.libraries.core.uri.ensureProtocol
|
||||||
|
import io.element.android.libraries.matrix.api.accountprovider.AccountProvider
|
||||||
|
import io.element.android.libraries.matrix.api.accountprovider.matrixOrgAccountProvider
|
||||||
import io.element.android.libraries.matrix.api.core.MatrixPatterns
|
import io.element.android.libraries.matrix.api.core.MatrixPatterns
|
||||||
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
@AssistedInject
|
@AssistedInject
|
||||||
class ConfirmAccountProviderPresenter(
|
class ConfirmAccountProviderPresenter(
|
||||||
@@ -56,23 +57,25 @@ class ConfirmAccountProviderPresenter(
|
|||||||
val accountProvider by accountProviderDataSource.flow.collectAsState()
|
val accountProvider by accountProviderDataSource.flow.collectAsState()
|
||||||
val loginModeState = loginModePresenter.present()
|
val loginModeState = loginModePresenter.present()
|
||||||
val changeServerState = changeServerPresenter.present()
|
val changeServerState = changeServerPresenter.present()
|
||||||
val homeserverHistory by appPreferencesStore.getHomeserverHistoryFlow().collectAsState(emptyList())
|
val homeserverHistory by remember {
|
||||||
|
appPreferencesStore.getHomeserverHistoryFlow().map { history ->
|
||||||
|
history.map { AccountProvider.Generic(it) }
|
||||||
|
}
|
||||||
|
}.collectAsState(emptyList())
|
||||||
|
|
||||||
// The account providers offered for autocomplete: previously-used ones first, then the
|
// The account providers offered for autocomplete: previously-used ones first, then the
|
||||||
// enterprise/MDM-configured allow-list, then matrix.org (always available, even before any sign-in).
|
// enterprise/MDM-configured allow-list, then matrix.org (always available, even before any sign-in).
|
||||||
// The "*" wildcard is a routing marker, not a real provider, so it is filtered out. Everything is
|
// Everything is rendered without the https:// scheme (added back at connection time).
|
||||||
// rendered without the https:// scheme (added back at connection time).
|
|
||||||
val autocompleteCandidates = remember(homeserverHistory) {
|
val autocompleteCandidates = remember(homeserverHistory) {
|
||||||
(homeserverHistory + enterpriseService.homeserverAllowList() + AuthenticationConfig.MATRIX_ORG_URL)
|
(homeserverHistory + enterpriseService.accountProviderAllowList() + matrixOrgAccountProvider)
|
||||||
.filter { it != EnterpriseService.ANY_ACCOUNT_PROVIDER }
|
.map { it.friendlyServerName() }
|
||||||
.map { it.withoutScheme() }
|
|
||||||
.distinct()
|
.distinct()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Editable input, seeded from the current (history-defaulted) account provider until the user edits it.
|
// Editable input, seeded from the current (history-defaulted) account provider until the user edits it.
|
||||||
// Displayed without the scheme, so the field shows e.g. "matrix.org" rather than "https://matrix.org".
|
// Displayed without the scheme, so the field shows e.g. "matrix.org" rather than "https://matrix.org".
|
||||||
var userInput by rememberSaveable { mutableStateOf<String?>(null) }
|
var userInput by rememberSaveable { mutableStateOf<String?>(null) }
|
||||||
val accountProviderInput = userInput ?: accountProvider.url.withoutScheme()
|
val accountProviderInput = userInput ?: accountProvider.friendlyServerName()
|
||||||
|
|
||||||
// Offer the first candidate that the current input is a (case-insensitive) prefix of.
|
// Offer the first candidate that the current input is a (case-insensitive) prefix of.
|
||||||
val accountProviderSuggestion = remember(accountProviderInput, autocompleteCandidates) {
|
val accountProviderSuggestion = remember(accountProviderInput, autocompleteCandidates) {
|
||||||
@@ -134,7 +137,7 @@ class ConfirmAccountProviderPresenter(
|
|||||||
val accountProviderUrl = host.ensureProtocol()
|
val accountProviderUrl = host.ensureProtocol()
|
||||||
submittedAccountProvider = accountProviderUrl
|
submittedAccountProvider = accountProviderUrl
|
||||||
changeServerState.eventSink(
|
changeServerState.eventSink(
|
||||||
ChangeServerEvent.ChangeServer(AccountProvider(url = accountProviderUrl))
|
ChangeServerEvent.ChangeServer(AccountProvider.Generic(host))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
ConfirmAccountProviderEvent.ClearError -> {
|
ConfirmAccountProviderEvent.ClearError -> {
|
||||||
@@ -154,9 +157,3 @@ class ConfirmAccountProviderPresenter(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* The host part of an account provider URL, i.e. the value with any `https://` / `http://` scheme removed.
|
|
||||||
* Returns the input unchanged when it has no scheme (e.g. `matrix.org`).
|
|
||||||
*/
|
|
||||||
private fun String.withoutScheme(): String = substringAfter("://")
|
|
||||||
|
|||||||
+1
-1
@@ -9,8 +9,8 @@
|
|||||||
package io.element.android.features.login.impl.screens.loginpassword
|
package io.element.android.features.login.impl.screens.loginpassword
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import io.element.android.features.login.impl.accountprovider.AccountProvider
|
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
|
import io.element.android.libraries.matrix.api.accountprovider.AccountProvider
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -9,9 +9,9 @@
|
|||||||
package io.element.android.features.login.impl.screens.loginpassword
|
package io.element.android.features.login.impl.screens.loginpassword
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.features.login.impl.accountprovider.AccountProvider
|
import io.element.android.features.login.impl.accountprovider.anAccountProviderManaged
|
||||||
import io.element.android.features.login.impl.accountprovider.anAccountProvider
|
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
|
import io.element.android.libraries.matrix.api.accountprovider.AccountProvider
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
|
|
||||||
open class LoginPasswordStatePreviewParam : PreviewParameterProvider<LoginPasswordState> {
|
open class LoginPasswordStatePreviewParam : PreviewParameterProvider<LoginPasswordState> {
|
||||||
@@ -26,7 +26,7 @@ open class LoginPasswordStatePreviewParam : PreviewParameterProvider<LoginPasswo
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun aLoginPasswordState(
|
fun aLoginPasswordState(
|
||||||
accountProvider: AccountProvider = anAccountProvider(),
|
accountProvider: AccountProvider = anAccountProviderManaged(),
|
||||||
formState: LoginFormState = LoginFormState.Default,
|
formState: LoginFormState = LoginFormState.Default,
|
||||||
loginAction: AsyncData<SessionId> = AsyncData.Uninitialized,
|
loginAction: AsyncData<SessionId> = AsyncData.Uninitialized,
|
||||||
eventSink: (LoginPasswordEvent) -> Unit = {},
|
eventSink: (LoginPasswordEvent) -> Unit = {},
|
||||||
|
|||||||
+1
-1
@@ -131,7 +131,7 @@ fun LoginPasswordView(
|
|||||||
iconStyle = BigIcon.Style.Default(CompoundIcons.UserProfileSolid()),
|
iconStyle = BigIcon.Style.Default(CompoundIcons.UserProfileSolid()),
|
||||||
title = stringResource(
|
title = stringResource(
|
||||||
id = R.string.screen_account_provider_signin_title,
|
id = R.string.screen_account_provider_signin_title,
|
||||||
state.accountProvider.title
|
state.accountProvider.friendlyServerName()
|
||||||
),
|
),
|
||||||
subTitle = stringResource(id = R.string.screen_login_subtitle)
|
subTitle = stringResource(id = R.string.screen_login_subtitle)
|
||||||
)
|
)
|
||||||
|
|||||||
+3
-1
@@ -8,9 +8,11 @@
|
|||||||
|
|
||||||
package io.element.android.features.login.impl.screens.onboarding
|
package io.element.android.features.login.impl.screens.onboarding
|
||||||
|
|
||||||
|
import io.element.android.libraries.matrix.api.accountprovider.AccountProvider
|
||||||
|
|
||||||
sealed interface OnBoardingEvent {
|
sealed interface OnBoardingEvent {
|
||||||
data class OnSignIn(
|
data class OnSignIn(
|
||||||
val defaultAccountProvider: String
|
val defaultAccountProvider: AccountProvider
|
||||||
) : OnBoardingEvent
|
) : OnBoardingEvent
|
||||||
|
|
||||||
data object OnVersionClick : OnBoardingEvent
|
data object OnVersionClick : OnBoardingEvent
|
||||||
|
|||||||
+24
-15
@@ -22,7 +22,6 @@ import dev.zacsweers.metro.AssistedFactory
|
|||||||
import dev.zacsweers.metro.AssistedInject
|
import dev.zacsweers.metro.AssistedInject
|
||||||
import io.element.android.appconfig.OnBoardingConfig
|
import io.element.android.appconfig.OnBoardingConfig
|
||||||
import io.element.android.features.enterprise.api.EnterpriseService
|
import io.element.android.features.enterprise.api.EnterpriseService
|
||||||
import io.element.android.features.enterprise.api.canConnectToAnyHomeserver
|
|
||||||
import io.element.android.features.login.impl.accesscontrol.DefaultAccountProviderAccessControl
|
import io.element.android.features.login.impl.accesscontrol.DefaultAccountProviderAccessControl
|
||||||
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
|
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
|
||||||
import io.element.android.features.login.impl.login.LoginModeEvent
|
import io.element.android.features.login.impl.login.LoginModeEvent
|
||||||
@@ -31,9 +30,11 @@ import io.element.android.features.rageshake.api.RageshakeFeatureAvailability
|
|||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.core.meta.BuildMeta
|
import io.element.android.libraries.core.meta.BuildMeta
|
||||||
import io.element.android.libraries.core.meta.BuildType
|
import io.element.android.libraries.core.meta.BuildType
|
||||||
|
import io.element.android.libraries.matrix.api.accountprovider.AccountProvider
|
||||||
import io.element.android.libraries.sessionstorage.api.SessionStore
|
import io.element.android.libraries.sessionstorage.api.SessionStore
|
||||||
import io.element.android.libraries.ui.utils.MultipleTapToUnlock
|
import io.element.android.libraries.ui.utils.MultipleTapToUnlock
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
@AssistedInject
|
@AssistedInject
|
||||||
class OnBoardingPresenter(
|
class OnBoardingPresenter(
|
||||||
@@ -59,24 +60,32 @@ class OnBoardingPresenter(
|
|||||||
@Composable
|
@Composable
|
||||||
override fun present(): OnBoardingState {
|
override fun present(): OnBoardingState {
|
||||||
val localCoroutineScope = rememberCoroutineScope()
|
val localCoroutineScope = rememberCoroutineScope()
|
||||||
val forcedAccountProvider = remember {
|
val canConnectToAnyAccountProvider = remember {
|
||||||
// If homeserverAllowList() returns a singleton list, this is the default account provider.
|
enterpriseService.canConnectToAnyAccountProvider()
|
||||||
// In this case, the user can sign in using this homeserver, or use QrCode login
|
}
|
||||||
enterpriseService.homeserverAllowList().singleOrNull()
|
val forcedAccountProvider = remember {
|
||||||
|
// If accountProviderAllowList() returns a singleton list, and the user is not free to use another
|
||||||
|
// account provider, this is the default account provider.
|
||||||
|
// In this case, the user can sign in using this homeserver, or use QrCode login
|
||||||
|
if (canConnectToAnyAccountProvider) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
enterpriseService.accountProviderAllowList().singleOrNull()
|
||||||
}
|
}
|
||||||
val canConnectToAnyHomeserver = remember {
|
|
||||||
enterpriseService.canConnectToAnyHomeserver()
|
|
||||||
}
|
}
|
||||||
val mustChooseAccountProvider = remember {
|
val mustChooseAccountProvider = remember {
|
||||||
!canConnectToAnyHomeserver && enterpriseService.homeserverAllowList().size > 1
|
!canConnectToAnyAccountProvider && enterpriseService.accountProviderAllowList().size > 1
|
||||||
}
|
}
|
||||||
val linkAccountProvider by produceState<String?>(initialValue = null) {
|
val linkAccountProvider by produceState<AccountProvider?>(initialValue = null) {
|
||||||
// Account provider from the link, if allowed by the enterprise service
|
// Account provider from the link, if allowed by the enterprise service
|
||||||
value = params.accountProvider?.takeIf {
|
value = params.accountProvider
|
||||||
|
?.let { AccountProvider.Generic(it) }
|
||||||
|
?.takeIf {
|
||||||
try {
|
try {
|
||||||
defaultAccountProviderAccessControl.assertIsAllowedToConnectToAccountProvider(it, it)
|
defaultAccountProviderAccessControl.assertIsAllowedToConnectToAccountProvider(it)
|
||||||
true
|
true
|
||||||
} catch (_: Exception) {
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e, "Failed to check account provider from link, assuming not allowed")
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -105,11 +114,11 @@ class OnBoardingPresenter(
|
|||||||
when (event) {
|
when (event) {
|
||||||
is OnBoardingEvent.OnSignIn -> localCoroutineScope.launch {
|
is OnBoardingEvent.OnSignIn -> localCoroutineScope.launch {
|
||||||
// Ensure that the current account provider is set
|
// Ensure that the current account provider is set
|
||||||
accountProviderDataSource.setUrl(event.defaultAccountProvider)
|
accountProviderDataSource.setAccountProvider(event.defaultAccountProvider)
|
||||||
loginModeState.eventSink(
|
loginModeState.eventSink(
|
||||||
LoginModeEvent.Submit(
|
LoginModeEvent.Submit(
|
||||||
isAccountCreation = false,
|
isAccountCreation = false,
|
||||||
homeserverUrl = event.defaultAccountProvider,
|
homeserverUrl = event.defaultAccountProvider.serverNameOrBaseUrl(),
|
||||||
resolvedHomeserverUrl = null,
|
resolvedHomeserverUrl = null,
|
||||||
loginHint = params.loginHint?.takeIf { forcedAccountProvider == null },
|
loginHint = params.loginHint?.takeIf { forcedAccountProvider == null },
|
||||||
)
|
)
|
||||||
@@ -134,7 +143,7 @@ class OnBoardingPresenter(
|
|||||||
defaultAccountProvider = defaultAccountProvider,
|
defaultAccountProvider = defaultAccountProvider,
|
||||||
mustChooseAccountProvider = mustChooseAccountProvider,
|
mustChooseAccountProvider = mustChooseAccountProvider,
|
||||||
canLoginWithQrCode = canLoginWithQrCode,
|
canLoginWithQrCode = canLoginWithQrCode,
|
||||||
canCreateAccount = defaultAccountProvider == null && canConnectToAnyHomeserver && OnBoardingConfig.CAN_CREATE_ACCOUNT,
|
canCreateAccount = defaultAccountProvider == null && canConnectToAnyAccountProvider && OnBoardingConfig.CAN_CREATE_ACCOUNT,
|
||||||
canReportBug = canReportBug && showReportBug,
|
canReportBug = canReportBug && showReportBug,
|
||||||
loginModeState = loginModeState,
|
loginModeState = loginModeState,
|
||||||
version = buildMeta.versionName,
|
version = buildMeta.versionName,
|
||||||
|
|||||||
+2
-1
@@ -11,13 +11,14 @@ package io.element.android.features.login.impl.screens.onboarding
|
|||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import io.element.android.features.login.impl.login.LoginModeState
|
import io.element.android.features.login.impl.login.LoginModeState
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
|
import io.element.android.libraries.matrix.api.accountprovider.AccountProvider
|
||||||
|
|
||||||
data class OnBoardingState(
|
data class OnBoardingState(
|
||||||
val isAddingAccount: Boolean,
|
val isAddingAccount: Boolean,
|
||||||
val showBackButton: Boolean,
|
val showBackButton: Boolean,
|
||||||
val showDeveloperSettings: Boolean,
|
val showDeveloperSettings: Boolean,
|
||||||
val productionApplicationName: String,
|
val productionApplicationName: String,
|
||||||
val defaultAccountProvider: String?,
|
val defaultAccountProvider: AccountProvider?,
|
||||||
val mustChooseAccountProvider: Boolean,
|
val mustChooseAccountProvider: Boolean,
|
||||||
val canLoginWithQrCode: Boolean,
|
val canLoginWithQrCode: Boolean,
|
||||||
val canCreateAccount: Boolean,
|
val canCreateAccount: Boolean,
|
||||||
|
|||||||
+7
-2
@@ -13,6 +13,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
|||||||
import io.element.android.features.login.impl.login.LoginModeState
|
import io.element.android.features.login.impl.login.LoginModeState
|
||||||
import io.element.android.features.login.impl.login.aLoginModeState
|
import io.element.android.features.login.impl.login.aLoginModeState
|
||||||
import io.element.android.libraries.designsystem.R
|
import io.element.android.libraries.designsystem.R
|
||||||
|
import io.element.android.libraries.matrix.api.accountprovider.AccountProvider
|
||||||
|
|
||||||
open class OnBoardingStatePreviewParam : PreviewParameterProvider<OnBoardingState> {
|
open class OnBoardingStatePreviewParam : PreviewParameterProvider<OnBoardingState> {
|
||||||
override val values: Sequence<OnBoardingState>
|
override val values: Sequence<OnBoardingState>
|
||||||
@@ -22,7 +23,11 @@ open class OnBoardingStatePreviewParam : PreviewParameterProvider<OnBoardingStat
|
|||||||
anOnBoardingState(canCreateAccount = true),
|
anOnBoardingState(canCreateAccount = true),
|
||||||
anOnBoardingState(canLoginWithQrCode = true, canCreateAccount = true),
|
anOnBoardingState(canLoginWithQrCode = true, canCreateAccount = true),
|
||||||
anOnBoardingState(canLoginWithQrCode = true, canCreateAccount = true, canReportBug = true),
|
anOnBoardingState(canLoginWithQrCode = true, canCreateAccount = true, canReportBug = true),
|
||||||
anOnBoardingState(defaultAccountProvider = "element.io", canCreateAccount = false, canReportBug = true),
|
anOnBoardingState(
|
||||||
|
defaultAccountProvider = AccountProvider.Generic("element.io"),
|
||||||
|
canCreateAccount = false,
|
||||||
|
canReportBug = true
|
||||||
|
),
|
||||||
anOnBoardingState(customLogoResId = R.drawable.sample_background),
|
anOnBoardingState(customLogoResId = R.drawable.sample_background),
|
||||||
anOnBoardingState(
|
anOnBoardingState(
|
||||||
isAddingAccount = true,
|
isAddingAccount = true,
|
||||||
@@ -41,7 +46,7 @@ fun anOnBoardingState(
|
|||||||
showBackButton: Boolean = false,
|
showBackButton: Boolean = false,
|
||||||
showDeveloperSettings: Boolean = false,
|
showDeveloperSettings: Boolean = false,
|
||||||
productionApplicationName: String = "Element",
|
productionApplicationName: String = "Element",
|
||||||
defaultAccountProvider: String? = null,
|
defaultAccountProvider: AccountProvider? = null,
|
||||||
mustChooseAccountProvider: Boolean = false,
|
mustChooseAccountProvider: Boolean = false,
|
||||||
canLoginWithQrCode: Boolean = false,
|
canLoginWithQrCode: Boolean = false,
|
||||||
canCreateAccount: Boolean = false,
|
canCreateAccount: Boolean = false,
|
||||||
|
|||||||
+1
-1
@@ -303,7 +303,7 @@ private fun OnBoardingButtons(
|
|||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Button(
|
Button(
|
||||||
text = stringResource(id = R.string.screen_onboarding_sign_in_to, defaultAccountProvider),
|
text = stringResource(id = R.string.screen_onboarding_sign_in_to, defaultAccountProvider.friendlyServerName()),
|
||||||
showProgress = isLoading,
|
showProgress = isLoading,
|
||||||
onClick = {
|
onClick = {
|
||||||
state.eventSink(OnBoardingEvent.OnSignIn(defaultAccountProvider))
|
state.eventSink(OnBoardingEvent.OnSignIn(defaultAccountProvider))
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="16dp"
|
|
||||||
android:height="16dp"
|
|
||||||
android:viewportWidth="16"
|
|
||||||
android:viewportHeight="16">
|
|
||||||
<path
|
|
||||||
android:pathData="M8,0L8,0A8,8 0,0 1,16 8L16,8A8,8 0,0 1,8 16L8,16A8,8 0,0 1,0 8L0,8A8,8 0,0 1,8 0z"
|
|
||||||
android:fillColor="#101317"/>
|
|
||||||
<path
|
|
||||||
android:pathData="M5.355,5.141V5.85H5.375C5.564,5.579 5.793,5.37 6.059,5.223C6.324,5.073 6.632,5 6.976,5C7.307,5 7.609,5.065 7.883,5.192C8.157,5.319 8.363,5.548 8.507,5.87C8.662,5.641 8.874,5.438 9.139,5.263C9.405,5.088 9.721,5 10.085,5C10.362,5 10.619,5.034 10.856,5.102C11.094,5.169 11.294,5.277 11.464,5.426C11.633,5.576 11.763,5.768 11.859,6.008C11.952,6.248 12,6.536 12,6.875V10.38H10.563V7.412C10.563,7.236 10.557,7.07 10.543,6.915C10.529,6.759 10.492,6.624 10.433,6.511C10.371,6.395 10.283,6.305 10.165,6.237C10.046,6.169 9.885,6.135 9.684,6.135C9.481,6.135 9.317,6.175 9.193,6.251C9.069,6.33 8.97,6.429 8.899,6.556C8.829,6.68 8.781,6.821 8.758,6.982C8.736,7.14 8.722,7.301 8.722,7.462V10.38H7.284V7.443C7.284,7.287 7.281,7.135 7.273,6.982C7.267,6.83 7.236,6.691 7.185,6.562C7.134,6.435 7.05,6.33 6.931,6.254C6.813,6.178 6.64,6.138 6.409,6.138C6.341,6.138 6.251,6.152 6.14,6.183C6.03,6.214 5.92,6.271 5.816,6.355C5.711,6.44 5.621,6.562 5.547,6.72C5.474,6.878 5.437,7.087 5.437,7.344V10.382H4V5.141H5.355Z"
|
|
||||||
android:fillColor="#EBEEF2"/>
|
|
||||||
</vector>
|
|
||||||
+43
-47
@@ -11,19 +11,30 @@ package io.element.android.features.login.impl.accesscontrol
|
|||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.features.enterprise.test.FakeEnterpriseService
|
import io.element.android.features.enterprise.test.FakeEnterpriseService
|
||||||
import io.element.android.features.login.impl.changeserver.AccountProviderAccessException
|
import io.element.android.features.login.impl.changeserver.AccountProviderAccessException
|
||||||
|
import io.element.android.libraries.matrix.api.accountprovider.AccountProvider
|
||||||
import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER
|
import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER
|
||||||
import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER_2
|
import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER_2
|
||||||
import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER_URL
|
import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER_URL
|
||||||
|
import io.element.android.libraries.matrix.test.accountprovider.anAccountProviderManaged
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Assert.assertThrows
|
import org.junit.Assert.assertThrows
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class DefaultAccountProviderAccessControlTest {
|
class DefaultAccountProviderAccessControlTest {
|
||||||
|
/**
|
||||||
|
* The account provider the assertions are run against: its server name is the title reported by the exceptions,
|
||||||
|
* its base url is what the enterprise service is queried with.
|
||||||
|
*/
|
||||||
|
private fun anAccountProviderUnderTest() = anAccountProviderManaged(
|
||||||
|
serverName = AN_ACCOUNT_PROVIDER,
|
||||||
|
baseUrl = AN_ACCOUNT_PROVIDER_URL,
|
||||||
|
)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `foss build should not allow using account provider that enforce enterprise build`() {
|
fun `foss build should not allow using account provider that enforce enterprise build`() {
|
||||||
val accessControl = createDefaultAccountProviderAccessControl(
|
val accessControl = createDefaultAccountProviderAccessControl(
|
||||||
isEnterpriseBuild = false,
|
isEnterpriseBuild = false,
|
||||||
isAllowedToConnectToHomeserver = true,
|
isAllowedToConnectToAccountProvider = true,
|
||||||
enterpriseService = FakeEnterpriseService(isElementProEnforcedResult = { true }),
|
enterpriseService = FakeEnterpriseService(isElementProEnforcedResult = { true }),
|
||||||
)
|
)
|
||||||
accessControl.expectNeedElementProException()
|
accessControl.expectNeedElementProException()
|
||||||
@@ -34,7 +45,7 @@ class DefaultAccountProviderAccessControlTest {
|
|||||||
val accessControl = createDefaultAccountProviderAccessControl(
|
val accessControl = createDefaultAccountProviderAccessControl(
|
||||||
isEnterpriseBuild = false,
|
isEnterpriseBuild = false,
|
||||||
// false here.
|
// false here.
|
||||||
isAllowedToConnectToHomeserver = false,
|
isAllowedToConnectToAccountProvider = false,
|
||||||
enterpriseService = FakeEnterpriseService(isElementProEnforcedResult = { true }),
|
enterpriseService = FakeEnterpriseService(isElementProEnforcedResult = { true }),
|
||||||
)
|
)
|
||||||
accessControl.expectNeedElementProException()
|
accessControl.expectNeedElementProException()
|
||||||
@@ -44,9 +55,9 @@ class DefaultAccountProviderAccessControlTest {
|
|||||||
fun `foss build should allow using account provider that does not enforce enterprise build`() = runTest {
|
fun `foss build should allow using account provider that does not enforce enterprise build`() = runTest {
|
||||||
val accessControl = createDefaultAccountProviderAccessControl(
|
val accessControl = createDefaultAccountProviderAccessControl(
|
||||||
isEnterpriseBuild = false,
|
isEnterpriseBuild = false,
|
||||||
isAllowedToConnectToHomeserver = true,
|
isAllowedToConnectToAccountProvider = true,
|
||||||
enterpriseService = FakeEnterpriseService(
|
enterpriseService = FakeEnterpriseService(
|
||||||
isAllowedToConnectToHomeserverResult = { true },
|
isAllowedToConnectToAccountProviderResult = { true },
|
||||||
isElementProEnforcedResult = { false }
|
isElementProEnforcedResult = { false }
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -57,7 +68,7 @@ class DefaultAccountProviderAccessControlTest {
|
|||||||
fun `foss build should allow using account provider twith missing key in wellknown`() = runTest {
|
fun `foss build should allow using account provider twith missing key in wellknown`() = runTest {
|
||||||
val accessControl = createDefaultAccountProviderAccessControl(
|
val accessControl = createDefaultAccountProviderAccessControl(
|
||||||
isEnterpriseBuild = false,
|
isEnterpriseBuild = false,
|
||||||
isAllowedToConnectToHomeserver = true,
|
isAllowedToConnectToAccountProvider = true,
|
||||||
)
|
)
|
||||||
accessControl.expectAllowed()
|
accessControl.expectAllowed()
|
||||||
}
|
}
|
||||||
@@ -66,7 +77,7 @@ class DefaultAccountProviderAccessControlTest {
|
|||||||
fun `foss build should allow using account provider with missing wellknown`() = runTest {
|
fun `foss build should allow using account provider with missing wellknown`() = runTest {
|
||||||
val accessControl = createDefaultAccountProviderAccessControl(
|
val accessControl = createDefaultAccountProviderAccessControl(
|
||||||
isEnterpriseBuild = false,
|
isEnterpriseBuild = false,
|
||||||
isAllowedToConnectToHomeserver = true,
|
isAllowedToConnectToAccountProvider = true,
|
||||||
)
|
)
|
||||||
accessControl.expectAllowed()
|
accessControl.expectAllowed()
|
||||||
}
|
}
|
||||||
@@ -75,12 +86,12 @@ class DefaultAccountProviderAccessControlTest {
|
|||||||
fun `foss build should not allow using account provider that do not enforce enterprise build but is not allowed`() {
|
fun `foss build should not allow using account provider that do not enforce enterprise build but is not allowed`() {
|
||||||
val accessControl = createDefaultAccountProviderAccessControl(
|
val accessControl = createDefaultAccountProviderAccessControl(
|
||||||
isEnterpriseBuild = false,
|
isEnterpriseBuild = false,
|
||||||
isAllowedToConnectToHomeserver = false,
|
isAllowedToConnectToAccountProvider = false,
|
||||||
allowedAccountProviders = listOf(AN_ACCOUNT_PROVIDER_2),
|
allowedAccountProviders = listOf(anAccountProviderManaged(serverName = AN_ACCOUNT_PROVIDER_2)),
|
||||||
enterpriseService = FakeEnterpriseService(
|
enterpriseService = FakeEnterpriseService(
|
||||||
isAllowedToConnectToHomeserverResult = { false },
|
isAllowedToConnectToAccountProviderResult = { false },
|
||||||
isElementProEnforcedResult = { false },
|
isElementProEnforcedResult = { false },
|
||||||
defaultHomeserverListResult = { listOf(AN_ACCOUNT_PROVIDER_2) },
|
accountProviderAllowListResult = { listOf(anAccountProviderManaged(serverName = AN_ACCOUNT_PROVIDER_2)) },
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
accessControl.expectUnauthorizedAccountProviderException()
|
accessControl.expectUnauthorizedAccountProviderException()
|
||||||
@@ -90,9 +101,9 @@ class DefaultAccountProviderAccessControlTest {
|
|||||||
fun `enterprise build should allow using account provider that enforce enterprise build`() = runTest {
|
fun `enterprise build should allow using account provider that enforce enterprise build`() = runTest {
|
||||||
val accessControl = createDefaultAccountProviderAccessControl(
|
val accessControl = createDefaultAccountProviderAccessControl(
|
||||||
isEnterpriseBuild = true,
|
isEnterpriseBuild = true,
|
||||||
isAllowedToConnectToHomeserver = true,
|
isAllowedToConnectToAccountProvider = true,
|
||||||
enterpriseService = FakeEnterpriseService(
|
enterpriseService = FakeEnterpriseService(
|
||||||
isAllowedToConnectToHomeserverResult = { true },
|
isAllowedToConnectToAccountProviderResult = { true },
|
||||||
isElementProEnforcedResult = { true },
|
isElementProEnforcedResult = { true },
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -103,9 +114,9 @@ class DefaultAccountProviderAccessControlTest {
|
|||||||
fun `enterprise build should allow using account provider that do not enforce enterprise build`() = runTest {
|
fun `enterprise build should allow using account provider that do not enforce enterprise build`() = runTest {
|
||||||
val accessControl = createDefaultAccountProviderAccessControl(
|
val accessControl = createDefaultAccountProviderAccessControl(
|
||||||
isEnterpriseBuild = true,
|
isEnterpriseBuild = true,
|
||||||
isAllowedToConnectToHomeserver = true,
|
isAllowedToConnectToAccountProvider = true,
|
||||||
enterpriseService = FakeEnterpriseService(
|
enterpriseService = FakeEnterpriseService(
|
||||||
isAllowedToConnectToHomeserverResult = { true },
|
isAllowedToConnectToAccountProviderResult = { true },
|
||||||
isElementProEnforcedResult = { false },
|
isElementProEnforcedResult = { false },
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -116,12 +127,12 @@ class DefaultAccountProviderAccessControlTest {
|
|||||||
fun `enterprise build should not allow using account provider that enforce enterprise build but is not allowed`() = runTest {
|
fun `enterprise build should not allow using account provider that enforce enterprise build but is not allowed`() = runTest {
|
||||||
val accessControl = createDefaultAccountProviderAccessControl(
|
val accessControl = createDefaultAccountProviderAccessControl(
|
||||||
isEnterpriseBuild = true,
|
isEnterpriseBuild = true,
|
||||||
isAllowedToConnectToHomeserver = false,
|
isAllowedToConnectToAccountProvider = false,
|
||||||
allowedAccountProviders = listOf(AN_ACCOUNT_PROVIDER_2),
|
allowedAccountProviders = listOf(anAccountProviderManaged(serverName = AN_ACCOUNT_PROVIDER_2)),
|
||||||
enterpriseService = FakeEnterpriseService(
|
enterpriseService = FakeEnterpriseService(
|
||||||
isAllowedToConnectToHomeserverResult = { false },
|
isAllowedToConnectToAccountProviderResult = { false },
|
||||||
isElementProEnforcedResult = { true },
|
isElementProEnforcedResult = { true },
|
||||||
defaultHomeserverListResult = { listOf(AN_ACCOUNT_PROVIDER_2) },
|
accountProviderAllowListResult = { listOf(anAccountProviderManaged(serverName = AN_ACCOUNT_PROVIDER_2)) },
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
accessControl.expectUnauthorizedAccountProviderException()
|
accessControl.expectUnauthorizedAccountProviderException()
|
||||||
@@ -131,12 +142,12 @@ class DefaultAccountProviderAccessControlTest {
|
|||||||
fun `enterprise build should not allow using account provider that do not enforce enterprise build but is not allowed`() = runTest {
|
fun `enterprise build should not allow using account provider that do not enforce enterprise build but is not allowed`() = runTest {
|
||||||
val accessControl = createDefaultAccountProviderAccessControl(
|
val accessControl = createDefaultAccountProviderAccessControl(
|
||||||
isEnterpriseBuild = true,
|
isEnterpriseBuild = true,
|
||||||
isAllowedToConnectToHomeserver = false,
|
isAllowedToConnectToAccountProvider = false,
|
||||||
allowedAccountProviders = listOf(AN_ACCOUNT_PROVIDER_2),
|
allowedAccountProviders = listOf(anAccountProviderManaged(serverName = AN_ACCOUNT_PROVIDER_2)),
|
||||||
enterpriseService = FakeEnterpriseService(
|
enterpriseService = FakeEnterpriseService(
|
||||||
isAllowedToConnectToHomeserverResult = { false },
|
isAllowedToConnectToAccountProviderResult = { false },
|
||||||
isElementProEnforcedResult = { false },
|
isElementProEnforcedResult = { false },
|
||||||
defaultHomeserverListResult = { listOf(AN_ACCOUNT_PROVIDER_2) },
|
accountProviderAllowListResult = { listOf(anAccountProviderManaged(serverName = AN_ACCOUNT_PROVIDER_2)) },
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
accessControl.expectUnauthorizedAccountProviderException()
|
accessControl.expectUnauthorizedAccountProviderException()
|
||||||
@@ -144,11 +155,11 @@ class DefaultAccountProviderAccessControlTest {
|
|||||||
|
|
||||||
private fun createDefaultAccountProviderAccessControl(
|
private fun createDefaultAccountProviderAccessControl(
|
||||||
isEnterpriseBuild: Boolean = false,
|
isEnterpriseBuild: Boolean = false,
|
||||||
isAllowedToConnectToHomeserver: Boolean = false,
|
isAllowedToConnectToAccountProvider: Boolean = false,
|
||||||
allowedAccountProviders: List<String> = emptyList(),
|
allowedAccountProviders: List<AccountProvider> = emptyList(),
|
||||||
enterpriseService: FakeEnterpriseService = FakeEnterpriseService(
|
enterpriseService: FakeEnterpriseService = FakeEnterpriseService(
|
||||||
isAllowedToConnectToHomeserverResult = { isAllowedToConnectToHomeserver },
|
isAllowedToConnectToAccountProviderResult = { isAllowedToConnectToAccountProvider },
|
||||||
defaultHomeserverListResult = { allowedAccountProviders },
|
accountProviderAllowListResult = { allowedAccountProviders },
|
||||||
isElementProEnforcedResult = { false },
|
isElementProEnforcedResult = { false },
|
||||||
)
|
)
|
||||||
) = DefaultAccountProviderAccessControl(
|
) = DefaultAccountProviderAccessControl(
|
||||||
@@ -159,19 +170,14 @@ class DefaultAccountProviderAccessControlTest {
|
|||||||
private fun DefaultAccountProviderAccessControl.expectNeedElementProException() {
|
private fun DefaultAccountProviderAccessControl.expectNeedElementProException() {
|
||||||
val exception = assertThrows(AccountProviderAccessException.NeedElementProException::class.java) {
|
val exception = assertThrows(AccountProviderAccessException.NeedElementProException::class.java) {
|
||||||
runTest {
|
runTest {
|
||||||
assertIsAllowedToConnectToAccountProvider(
|
assertIsAllowedToConnectToAccountProvider(anAccountProviderUnderTest())
|
||||||
title = AN_ACCOUNT_PROVIDER,
|
|
||||||
accountProviderUrl = AN_ACCOUNT_PROVIDER_URL,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assertThat(exception.unauthorisedAccountProviderTitle).isEqualTo(AN_ACCOUNT_PROVIDER)
|
assertThat(exception.unauthorisedAccountProviderTitle).isEqualTo(AN_ACCOUNT_PROVIDER)
|
||||||
assertThat(exception.applicationId).isEqualTo("io.element.enterprise")
|
assertThat(exception.applicationId).isEqualTo("io.element.enterprise")
|
||||||
runTest {
|
runTest {
|
||||||
assertThat(
|
assertThat(
|
||||||
isAllowedToConnectToAccountProvider(
|
isAllowedToConnectToAccountProvider(anAccountProviderUnderTest())
|
||||||
accountProviderUrl = AN_ACCOUNT_PROVIDER_URL,
|
|
||||||
)
|
|
||||||
).isFalse()
|
).isFalse()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -179,34 +185,24 @@ class DefaultAccountProviderAccessControlTest {
|
|||||||
private fun DefaultAccountProviderAccessControl.expectUnauthorizedAccountProviderException() {
|
private fun DefaultAccountProviderAccessControl.expectUnauthorizedAccountProviderException() {
|
||||||
val exception = assertThrows(AccountProviderAccessException.UnauthorizedAccountProviderException::class.java) {
|
val exception = assertThrows(AccountProviderAccessException.UnauthorizedAccountProviderException::class.java) {
|
||||||
runTest {
|
runTest {
|
||||||
assertIsAllowedToConnectToAccountProvider(
|
assertIsAllowedToConnectToAccountProvider(anAccountProviderUnderTest())
|
||||||
title = AN_ACCOUNT_PROVIDER,
|
|
||||||
accountProviderUrl = AN_ACCOUNT_PROVIDER_URL,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assertThat(exception.unauthorisedAccountProviderTitle).isEqualTo(AN_ACCOUNT_PROVIDER)
|
assertThat(exception.unauthorisedAccountProviderTitle).isEqualTo(AN_ACCOUNT_PROVIDER)
|
||||||
assertThat(exception.authorisedAccountProviderTitles).containsExactly(AN_ACCOUNT_PROVIDER_2)
|
assertThat(exception.authorisedAccountProviderTitles).containsExactly(AN_ACCOUNT_PROVIDER_2)
|
||||||
runTest {
|
runTest {
|
||||||
assertThat(
|
assertThat(
|
||||||
isAllowedToConnectToAccountProvider(
|
isAllowedToConnectToAccountProvider(anAccountProviderUnderTest())
|
||||||
accountProviderUrl = AN_ACCOUNT_PROVIDER_URL,
|
|
||||||
)
|
|
||||||
).isFalse()
|
).isFalse()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun DefaultAccountProviderAccessControl.expectAllowed() {
|
private suspend fun DefaultAccountProviderAccessControl.expectAllowed() {
|
||||||
// If no exception is thrown, the test passes
|
// If no exception is thrown, the test passes
|
||||||
assertIsAllowedToConnectToAccountProvider(
|
assertIsAllowedToConnectToAccountProvider(anAccountProviderUnderTest())
|
||||||
title = AN_ACCOUNT_PROVIDER,
|
|
||||||
accountProviderUrl = AN_ACCOUNT_PROVIDER_URL,
|
|
||||||
)
|
|
||||||
runTest {
|
runTest {
|
||||||
assertThat(
|
assertThat(
|
||||||
isAllowedToConnectToAccountProvider(
|
isAllowedToConnectToAccountProvider(anAccountProviderUnderTest())
|
||||||
accountProviderUrl = AN_ACCOUNT_PROVIDER_URL,
|
|
||||||
)
|
|
||||||
).isTrue()
|
).isTrue()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+51
-80
@@ -10,9 +10,10 @@ package io.element.android.features.login.impl.accountprovider
|
|||||||
|
|
||||||
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.appconfig.AuthenticationConfig
|
|
||||||
import io.element.android.features.enterprise.api.EnterpriseService
|
|
||||||
import io.element.android.features.enterprise.test.FakeEnterpriseService
|
import io.element.android.features.enterprise.test.FakeEnterpriseService
|
||||||
|
import io.element.android.libraries.matrix.api.accountprovider.AccountProvider
|
||||||
|
import io.element.android.libraries.matrix.api.accountprovider.matrixOrgAccountProvider
|
||||||
|
import io.element.android.libraries.matrix.test.accountprovider.anAccountProviderManaged
|
||||||
import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore
|
import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore
|
||||||
import io.element.android.tests.testutils.WarmUpRule
|
import io.element.android.tests.testutils.WarmUpRule
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
@@ -24,61 +25,24 @@ class AccountProviderDataSourceTest {
|
|||||||
val warmUpRule = WarmUpRule()
|
val warmUpRule = WarmUpRule()
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - initial state`() = runTest {
|
fun `present - initial state - matrix org is the default when nothing is configured`() = runTest {
|
||||||
val sut = anAccountProviderDataSource()
|
val sut = anAccountProviderDataSource()
|
||||||
sut.flow.test {
|
sut.flow.test {
|
||||||
val initialState = awaitItem()
|
assertThat(awaitItem()).isEqualTo(matrixOrgAccountProvider)
|
||||||
assertThat(initialState).isEqualTo(
|
|
||||||
AccountProvider(
|
|
||||||
url = AuthenticationConfig.MATRIX_ORG_URL,
|
|
||||||
title = "matrix.org",
|
|
||||||
subtitle = null,
|
|
||||||
isPublic = true,
|
|
||||||
isMatrixOrg = true,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - initial state - matrix org`() = runTest {
|
fun `present - initial state - the first configured account provider is the default`() = runTest {
|
||||||
val sut = anAccountProviderDataSource(
|
val sut = anAccountProviderDataSource(
|
||||||
enterpriseService = FakeEnterpriseService(
|
enterpriseService = FakeEnterpriseService(
|
||||||
defaultHomeserverListResult = { listOf(AuthenticationConfig.MATRIX_ORG_URL) }
|
accountProviderAllowListResult = {
|
||||||
|
listOf(anAccountProviderManaged(serverName = "first.org"), anAccountProviderManaged(serverName = "second.org"))
|
||||||
|
}
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
sut.flow.test {
|
sut.flow.test {
|
||||||
val initialState = awaitItem()
|
assertThat(awaitItem()).isEqualTo(anAccountProviderManaged(serverName = "first.org"))
|
||||||
assertThat(initialState).isEqualTo(
|
|
||||||
AccountProvider(
|
|
||||||
url = AuthenticationConfig.MATRIX_ORG_URL,
|
|
||||||
title = "matrix.org",
|
|
||||||
subtitle = null,
|
|
||||||
isPublic = true,
|
|
||||||
isMatrixOrg = true,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `present - ensure that default homeserver is not star char`() = runTest {
|
|
||||||
val sut = anAccountProviderDataSource(
|
|
||||||
enterpriseService = FakeEnterpriseService(
|
|
||||||
defaultHomeserverListResult = { listOf(EnterpriseService.ANY_ACCOUNT_PROVIDER, AuthenticationConfig.MATRIX_ORG_URL) }
|
|
||||||
),
|
|
||||||
)
|
|
||||||
sut.flow.test {
|
|
||||||
val initialState = awaitItem()
|
|
||||||
assertThat(initialState).isEqualTo(
|
|
||||||
AccountProvider(
|
|
||||||
url = AuthenticationConfig.MATRIX_ORG_URL,
|
|
||||||
title = "matrix.org",
|
|
||||||
subtitle = null,
|
|
||||||
isPublic = true,
|
|
||||||
isMatrixOrg = true,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,22 +50,11 @@ class AccountProviderDataSourceTest {
|
|||||||
fun `present - user change and reset`() = runTest {
|
fun `present - user change and reset`() = runTest {
|
||||||
val sut = anAccountProviderDataSource()
|
val sut = anAccountProviderDataSource()
|
||||||
sut.flow.test {
|
sut.flow.test {
|
||||||
val initialState = awaitItem()
|
assertThat(awaitItem()).isEqualTo(matrixOrgAccountProvider)
|
||||||
assertThat(initialState.url).isEqualTo(AuthenticationConfig.MATRIX_ORG_URL)
|
sut.setAccountProvider(AccountProvider.Generic("https://example.com"))
|
||||||
sut.setAccountProvider(AccountProvider(url = "https://example.com"))
|
assertThat(awaitItem()).isEqualTo(AccountProvider.Generic("https://example.com"))
|
||||||
val changedState = awaitItem()
|
|
||||||
assertThat(changedState).isEqualTo(
|
|
||||||
AccountProvider(
|
|
||||||
url = "https://example.com",
|
|
||||||
title = "example.com",
|
|
||||||
subtitle = null,
|
|
||||||
isPublic = false,
|
|
||||||
isMatrixOrg = false,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
sut.reset()
|
sut.reset()
|
||||||
val resetState = awaitItem()
|
assertThat(awaitItem()).isEqualTo(matrixOrgAccountProvider)
|
||||||
assertThat(resetState.url).isEqualTo(AuthenticationConfig.MATRIX_ORG_URL)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,22 +62,23 @@ class AccountProviderDataSourceTest {
|
|||||||
fun `present - set url and reset`() = runTest {
|
fun `present - set url and reset`() = runTest {
|
||||||
val sut = anAccountProviderDataSource()
|
val sut = anAccountProviderDataSource()
|
||||||
sut.flow.test {
|
sut.flow.test {
|
||||||
val initialState = awaitItem()
|
assertThat(awaitItem()).isEqualTo(matrixOrgAccountProvider)
|
||||||
assertThat(initialState.url).isEqualTo(AuthenticationConfig.MATRIX_ORG_URL)
|
|
||||||
sut.setUrl(url = "https://example.com")
|
sut.setUrl(url = "https://example.com")
|
||||||
val changedState = awaitItem()
|
assertThat(awaitItem()).isEqualTo(
|
||||||
assertThat(changedState).isEqualTo(
|
AccountProvider.Generic("https://example.com")
|
||||||
AccountProvider(
|
|
||||||
url = "https://example.com",
|
|
||||||
title = "example.com",
|
|
||||||
subtitle = null,
|
|
||||||
isPublic = false,
|
|
||||||
isMatrixOrg = false,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
sut.reset()
|
sut.reset()
|
||||||
val resetState = awaitItem()
|
assertThat(awaitItem()).isEqualTo(matrixOrgAccountProvider)
|
||||||
assertThat(resetState.url).isEqualTo(AuthenticationConfig.MATRIX_ORG_URL)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - a provider set by the user is stored as they input it`() = runTest {
|
||||||
|
val sut = anAccountProviderDataSource()
|
||||||
|
sut.flow.test {
|
||||||
|
skipItems(1)
|
||||||
|
sut.setUrl(url = "example.com")
|
||||||
|
assertThat(awaitItem()).isEqualTo(AccountProvider.Generic("example.com"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,7 +90,7 @@ class AccountProviderDataSourceTest {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
sut.flow.test {
|
sut.flow.test {
|
||||||
assertThat(awaitItem().url).isEqualTo("https://example.com")
|
assertThat(awaitItem()).isEqualTo(AccountProvider.Generic("https://example.com"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,14 +98,31 @@ class AccountProviderDataSourceTest {
|
|||||||
fun `present - history is ignored when the account provider is enforced`() = runTest {
|
fun `present - history is ignored when the account provider is enforced`() = runTest {
|
||||||
val sut = anAccountProviderDataSource(
|
val sut = anAccountProviderDataSource(
|
||||||
enterpriseService = FakeEnterpriseService(
|
enterpriseService = FakeEnterpriseService(
|
||||||
defaultHomeserverListResult = { listOf("https://enforced.org") }
|
accountProviderAllowListResult = { listOf(anAccountProviderManaged(serverName = "enforced.org")) },
|
||||||
|
canConnectToAnyAccountProviderResult = { false },
|
||||||
),
|
),
|
||||||
appPreferencesStore = InMemoryAppPreferencesStore(
|
appPreferencesStore = InMemoryAppPreferencesStore(
|
||||||
homeserverHistory = listOf("https://example.com"),
|
homeserverHistory = listOf("https://example.com"),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
sut.flow.test {
|
sut.flow.test {
|
||||||
assertThat(awaitItem().url).isEqualTo("https://enforced.org")
|
assertThat(awaitItem()).isEqualTo(anAccountProviderManaged(serverName = "enforced.org"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - history wins over the configured provider when other providers are allowed`() = runTest {
|
||||||
|
val sut = anAccountProviderDataSource(
|
||||||
|
enterpriseService = FakeEnterpriseService(
|
||||||
|
accountProviderAllowListResult = { listOf(anAccountProviderManaged(serverName = "preferred.org")) },
|
||||||
|
canConnectToAnyAccountProviderResult = { true },
|
||||||
|
),
|
||||||
|
appPreferencesStore = InMemoryAppPreferencesStore(
|
||||||
|
homeserverHistory = listOf("https://example.com"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
sut.flow.test {
|
||||||
|
assertThat(awaitItem()).isEqualTo(AccountProvider.Generic("https://example.com"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,11 +134,11 @@ class AccountProviderDataSourceTest {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
sut.flow.test {
|
sut.flow.test {
|
||||||
assertThat(awaitItem().url).isEqualTo("https://example.com")
|
assertThat(awaitItem()).isEqualTo(AccountProvider.Generic("https://example.com"))
|
||||||
sut.setUrl("https://other.com")
|
sut.setUrl("https://other.com")
|
||||||
assertThat(awaitItem().url).isEqualTo("https://other.com")
|
assertThat(awaitItem()).isEqualTo(AccountProvider.Generic("https://other.com"))
|
||||||
sut.reset()
|
sut.reset()
|
||||||
assertThat(awaitItem().url).isEqualTo("https://example.com")
|
assertThat(awaitItem()).isEqualTo(AccountProvider.Generic("https://example.com"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -20,7 +20,7 @@ class SaveAccountProviderToHistoryTest {
|
|||||||
val warmUpRule = WarmUpRule()
|
val warmUpRule = WarmUpRule()
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `invoke saves the current account provider url to history`() = runTest {
|
fun `invoke saves the current account provider server name to history`() = runTest {
|
||||||
val appPreferencesStore = InMemoryAppPreferencesStore()
|
val appPreferencesStore = InMemoryAppPreferencesStore()
|
||||||
val accountProviderDataSource = anAccountProviderDataSource()
|
val accountProviderDataSource = anAccountProviderDataSource()
|
||||||
accountProviderDataSource.setUrl("https://example.com")
|
accountProviderDataSource.setUrl("https://example.com")
|
||||||
@@ -29,7 +29,7 @@ class SaveAccountProviderToHistoryTest {
|
|||||||
sut()
|
sut()
|
||||||
|
|
||||||
assertThat(appPreferencesStore.getHomeserverHistoryFlow().first())
|
assertThat(appPreferencesStore.getHomeserverHistoryFlow().first())
|
||||||
.containsExactly("https://example.com")
|
.containsExactly("example.com")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -45,6 +45,6 @@ class SaveAccountProviderToHistoryTest {
|
|||||||
sut()
|
sut()
|
||||||
|
|
||||||
assertThat(appPreferencesStore.getHomeserverHistoryFlow().first())
|
assertThat(appPreferencesStore.getHomeserverHistoryFlow().first())
|
||||||
.containsExactly("https://example.com")
|
.containsExactly("example.com")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-22
@@ -12,14 +12,15 @@ import com.google.common.truth.Truth.assertThat
|
|||||||
import io.element.android.features.enterprise.api.EnterpriseService
|
import io.element.android.features.enterprise.api.EnterpriseService
|
||||||
import io.element.android.features.enterprise.test.FakeEnterpriseService
|
import io.element.android.features.enterprise.test.FakeEnterpriseService
|
||||||
import io.element.android.features.login.impl.accesscontrol.DefaultAccountProviderAccessControl
|
import io.element.android.features.login.impl.accesscontrol.DefaultAccountProviderAccessControl
|
||||||
import io.element.android.features.login.impl.accountprovider.AccountProvider
|
|
||||||
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
|
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
|
||||||
import io.element.android.features.login.impl.accountprovider.anAccountProviderDataSource
|
import io.element.android.features.login.impl.accountprovider.anAccountProviderDataSource
|
||||||
import io.element.android.features.login.impl.error.ChangeServerError
|
import io.element.android.features.login.impl.error.ChangeServerError
|
||||||
import io.element.android.features.login.impl.localnetwork.LocalNetworkPermissionGate
|
import io.element.android.features.login.impl.localnetwork.LocalNetworkPermissionGate
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
|
import io.element.android.libraries.matrix.api.accountprovider.AccountProvider
|
||||||
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
||||||
import io.element.android.libraries.matrix.test.A_HOMESERVER_URL
|
import io.element.android.libraries.matrix.test.A_HOMESERVER_URL
|
||||||
|
import io.element.android.libraries.matrix.test.accountprovider.anAccountProviderManaged
|
||||||
import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService
|
import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService
|
||||||
import io.element.android.libraries.matrix.test.auth.aMatrixHomeServerDetails
|
import io.element.android.libraries.matrix.test.auth.aMatrixHomeServerDetails
|
||||||
import io.element.android.libraries.permissions.api.localnetwork.LocalNetworkPermissionDialog
|
import io.element.android.libraries.permissions.api.localnetwork.LocalNetworkPermissionDialog
|
||||||
@@ -57,13 +58,13 @@ class ChangeServerPresenterTest {
|
|||||||
createPresenter(
|
createPresenter(
|
||||||
authenticationService = authenticationService,
|
authenticationService = authenticationService,
|
||||||
enterpriseService = FakeEnterpriseService(
|
enterpriseService = FakeEnterpriseService(
|
||||||
isAllowedToConnectToHomeserverResult = { true },
|
isAllowedToConnectToAccountProviderResult = { true },
|
||||||
isElementProEnforcedResult = { false },
|
isElementProEnforcedResult = { false },
|
||||||
),
|
),
|
||||||
).test {
|
).test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.changeServerAction).isEqualTo(AsyncData.Uninitialized)
|
assertThat(initialState.changeServerAction).isEqualTo(AsyncData.Uninitialized)
|
||||||
initialState.eventSink.invoke(ChangeServerEvent.ChangeServer(AccountProvider(url = A_HOMESERVER_URL)))
|
initialState.eventSink.invoke(ChangeServerEvent.ChangeServer(AccountProvider.Generic(A_HOMESERVER_URL)))
|
||||||
val loadingState = awaitItem()
|
val loadingState = awaitItem()
|
||||||
assertThat(loadingState.changeServerAction).isInstanceOf(AsyncData.Loading::class.java)
|
assertThat(loadingState.changeServerAction).isInstanceOf(AsyncData.Loading::class.java)
|
||||||
val successState = awaitItem()
|
val successState = awaitItem()
|
||||||
@@ -80,14 +81,14 @@ class ChangeServerPresenterTest {
|
|||||||
)
|
)
|
||||||
createPresenter(
|
createPresenter(
|
||||||
enterpriseService = FakeEnterpriseService(
|
enterpriseService = FakeEnterpriseService(
|
||||||
isAllowedToConnectToHomeserverResult = { true },
|
isAllowedToConnectToAccountProviderResult = { true },
|
||||||
isElementProEnforcedResult = { false },
|
isElementProEnforcedResult = { false },
|
||||||
),
|
),
|
||||||
authenticationService = authenticationService,
|
authenticationService = authenticationService,
|
||||||
).test {
|
).test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.changeServerAction).isEqualTo(AsyncData.Uninitialized)
|
assertThat(initialState.changeServerAction).isEqualTo(AsyncData.Uninitialized)
|
||||||
initialState.eventSink.invoke(ChangeServerEvent.ChangeServer(AccountProvider(url = A_HOMESERVER_URL)))
|
initialState.eventSink.invoke(ChangeServerEvent.ChangeServer(AccountProvider.Generic(A_HOMESERVER_URL)))
|
||||||
val loadingState = awaitItem()
|
val loadingState = awaitItem()
|
||||||
assertThat(loadingState.changeServerAction).isInstanceOf(AsyncData.Loading::class.java)
|
assertThat(loadingState.changeServerAction).isInstanceOf(AsyncData.Loading::class.java)
|
||||||
val failureState = awaitItem()
|
val failureState = awaitItem()
|
||||||
@@ -108,14 +109,14 @@ class ChangeServerPresenterTest {
|
|||||||
)
|
)
|
||||||
createPresenter(
|
createPresenter(
|
||||||
enterpriseService = FakeEnterpriseService(
|
enterpriseService = FakeEnterpriseService(
|
||||||
isAllowedToConnectToHomeserverResult = { true },
|
isAllowedToConnectToAccountProviderResult = { true },
|
||||||
isElementProEnforcedResult = { false },
|
isElementProEnforcedResult = { false },
|
||||||
),
|
),
|
||||||
authenticationService = authenticationService,
|
authenticationService = authenticationService,
|
||||||
).test {
|
).test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.changeServerAction).isEqualTo(AsyncData.Uninitialized)
|
assertThat(initialState.changeServerAction).isEqualTo(AsyncData.Uninitialized)
|
||||||
initialState.eventSink.invoke(ChangeServerEvent.ChangeServer(AccountProvider(url = A_HOMESERVER_URL)))
|
initialState.eventSink.invoke(ChangeServerEvent.ChangeServer(AccountProvider.Generic(A_HOMESERVER_URL)))
|
||||||
val loadingState = awaitItem()
|
val loadingState = awaitItem()
|
||||||
assertThat(loadingState.changeServerAction).isInstanceOf(AsyncData.Loading::class.java)
|
assertThat(loadingState.changeServerAction).isInstanceOf(AsyncData.Loading::class.java)
|
||||||
val failureState = awaitItem()
|
val failureState = awaitItem()
|
||||||
@@ -128,30 +129,30 @@ class ChangeServerPresenterTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - change server not allowed error`() = runTest {
|
fun `present - change server not allowed error`() = runTest {
|
||||||
val isAllowedToConnectToHomeserverResult = lambdaRecorder<String, Boolean> { false }
|
val isAllowedToConnectToAccountProviderResult = lambdaRecorder<AccountProvider, Boolean> { false }
|
||||||
createPresenter(
|
createPresenter(
|
||||||
enterpriseService = FakeEnterpriseService(
|
enterpriseService = FakeEnterpriseService(
|
||||||
isAllowedToConnectToHomeserverResult = isAllowedToConnectToHomeserverResult,
|
isAllowedToConnectToAccountProviderResult = isAllowedToConnectToAccountProviderResult,
|
||||||
defaultHomeserverListResult = { listOf("element.io") },
|
accountProviderAllowListResult = { listOf(anAccountProviderManaged(serverName = "element.io")) },
|
||||||
isElementProEnforcedResult = { false },
|
isElementProEnforcedResult = { false },
|
||||||
),
|
),
|
||||||
).test {
|
).test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.changeServerAction).isEqualTo(AsyncData.Uninitialized)
|
assertThat(initialState.changeServerAction).isEqualTo(AsyncData.Uninitialized)
|
||||||
val anAccountProvider = AccountProvider(url = A_HOMESERVER_URL)
|
val anAccountProvider = AccountProvider.Generic(A_HOMESERVER_URL)
|
||||||
initialState.eventSink.invoke(ChangeServerEvent.ChangeServer(anAccountProvider))
|
initialState.eventSink.invoke(ChangeServerEvent.ChangeServer(anAccountProvider))
|
||||||
val loadingState = awaitItem()
|
val loadingState = awaitItem()
|
||||||
assertThat(loadingState.changeServerAction).isInstanceOf(AsyncData.Loading::class.java)
|
assertThat(loadingState.changeServerAction).isInstanceOf(AsyncData.Loading::class.java)
|
||||||
val failureState = awaitItem()
|
val failureState = awaitItem()
|
||||||
assertThat(
|
assertThat(
|
||||||
(failureState.changeServerAction.errorOrNull() as ChangeServerError.UnauthorizedAccountProvider).unauthorisedAccountProviderTitle
|
(failureState.changeServerAction.errorOrNull() as ChangeServerError.UnauthorizedAccountProvider).unauthorisedAccountProviderTitle
|
||||||
).isEqualTo(anAccountProvider.title)
|
).isEqualTo(anAccountProvider.serverNameOrBaseUrl())
|
||||||
assertThat(
|
assertThat(
|
||||||
(failureState.changeServerAction.errorOrNull() as ChangeServerError.UnauthorizedAccountProvider).authorisedAccountProviderTitles
|
(failureState.changeServerAction.errorOrNull() as ChangeServerError.UnauthorizedAccountProvider).authorisedAccountProviderTitles
|
||||||
).containsExactly("element.io")
|
).containsExactly("element.io")
|
||||||
isAllowedToConnectToHomeserverResult.assertions()
|
isAllowedToConnectToAccountProviderResult.assertions()
|
||||||
.isCalledOnce()
|
.isCalledOnce()
|
||||||
.with(value(A_HOMESERVER_URL))
|
.with(value(AccountProvider.Generic(A_HOMESERVER_URL)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,14 +167,14 @@ class ChangeServerPresenterTest {
|
|||||||
).test {
|
).test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.changeServerAction).isEqualTo(AsyncData.Uninitialized)
|
assertThat(initialState.changeServerAction).isEqualTo(AsyncData.Uninitialized)
|
||||||
val anAccountProvider = AccountProvider(url = A_HOMESERVER_URL)
|
val anAccountProvider = AccountProvider.Generic(A_HOMESERVER_URL)
|
||||||
initialState.eventSink.invoke(ChangeServerEvent.ChangeServer(anAccountProvider))
|
initialState.eventSink.invoke(ChangeServerEvent.ChangeServer(anAccountProvider))
|
||||||
// Skip loading state
|
// Skip loading state
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
val failureState = awaitItem()
|
val failureState = awaitItem()
|
||||||
assertThat(
|
assertThat(
|
||||||
(failureState.changeServerAction.errorOrNull() as ChangeServerError.NeedElementPro).unauthorisedAccountProviderTitle
|
(failureState.changeServerAction.errorOrNull() as ChangeServerError.NeedElementPro).unauthorisedAccountProviderTitle
|
||||||
).isEqualTo(anAccountProvider.title)
|
).isEqualTo(anAccountProvider.serverNameOrBaseUrl())
|
||||||
assertThat(
|
assertThat(
|
||||||
(failureState.changeServerAction.errorOrNull() as ChangeServerError.NeedElementPro).applicationId
|
(failureState.changeServerAction.errorOrNull() as ChangeServerError.NeedElementPro).applicationId
|
||||||
).isEqualTo("io.element.enterprise")
|
).isEqualTo("io.element.enterprise")
|
||||||
@@ -188,13 +189,13 @@ class ChangeServerPresenterTest {
|
|||||||
)
|
)
|
||||||
createPresenter(
|
createPresenter(
|
||||||
authenticationService = authenticationService,
|
authenticationService = authenticationService,
|
||||||
enterpriseService = FakeEnterpriseService(isAllowedToConnectToHomeserverResult = { true }),
|
enterpriseService = FakeEnterpriseService(isAllowedToConnectToAccountProviderResult = { true }),
|
||||||
localNetworkPermissionAdvisor = FakeLocalNetworkPermissionAdvisor(shouldPrompt = true),
|
localNetworkPermissionAdvisor = FakeLocalNetworkPermissionAdvisor(shouldPrompt = true),
|
||||||
permissionsPresenter = FakePermissionsPresenter(),
|
permissionsPresenter = FakePermissionsPresenter(),
|
||||||
).test {
|
).test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.localNetworkPermissionDialog).isEqualTo(LocalNetworkPermissionDialog.None)
|
assertThat(initialState.localNetworkPermissionDialog).isEqualTo(LocalNetworkPermissionDialog.None)
|
||||||
initialState.eventSink.invoke(ChangeServerEvent.ChangeServer(AccountProvider(url = A_HOMESERVER_URL)))
|
initialState.eventSink.invoke(ChangeServerEvent.ChangeServer(AccountProvider.Generic(A_HOMESERVER_URL)))
|
||||||
val promptState = expectMostRecentItem()
|
val promptState = expectMostRecentItem()
|
||||||
// Dialog is shown, permission has not been requested yet, setHomeserver has not been called.
|
// Dialog is shown, permission has not been requested yet, setHomeserver has not been called.
|
||||||
assertThat(promptState.localNetworkPermissionDialog).isNotEqualTo(LocalNetworkPermissionDialog.None)
|
assertThat(promptState.localNetworkPermissionDialog).isNotEqualTo(LocalNetworkPermissionDialog.None)
|
||||||
@@ -208,12 +209,12 @@ class ChangeServerPresenterTest {
|
|||||||
authenticationService = FakeMatrixAuthenticationService(
|
authenticationService = FakeMatrixAuthenticationService(
|
||||||
setHomeserverResult = { Result.success(aMatrixHomeServerDetails(supportsOAuthLogin = true)) },
|
setHomeserverResult = { Result.success(aMatrixHomeServerDetails(supportsOAuthLogin = true)) },
|
||||||
),
|
),
|
||||||
enterpriseService = FakeEnterpriseService(isAllowedToConnectToHomeserverResult = { true }),
|
enterpriseService = FakeEnterpriseService(isAllowedToConnectToAccountProviderResult = { true }),
|
||||||
localNetworkPermissionAdvisor = FakeLocalNetworkPermissionAdvisor(shouldPrompt = true),
|
localNetworkPermissionAdvisor = FakeLocalNetworkPermissionAdvisor(shouldPrompt = true),
|
||||||
permissionsPresenter = FakePermissionsPresenter(),
|
permissionsPresenter = FakePermissionsPresenter(),
|
||||||
).test {
|
).test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
initialState.eventSink.invoke(ChangeServerEvent.ChangeServer(AccountProvider(url = A_HOMESERVER_URL)))
|
initialState.eventSink.invoke(ChangeServerEvent.ChangeServer(AccountProvider.Generic(A_HOMESERVER_URL)))
|
||||||
val promptState = expectMostRecentItem()
|
val promptState = expectMostRecentItem()
|
||||||
assertThat(promptState.localNetworkPermissionDialog).isNotEqualTo(LocalNetworkPermissionDialog.None)
|
assertThat(promptState.localNetworkPermissionDialog).isNotEqualTo(LocalNetworkPermissionDialog.None)
|
||||||
promptState.eventSink.invoke(ChangeServerEvent.DismissLocalNetworkPermission)
|
promptState.eventSink.invoke(ChangeServerEvent.DismissLocalNetworkPermission)
|
||||||
@@ -231,12 +232,12 @@ class ChangeServerPresenterTest {
|
|||||||
val permissionsPresenter = FakePermissionsPresenter()
|
val permissionsPresenter = FakePermissionsPresenter()
|
||||||
createPresenter(
|
createPresenter(
|
||||||
authenticationService = authenticationService,
|
authenticationService = authenticationService,
|
||||||
enterpriseService = FakeEnterpriseService(isAllowedToConnectToHomeserverResult = { true }, isElementProEnforcedResult = { false }),
|
enterpriseService = FakeEnterpriseService(isAllowedToConnectToAccountProviderResult = { true }, isElementProEnforcedResult = { false }),
|
||||||
localNetworkPermissionAdvisor = FakeLocalNetworkPermissionAdvisor(shouldPrompt = true),
|
localNetworkPermissionAdvisor = FakeLocalNetworkPermissionAdvisor(shouldPrompt = true),
|
||||||
permissionsPresenter = permissionsPresenter,
|
permissionsPresenter = permissionsPresenter,
|
||||||
).test {
|
).test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
initialState.eventSink.invoke(ChangeServerEvent.ChangeServer(AccountProvider(url = A_HOMESERVER_URL)))
|
initialState.eventSink.invoke(ChangeServerEvent.ChangeServer(AccountProvider.Generic(A_HOMESERVER_URL)))
|
||||||
val promptState = expectMostRecentItem()
|
val promptState = expectMostRecentItem()
|
||||||
assertThat(promptState.localNetworkPermissionDialog).isNotEqualTo(LocalNetworkPermissionDialog.None)
|
assertThat(promptState.localNetworkPermissionDialog).isNotEqualTo(LocalNetworkPermissionDialog.None)
|
||||||
permissionsPresenter.setPermissionGranted()
|
permissionsPresenter.setPermissionGranted()
|
||||||
|
|||||||
+7
-18
@@ -11,14 +11,13 @@ package io.element.android.features.login.impl.screens.chooseaccountprovider
|
|||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.features.enterprise.api.EnterpriseService
|
import io.element.android.features.enterprise.api.EnterpriseService
|
||||||
import io.element.android.features.enterprise.test.FakeEnterpriseService
|
import io.element.android.features.enterprise.test.FakeEnterpriseService
|
||||||
import io.element.android.features.login.impl.accountprovider.AccountProvider
|
|
||||||
import io.element.android.features.login.impl.login.LoginModePresenter
|
import io.element.android.features.login.impl.login.LoginModePresenter
|
||||||
import io.element.android.features.login.impl.screens.onboarding.createLoginModePresenter
|
import io.element.android.features.login.impl.screens.onboarding.createLoginModePresenter
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.core.uri.ensureProtocol
|
|
||||||
import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER_2
|
import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER_2
|
||||||
import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER_3
|
import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER_3
|
||||||
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
||||||
|
import io.element.android.libraries.matrix.test.accountprovider.anAccountProviderManaged
|
||||||
import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService
|
import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService
|
||||||
import io.element.android.tests.testutils.WarmUpRule
|
import io.element.android.tests.testutils.WarmUpRule
|
||||||
import io.element.android.tests.testutils.test
|
import io.element.android.tests.testutils.test
|
||||||
@@ -33,18 +32,8 @@ class ChooseAccountProviderPresenterTest {
|
|||||||
companion object {
|
companion object {
|
||||||
private const val ACCOUNT_PROVIDER_FROM_CONFIG_1 = AN_ACCOUNT_PROVIDER_2
|
private const val ACCOUNT_PROVIDER_FROM_CONFIG_1 = AN_ACCOUNT_PROVIDER_2
|
||||||
private const val ACCOUNT_PROVIDER_FROM_CONFIG_2 = AN_ACCOUNT_PROVIDER_3
|
private const val ACCOUNT_PROVIDER_FROM_CONFIG_2 = AN_ACCOUNT_PROVIDER_3
|
||||||
val accountProvider1 = AccountProvider(
|
val accountProvider1 = anAccountProviderManaged(serverName = ACCOUNT_PROVIDER_FROM_CONFIG_1)
|
||||||
url = ACCOUNT_PROVIDER_FROM_CONFIG_1.ensureProtocol(),
|
val accountProvider2 = anAccountProviderManaged(serverName = ACCOUNT_PROVIDER_FROM_CONFIG_2)
|
||||||
subtitle = null,
|
|
||||||
isPublic = false,
|
|
||||||
isMatrixOrg = false,
|
|
||||||
)
|
|
||||||
val accountProvider2 = AccountProvider(
|
|
||||||
url = ACCOUNT_PROVIDER_FROM_CONFIG_2.ensureProtocol(),
|
|
||||||
subtitle = null,
|
|
||||||
isPublic = false,
|
|
||||||
isMatrixOrg = false,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -61,7 +50,7 @@ class ChooseAccountProviderPresenterTest {
|
|||||||
fun `present - initial state`() = runTest {
|
fun `present - initial state`() = runTest {
|
||||||
val presenter = createPresenter(
|
val presenter = createPresenter(
|
||||||
enterpriseService = FakeEnterpriseService(
|
enterpriseService = FakeEnterpriseService(
|
||||||
defaultHomeserverListResult = { listOf(ACCOUNT_PROVIDER_FROM_CONFIG_1, ACCOUNT_PROVIDER_FROM_CONFIG_2) },
|
accountProviderAllowListResult = { listOf(accountProvider1, accountProvider2) },
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
@@ -79,7 +68,7 @@ class ChooseAccountProviderPresenterTest {
|
|||||||
val authenticationService = FakeMatrixAuthenticationService()
|
val authenticationService = FakeMatrixAuthenticationService()
|
||||||
val presenter = createPresenter(
|
val presenter = createPresenter(
|
||||||
enterpriseService = FakeEnterpriseService(
|
enterpriseService = FakeEnterpriseService(
|
||||||
defaultHomeserverListResult = { listOf(ACCOUNT_PROVIDER_FROM_CONFIG_1, ACCOUNT_PROVIDER_FROM_CONFIG_2) },
|
accountProviderAllowListResult = { listOf(accountProvider1, accountProvider2) },
|
||||||
),
|
),
|
||||||
loginModePresenter = createLoginModePresenter(
|
loginModePresenter = createLoginModePresenter(
|
||||||
authenticationService = authenticationService,
|
authenticationService = authenticationService,
|
||||||
@@ -103,7 +92,7 @@ class ChooseAccountProviderPresenterTest {
|
|||||||
)
|
)
|
||||||
val presenter = createPresenter(
|
val presenter = createPresenter(
|
||||||
enterpriseService = FakeEnterpriseService(
|
enterpriseService = FakeEnterpriseService(
|
||||||
defaultHomeserverListResult = { listOf(ACCOUNT_PROVIDER_FROM_CONFIG_1, ACCOUNT_PROVIDER_FROM_CONFIG_2) },
|
accountProviderAllowListResult = { listOf(accountProvider1, accountProvider2) },
|
||||||
),
|
),
|
||||||
loginModePresenter = createLoginModePresenter(
|
loginModePresenter = createLoginModePresenter(
|
||||||
authenticationService = authenticationService,
|
authenticationService = authenticationService,
|
||||||
@@ -136,7 +125,7 @@ class ChooseAccountProviderPresenterTest {
|
|||||||
val authenticationService = FakeMatrixAuthenticationService()
|
val authenticationService = FakeMatrixAuthenticationService()
|
||||||
val presenter = createPresenter(
|
val presenter = createPresenter(
|
||||||
enterpriseService = FakeEnterpriseService(
|
enterpriseService = FakeEnterpriseService(
|
||||||
defaultHomeserverListResult = { listOf(ACCOUNT_PROVIDER_FROM_CONFIG_1, ACCOUNT_PROVIDER_FROM_CONFIG_2) },
|
accountProviderAllowListResult = { listOf(accountProvider1, accountProvider2) },
|
||||||
),
|
),
|
||||||
loginModePresenter = createLoginModePresenter(
|
loginModePresenter = createLoginModePresenter(
|
||||||
authenticationService = authenticationService,
|
authenticationService = authenticationService,
|
||||||
|
|||||||
+4
-4
@@ -9,7 +9,7 @@
|
|||||||
package io.element.android.features.login.impl.screens.chooseaccountprovider
|
package io.element.android.features.login.impl.screens.chooseaccountprovider
|
||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.features.login.impl.accountprovider.anAccountProvider
|
import io.element.android.features.login.impl.accountprovider.anAccountProviderManaged
|
||||||
import io.element.android.features.login.impl.login.LoginMode
|
import io.element.android.features.login.impl.login.LoginMode
|
||||||
import io.element.android.features.login.impl.login.aLoginModeState
|
import io.element.android.features.login.impl.login.aLoginModeState
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
@@ -27,7 +27,7 @@ class ChooseAccountProviderStateTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `submitEnabled returns true when there is a selectedAccountProvider`() {
|
fun `submitEnabled returns true when there is a selectedAccountProvider`() {
|
||||||
val sut = aChooseAccountProviderState(
|
val sut = aChooseAccountProviderState(
|
||||||
selectedAccountProvider = anAccountProvider(),
|
selectedAccountProvider = anAccountProviderManaged(),
|
||||||
)
|
)
|
||||||
assertThat(sut.submitEnabled).isTrue()
|
assertThat(sut.submitEnabled).isTrue()
|
||||||
}
|
}
|
||||||
@@ -35,7 +35,7 @@ class ChooseAccountProviderStateTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `submitEnabled returns false when there is a selectedAccountProvider but there is an error`() {
|
fun `submitEnabled returns false when there is a selectedAccountProvider but there is an error`() {
|
||||||
val sut = aChooseAccountProviderState(
|
val sut = aChooseAccountProviderState(
|
||||||
selectedAccountProvider = anAccountProvider(),
|
selectedAccountProvider = anAccountProviderManaged(),
|
||||||
loginModeState = aLoginModeState(loginMode = AsyncData.Failure(Throwable("Error"))),
|
loginModeState = aLoginModeState(loginMode = AsyncData.Failure(Throwable("Error"))),
|
||||||
)
|
)
|
||||||
assertThat(sut.submitEnabled).isFalse()
|
assertThat(sut.submitEnabled).isFalse()
|
||||||
@@ -44,7 +44,7 @@ class ChooseAccountProviderStateTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `submitEnabled returns false when there is a selectedAccountProvider but the result is successful`() {
|
fun `submitEnabled returns false when there is a selectedAccountProvider but the result is successful`() {
|
||||||
val sut = aChooseAccountProviderState(
|
val sut = aChooseAccountProviderState(
|
||||||
selectedAccountProvider = anAccountProvider(),
|
selectedAccountProvider = anAccountProviderManaged(),
|
||||||
loginModeState = aLoginModeState(loginMode = AsyncData.Success(LoginMode.PasswordLogin)),
|
loginModeState = aLoginModeState(loginMode = AsyncData.Success(LoginMode.PasswordLogin)),
|
||||||
)
|
)
|
||||||
assertThat(sut.submitEnabled).isFalse()
|
assertThat(sut.submitEnabled).isFalse()
|
||||||
|
|||||||
+3
-3
@@ -16,7 +16,7 @@ import androidx.compose.ui.test.ExperimentalTestApi
|
|||||||
import androidx.compose.ui.test.onNodeWithText
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
import androidx.compose.ui.test.performClick
|
import androidx.compose.ui.test.performClick
|
||||||
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import io.element.android.features.login.impl.accountprovider.anAccountProvider
|
import io.element.android.features.login.impl.accountprovider.anAccountProviderManaged
|
||||||
import io.element.android.features.login.impl.login.aLoginModeState
|
import io.element.android.features.login.impl.login.aLoginModeState
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.matrix.api.auth.OAuthDetails
|
import io.element.android.libraries.matrix.api.auth.OAuthDetails
|
||||||
@@ -57,11 +57,11 @@ class ChooseAccountProviderViewTest : RobolectricTest() {
|
|||||||
ChooseAccountProviderPresenterTest.accountProvider1,
|
ChooseAccountProviderPresenterTest.accountProvider1,
|
||||||
ChooseAccountProviderPresenterTest.accountProvider2,
|
ChooseAccountProviderPresenterTest.accountProvider2,
|
||||||
),
|
),
|
||||||
selectedAccountProvider = anAccountProvider(),
|
selectedAccountProvider = anAccountProviderManaged(),
|
||||||
eventSink = eventSink,
|
eventSink = eventSink,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
onNodeWithText(ChooseAccountProviderPresenterTest.accountProvider1.title).performClick()
|
onNodeWithText(ChooseAccountProviderPresenterTest.accountProvider1.friendlyServerName()).performClick()
|
||||||
eventSink.assertSingle(ChooseAccountProviderEvent.SelectAccountProvider(ChooseAccountProviderPresenterTest.accountProvider1))
|
eventSink.assertSingle(ChooseAccountProviderEvent.SelectAccountProvider(ChooseAccountProviderPresenterTest.accountProvider1))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+38
-21
@@ -10,7 +10,6 @@ package io.element.android.features.login.impl.screens.confirmaccountprovider
|
|||||||
|
|
||||||
import app.cash.turbine.ReceiveTurbine
|
import app.cash.turbine.ReceiveTurbine
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.appconfig.AuthenticationConfig
|
|
||||||
import io.element.android.features.enterprise.api.EnterpriseService
|
import io.element.android.features.enterprise.api.EnterpriseService
|
||||||
import io.element.android.features.enterprise.test.FakeEnterpriseService
|
import io.element.android.features.enterprise.test.FakeEnterpriseService
|
||||||
import io.element.android.features.login.impl.accesscontrol.DefaultAccountProviderAccessControl
|
import io.element.android.features.login.impl.accesscontrol.DefaultAccountProviderAccessControl
|
||||||
@@ -24,6 +23,7 @@ import io.element.android.features.login.impl.screens.onboarding.createLoginMode
|
|||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
||||||
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
||||||
|
import io.element.android.libraries.matrix.test.accountprovider.anAccountProviderManaged
|
||||||
import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService
|
import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService
|
||||||
import io.element.android.libraries.matrix.test.auth.aMatrixHomeServerDetails
|
import io.element.android.libraries.matrix.test.auth.aMatrixHomeServerDetails
|
||||||
import io.element.android.libraries.oauth.api.OAuthAction
|
import io.element.android.libraries.oauth.api.OAuthAction
|
||||||
@@ -44,6 +44,11 @@ class ConfirmAccountProviderPresenterTest {
|
|||||||
@get:Rule
|
@get:Rule
|
||||||
val warmUpRule = WarmUpRule()
|
val warmUpRule = WarmUpRule()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val MATRIX_ORG_SERVER_NAME = "matrix.org"
|
||||||
|
private const val MATRIX_ORG_URL = "https://matrix.org"
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - initial test`() = runTest {
|
fun `present - initial test`() = runTest {
|
||||||
val presenter = createConfirmAccountProviderPresenter()
|
val presenter = createConfirmAccountProviderPresenter()
|
||||||
@@ -68,7 +73,7 @@ class ConfirmAccountProviderPresenterTest {
|
|||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
initialState.eventSink.invoke(ConfirmAccountProviderEvent.Continue(AuthenticationConfig.MATRIX_ORG_URL))
|
initialState.eventSink.invoke(ConfirmAccountProviderEvent.Continue(MATRIX_ORG_URL))
|
||||||
val successState = awaitLoginMode { it is AsyncData.Success }
|
val successState = awaitLoginMode { it is AsyncData.Success }
|
||||||
assertThat(successState.loginModeState.loginMode.dataOrNull()).isEqualTo(LoginMode.PasswordLogin)
|
assertThat(successState.loginModeState.loginMode.dataOrNull()).isEqualTo(LoginMode.PasswordLogin)
|
||||||
cancelAndIgnoreRemainingEvents()
|
cancelAndIgnoreRemainingEvents()
|
||||||
@@ -87,7 +92,7 @@ class ConfirmAccountProviderPresenterTest {
|
|||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
initialState.eventSink.invoke(ConfirmAccountProviderEvent.Continue(AuthenticationConfig.MATRIX_ORG_URL))
|
initialState.eventSink.invoke(ConfirmAccountProviderEvent.Continue(MATRIX_ORG_URL))
|
||||||
val successState = awaitLoginMode { it is AsyncData.Success }
|
val successState = awaitLoginMode { it is AsyncData.Success }
|
||||||
assertThat(successState.loginModeState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java)
|
assertThat(successState.loginModeState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java)
|
||||||
cancelAndIgnoreRemainingEvents()
|
cancelAndIgnoreRemainingEvents()
|
||||||
@@ -108,7 +113,7 @@ class ConfirmAccountProviderPresenterTest {
|
|||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
initialState.eventSink.invoke(ConfirmAccountProviderEvent.Continue(AuthenticationConfig.MATRIX_ORG_URL))
|
initialState.eventSink.invoke(ConfirmAccountProviderEvent.Continue(MATRIX_ORG_URL))
|
||||||
val successState = awaitLoginMode { it is AsyncData.Success }
|
val successState = awaitLoginMode { it is AsyncData.Success }
|
||||||
assertThat(successState.loginModeState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java)
|
assertThat(successState.loginModeState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java)
|
||||||
authenticationService.givenOAuthCancelError(AN_EXCEPTION)
|
authenticationService.givenOAuthCancelError(AN_EXCEPTION)
|
||||||
@@ -133,7 +138,7 @@ class ConfirmAccountProviderPresenterTest {
|
|||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
initialState.eventSink.invoke(ConfirmAccountProviderEvent.Continue(AuthenticationConfig.MATRIX_ORG_URL))
|
initialState.eventSink.invoke(ConfirmAccountProviderEvent.Continue(MATRIX_ORG_URL))
|
||||||
val successState = awaitLoginMode { it is AsyncData.Success }
|
val successState = awaitLoginMode { it is AsyncData.Success }
|
||||||
assertThat(successState.loginModeState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java)
|
assertThat(successState.loginModeState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java)
|
||||||
defaultOAuthActionFlow.post(OAuthAction.GoBack())
|
defaultOAuthActionFlow.post(OAuthAction.GoBack())
|
||||||
@@ -157,7 +162,7 @@ class ConfirmAccountProviderPresenterTest {
|
|||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
initialState.eventSink.invoke(ConfirmAccountProviderEvent.Continue(AuthenticationConfig.MATRIX_ORG_URL))
|
initialState.eventSink.invoke(ConfirmAccountProviderEvent.Continue(MATRIX_ORG_URL))
|
||||||
awaitLoginMode { it is AsyncData.Loading }
|
awaitLoginMode { it is AsyncData.Loading }
|
||||||
defaultOAuthActionFlow.post(OAuthAction.GoBack(toUnblock = true))
|
defaultOAuthActionFlow.post(OAuthAction.GoBack(toUnblock = true))
|
||||||
val cancelFinalState = awaitLoginMode { it is AsyncData.Uninitialized }
|
val cancelFinalState = awaitLoginMode { it is AsyncData.Uninitialized }
|
||||||
@@ -180,7 +185,7 @@ class ConfirmAccountProviderPresenterTest {
|
|||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
initialState.eventSink.invoke(ConfirmAccountProviderEvent.Continue(AuthenticationConfig.MATRIX_ORG_URL))
|
initialState.eventSink.invoke(ConfirmAccountProviderEvent.Continue(MATRIX_ORG_URL))
|
||||||
val successState = awaitLoginMode { it is AsyncData.Success }
|
val successState = awaitLoginMode { it is AsyncData.Success }
|
||||||
assertThat(successState.loginModeState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java)
|
assertThat(successState.loginModeState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java)
|
||||||
authenticationService.givenLoginError(AN_EXCEPTION)
|
authenticationService.givenLoginError(AN_EXCEPTION)
|
||||||
@@ -205,7 +210,7 @@ class ConfirmAccountProviderPresenterTest {
|
|||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
initialState.eventSink.invoke(ConfirmAccountProviderEvent.Continue(AuthenticationConfig.MATRIX_ORG_URL))
|
initialState.eventSink.invoke(ConfirmAccountProviderEvent.Continue(MATRIX_ORG_URL))
|
||||||
val successState = awaitLoginMode { it is AsyncData.Success }
|
val successState = awaitLoginMode { it is AsyncData.Success }
|
||||||
assertThat(successState.loginModeState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java)
|
assertThat(successState.loginModeState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java)
|
||||||
defaultOidcActionFlow.post(OAuthAction.Success("aUrl"))
|
defaultOidcActionFlow.post(OAuthAction.Success("aUrl"))
|
||||||
@@ -227,7 +232,7 @@ class ConfirmAccountProviderPresenterTest {
|
|||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
initialState.eventSink.invoke(ConfirmAccountProviderEvent.Continue(AuthenticationConfig.MATRIX_ORG_URL))
|
initialState.eventSink.invoke(ConfirmAccountProviderEvent.Continue(MATRIX_ORG_URL))
|
||||||
// The account provider validation fails, so the login is never attempted.
|
// The account provider validation fails, so the login is never attempted.
|
||||||
val failureState = awaitState { it.changeServerState.changeServerAction is AsyncData.Failure }
|
val failureState = awaitState { it.changeServerState.changeServerAction is AsyncData.Failure }
|
||||||
assertThat(failureState.loginModeState.loginMode).isEqualTo(AsyncData.Uninitialized)
|
assertThat(failureState.loginModeState.loginMode).isEqualTo(AsyncData.Uninitialized)
|
||||||
@@ -249,7 +254,7 @@ class ConfirmAccountProviderPresenterTest {
|
|||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
|
|
||||||
// Submit will return an error while validating the account provider
|
// Submit will return an error while validating the account provider
|
||||||
initialState.eventSink(ConfirmAccountProviderEvent.Continue(AuthenticationConfig.MATRIX_ORG_URL))
|
initialState.eventSink(ConfirmAccountProviderEvent.Continue(MATRIX_ORG_URL))
|
||||||
|
|
||||||
// Check an error was returned
|
// Check an error was returned
|
||||||
val submittedState = awaitState { it.changeServerState.changeServerAction is AsyncData.Failure }
|
val submittedState = awaitState { it.changeServerState.changeServerAction is AsyncData.Failure }
|
||||||
@@ -274,7 +279,7 @@ class ConfirmAccountProviderPresenterTest {
|
|||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
initialState.eventSink(ConfirmAccountProviderEvent.Continue(AuthenticationConfig.MATRIX_ORG_URL))
|
initialState.eventSink(ConfirmAccountProviderEvent.Continue(MATRIX_ORG_URL))
|
||||||
// Check an error was returned
|
// Check an error was returned
|
||||||
val submittedState = awaitLoginMode { it is AsyncData.Failure }
|
val submittedState = awaitLoginMode { it is AsyncData.Failure }
|
||||||
assertThat(submittedState.loginModeState.loginMode.errorOrNull()).isInstanceOf(AccountCreationNotSupported::class.java)
|
assertThat(submittedState.loginModeState.loginMode.errorOrNull()).isInstanceOf(AccountCreationNotSupported::class.java)
|
||||||
@@ -299,7 +304,7 @@ class ConfirmAccountProviderPresenterTest {
|
|||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
initialState.eventSink(ConfirmAccountProviderEvent.Continue(AuthenticationConfig.MATRIX_ORG_URL))
|
initialState.eventSink(ConfirmAccountProviderEvent.Continue(MATRIX_ORG_URL))
|
||||||
val submittedState = awaitLoginMode { it is AsyncData.Success }
|
val submittedState = awaitLoginMode { it is AsyncData.Success }
|
||||||
assertThat(submittedState.loginModeState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java)
|
assertThat(submittedState.loginModeState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java)
|
||||||
cancelAndIgnoreRemainingEvents()
|
cancelAndIgnoreRemainingEvents()
|
||||||
@@ -344,7 +349,7 @@ class ConfirmAccountProviderPresenterTest {
|
|||||||
val suggestionState = awaitState { it.accountProviderSuggestion != null }
|
val suggestionState = awaitState { it.accountProviderSuggestion != null }
|
||||||
suggestionState.eventSink(ConfirmAccountProviderEvent.Continue("randomcommunity.org"))
|
suggestionState.eventSink(ConfirmAccountProviderEvent.Continue("randomcommunity.org"))
|
||||||
awaitLoginMode { it is AsyncData.Success }
|
awaitLoginMode { it is AsyncData.Success }
|
||||||
assertThat(submittedUrls.first()).isEqualTo("https://randomcommunity.org")
|
assertThat(submittedUrls.first()).isEqualTo("randomcommunity.org")
|
||||||
cancelAndIgnoreRemainingEvents()
|
cancelAndIgnoreRemainingEvents()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -375,8 +380,7 @@ class ConfirmAccountProviderPresenterTest {
|
|||||||
val presenter = createConfirmAccountProviderPresenter(
|
val presenter = createConfirmAccountProviderPresenter(
|
||||||
appPreferencesStore = InMemoryAppPreferencesStore(homeserverHistory = emptyList()),
|
appPreferencesStore = InMemoryAppPreferencesStore(homeserverHistory = emptyList()),
|
||||||
enterpriseService = FakeEnterpriseService(
|
enterpriseService = FakeEnterpriseService(
|
||||||
// Preferred servers plus the "*" (any) wildcard: the wildcard is filtered out, the rest complete.
|
accountProviderAllowListResult = { listOf(anAccountProviderManaged(serverName = "element.io")) },
|
||||||
defaultHomeserverListResult = { listOf("https://element.io", EnterpriseService.ANY_ACCOUNT_PROVIDER) },
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
@@ -387,6 +391,19 @@ class ConfirmAccountProviderPresenterTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - offers the previously used servers without the scheme as autocomplete suggestions`() = runTest {
|
||||||
|
val presenter = createConfirmAccountProviderPresenter(
|
||||||
|
appPreferencesStore = InMemoryAppPreferencesStore(homeserverHistory = listOf("https://previous.org")),
|
||||||
|
)
|
||||||
|
presenter.test {
|
||||||
|
val initialState = awaitItem()
|
||||||
|
initialState.eventSink(ConfirmAccountProviderEvent.UserInputChanged("prev"))
|
||||||
|
val suggestionState = awaitState { it.accountProviderInput == "prev" }
|
||||||
|
assertThat(suggestionState.accountProviderSuggestion).isEqualTo("previous.org")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - offers matrix_org without the scheme as an autocomplete suggestion`() = runTest {
|
fun `present - offers matrix_org without the scheme as an autocomplete suggestion`() = runTest {
|
||||||
val presenter = createConfirmAccountProviderPresenter(
|
val presenter = createConfirmAccountProviderPresenter(
|
||||||
@@ -432,7 +449,7 @@ class ConfirmAccountProviderPresenterTest {
|
|||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
initialState.eventSink(ConfirmAccountProviderEvent.Continue("matrix.org"))
|
initialState.eventSink(ConfirmAccountProviderEvent.Continue("matrix.org"))
|
||||||
awaitLoginMode { it is AsyncData.Success }
|
awaitLoginMode { it is AsyncData.Success }
|
||||||
assertThat(submittedUrls.first()).isEqualTo(AuthenticationConfig.MATRIX_ORG_URL)
|
assertThat(submittedUrls.first()).isEqualTo(MATRIX_ORG_SERVER_NAME)
|
||||||
cancelAndIgnoreRemainingEvents()
|
cancelAndIgnoreRemainingEvents()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -453,9 +470,9 @@ class ConfirmAccountProviderPresenterTest {
|
|||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
initialState.eventSink(ConfirmAccountProviderEvent.Continue(AuthenticationConfig.MATRIX_ORG_URL))
|
initialState.eventSink(ConfirmAccountProviderEvent.Continue(MATRIX_ORG_URL))
|
||||||
awaitLoginMode { it is AsyncData.Success }
|
awaitLoginMode { it is AsyncData.Success }
|
||||||
assertThat(submittedUrls).containsExactly(AuthenticationConfig.MATRIX_ORG_URL)
|
assertThat(submittedUrls).containsExactly(MATRIX_ORG_SERVER_NAME)
|
||||||
cancelAndIgnoreRemainingEvents()
|
cancelAndIgnoreRemainingEvents()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -474,7 +491,7 @@ class ConfirmAccountProviderPresenterTest {
|
|||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
initialState.eventSink(ConfirmAccountProviderEvent.Continue("@alice:example.org"))
|
initialState.eventSink(ConfirmAccountProviderEvent.Continue("@alice:example.org"))
|
||||||
awaitLoginMode { it is AsyncData.Success }
|
awaitLoginMode { it is AsyncData.Success }
|
||||||
assertThat(submittedUrls.first()).isEqualTo("https://example.org")
|
assertThat(submittedUrls.first()).isEqualTo("example.org")
|
||||||
assertThat(authenticationService.getOAuthUrlLoginHint).isEqualTo("mxid:@alice:example.org")
|
assertThat(authenticationService.getOAuthUrlLoginHint).isEqualTo("mxid:@alice:example.org")
|
||||||
cancelAndIgnoreRemainingEvents()
|
cancelAndIgnoreRemainingEvents()
|
||||||
}
|
}
|
||||||
@@ -494,7 +511,7 @@ class ConfirmAccountProviderPresenterTest {
|
|||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
initialState.eventSink(ConfirmAccountProviderEvent.Continue("@alice:example.org:8448"))
|
initialState.eventSink(ConfirmAccountProviderEvent.Continue("@alice:example.org:8448"))
|
||||||
awaitLoginMode { it is AsyncData.Success }
|
awaitLoginMode { it is AsyncData.Success }
|
||||||
assertThat(submittedUrls.first()).isEqualTo("https://example.org:8448")
|
assertThat(submittedUrls.first()).isEqualTo("example.org:8448")
|
||||||
cancelAndIgnoreRemainingEvents()
|
cancelAndIgnoreRemainingEvents()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -570,7 +587,7 @@ class ConfirmAccountProviderPresenterTest {
|
|||||||
accountProviderDataSource = accountProviderDataSource,
|
accountProviderDataSource = accountProviderDataSource,
|
||||||
defaultAccountProviderAccessControl = DefaultAccountProviderAccessControl(
|
defaultAccountProviderAccessControl = DefaultAccountProviderAccessControl(
|
||||||
enterpriseService = FakeEnterpriseService(
|
enterpriseService = FakeEnterpriseService(
|
||||||
isAllowedToConnectToHomeserverResult = { true },
|
isAllowedToConnectToAccountProviderResult = { true },
|
||||||
isElementProEnforcedResult = { false },
|
isElementProEnforcedResult = { false },
|
||||||
),
|
),
|
||||||
isEnterpriseBuild = { false },
|
isEnterpriseBuild = { false },
|
||||||
|
|||||||
+3
-3
@@ -9,11 +9,11 @@
|
|||||||
package io.element.android.features.login.impl.screens.loginpassword
|
package io.element.android.features.login.impl.screens.loginpassword
|
||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.appconfig.AuthenticationConfig
|
|
||||||
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
|
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
|
||||||
import io.element.android.features.login.impl.accountprovider.SaveAccountProviderToHistory
|
import io.element.android.features.login.impl.accountprovider.SaveAccountProviderToHistory
|
||||||
import io.element.android.features.login.impl.accountprovider.anAccountProviderDataSource
|
import io.element.android.features.login.impl.accountprovider.anAccountProviderDataSource
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
|
import io.element.android.libraries.matrix.api.accountprovider.matrixOrgAccountProvider
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
||||||
import io.element.android.libraries.matrix.test.A_PASSWORD
|
import io.element.android.libraries.matrix.test.A_PASSWORD
|
||||||
@@ -39,7 +39,7 @@ class LoginPasswordPresenterTest {
|
|||||||
fun `present - initial state`() = runTest {
|
fun `present - initial state`() = runTest {
|
||||||
createLoginPasswordPresenter().test {
|
createLoginPasswordPresenter().test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.accountProvider.url).isEqualTo(AuthenticationConfig.MATRIX_ORG_URL)
|
assertThat(initialState.accountProvider).isEqualTo(matrixOrgAccountProvider)
|
||||||
assertThat(initialState.formState).isEqualTo(LoginFormState.Default)
|
assertThat(initialState.formState).isEqualTo(LoginFormState.Default)
|
||||||
assertThat(initialState.loginAction).isEqualTo(AsyncData.Uninitialized)
|
assertThat(initialState.loginAction).isEqualTo(AsyncData.Uninitialized)
|
||||||
assertThat(initialState.submitEnabled).isFalse()
|
assertThat(initialState.submitEnabled).isFalse()
|
||||||
@@ -127,7 +127,7 @@ class LoginPasswordPresenterTest {
|
|||||||
val loggedInState = awaitItem()
|
val loggedInState = awaitItem()
|
||||||
assertThat(loggedInState.loginAction).isEqualTo(AsyncData.Success(A_SESSION_ID))
|
assertThat(loggedInState.loginAction).isEqualTo(AsyncData.Success(A_SESSION_ID))
|
||||||
assertThat(appPreferencesStore.getHomeserverHistoryFlow().first())
|
assertThat(appPreferencesStore.getHomeserverHistoryFlow().first())
|
||||||
.containsExactly(AuthenticationConfig.MATRIX_ORG_URL)
|
.containsExactly(matrixOrgAccountProvider.serverName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+65
-14
@@ -9,7 +9,6 @@
|
|||||||
package io.element.android.features.login.impl.screens.onboarding
|
package io.element.android.features.login.impl.screens.onboarding
|
||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.appconfig.AuthenticationConfig
|
|
||||||
import io.element.android.appconfig.OnBoardingConfig
|
import io.element.android.appconfig.OnBoardingConfig
|
||||||
import io.element.android.features.enterprise.api.EnterpriseService
|
import io.element.android.features.enterprise.api.EnterpriseService
|
||||||
import io.element.android.features.enterprise.api.IsEnterpriseBuild
|
import io.element.android.features.enterprise.api.IsEnterpriseBuild
|
||||||
@@ -22,6 +21,8 @@ import io.element.android.features.login.impl.localnetwork.LocalNetworkPermissio
|
|||||||
import io.element.android.features.login.impl.login.LoginModePresenter
|
import io.element.android.features.login.impl.login.LoginModePresenter
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.core.meta.BuildMeta
|
import io.element.android.libraries.core.meta.BuildMeta
|
||||||
|
import io.element.android.libraries.matrix.api.accountprovider.AccountProvider
|
||||||
|
import io.element.android.libraries.matrix.api.accountprovider.matrixOrgAccountProvider
|
||||||
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
||||||
import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER
|
import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER
|
||||||
import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER_2
|
import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER_2
|
||||||
@@ -30,6 +31,7 @@ import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
|||||||
import io.element.android.libraries.matrix.test.A_HOMESERVER_URL
|
import io.element.android.libraries.matrix.test.A_HOMESERVER_URL
|
||||||
import io.element.android.libraries.matrix.test.A_HOMESERVER_URL_2
|
import io.element.android.libraries.matrix.test.A_HOMESERVER_URL_2
|
||||||
import io.element.android.libraries.matrix.test.A_LOGIN_HINT
|
import io.element.android.libraries.matrix.test.A_LOGIN_HINT
|
||||||
|
import io.element.android.libraries.matrix.test.accountprovider.anAccountProviderManaged
|
||||||
import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService
|
import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService
|
||||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||||
import io.element.android.libraries.oauth.api.OAuthActionFlow
|
import io.element.android.libraries.oauth.api.OAuthActionFlow
|
||||||
@@ -82,7 +84,8 @@ class OnBoardingPresenterTest {
|
|||||||
val presenter = createPresenter(
|
val presenter = createPresenter(
|
||||||
buildMeta = buildMeta,
|
buildMeta = buildMeta,
|
||||||
enterpriseService = FakeEnterpriseService(
|
enterpriseService = FakeEnterpriseService(
|
||||||
defaultHomeserverListResult = { listOf(ACCOUNT_PROVIDER_FROM_CONFIG, EnterpriseService.ANY_ACCOUNT_PROVIDER) },
|
accountProviderAllowListResult = { listOf(anAccountProviderManaged(serverName = ACCOUNT_PROVIDER_FROM_CONFIG)) },
|
||||||
|
canConnectToAnyAccountProviderResult = { true },
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
@@ -184,15 +187,16 @@ class OnBoardingPresenterTest {
|
|||||||
showBackButton = false,
|
showBackButton = false,
|
||||||
),
|
),
|
||||||
enterpriseService = FakeEnterpriseService(
|
enterpriseService = FakeEnterpriseService(
|
||||||
defaultHomeserverListResult = { listOf(ACCOUNT_PROVIDER_FROM_CONFIG, EnterpriseService.ANY_ACCOUNT_PROVIDER) },
|
accountProviderAllowListResult = { listOf(anAccountProviderManaged(serverName = ACCOUNT_PROVIDER_FROM_CONFIG)) },
|
||||||
isAllowedToConnectToHomeserverResult = { true },
|
canConnectToAnyAccountProviderResult = { true },
|
||||||
|
isAllowedToConnectToAccountProviderResult = { true },
|
||||||
isElementProEnforcedResult = { false },
|
isElementProEnforcedResult = { false },
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
skipItems(3)
|
skipItems(3)
|
||||||
awaitItem().also {
|
awaitItem().also {
|
||||||
assertThat(it.defaultAccountProvider).isEqualTo(ACCOUNT_PROVIDER_FROM_LINK)
|
assertThat(it.defaultAccountProvider).isEqualTo(AccountProvider.Generic(ACCOUNT_PROVIDER_FROM_LINK))
|
||||||
assertThat(it.canLoginWithQrCode).isFalse()
|
assertThat(it.canLoginWithQrCode).isFalse()
|
||||||
assertThat(it.canCreateAccount).isFalse()
|
assertThat(it.canCreateAccount).isFalse()
|
||||||
}
|
}
|
||||||
@@ -208,8 +212,14 @@ class OnBoardingPresenterTest {
|
|||||||
showBackButton = false,
|
showBackButton = false,
|
||||||
),
|
),
|
||||||
enterpriseService = FakeEnterpriseService(
|
enterpriseService = FakeEnterpriseService(
|
||||||
defaultHomeserverListResult = { listOf(ACCOUNT_PROVIDER_FROM_CONFIG, ACCOUNT_PROVIDER_FROM_CONFIG_2) },
|
accountProviderAllowListResult = {
|
||||||
isAllowedToConnectToHomeserverResult = { false },
|
listOf(
|
||||||
|
anAccountProviderManaged(serverName = ACCOUNT_PROVIDER_FROM_CONFIG),
|
||||||
|
anAccountProviderManaged(serverName = ACCOUNT_PROVIDER_FROM_CONFIG_2)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
canConnectToAnyAccountProviderResult = { false },
|
||||||
|
isAllowedToConnectToAccountProviderResult = { false },
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
@@ -231,19 +241,60 @@ class OnBoardingPresenterTest {
|
|||||||
showBackButton = false,
|
showBackButton = false,
|
||||||
),
|
),
|
||||||
enterpriseService = FakeEnterpriseService(
|
enterpriseService = FakeEnterpriseService(
|
||||||
defaultHomeserverListResult = { listOf(ACCOUNT_PROVIDER_FROM_CONFIG) },
|
accountProviderAllowListResult = { listOf(anAccountProviderManaged(serverName = ACCOUNT_PROVIDER_FROM_CONFIG)) },
|
||||||
|
canConnectToAnyAccountProviderResult = { false },
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
awaitItem().also {
|
awaitItem().also {
|
||||||
assertThat(it.defaultAccountProvider).isEqualTo(ACCOUNT_PROVIDER_FROM_CONFIG)
|
assertThat(it.defaultAccountProvider).isEqualTo(anAccountProviderManaged(serverName = ACCOUNT_PROVIDER_FROM_CONFIG))
|
||||||
assertThat(it.canLoginWithQrCode).isTrue()
|
assertThat(it.canLoginWithQrCode).isTrue()
|
||||||
assertThat(it.canCreateAccount).isFalse()
|
assertThat(it.canCreateAccount).isFalse()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - a single configured account provider is not forced when the user may use another one`() = runTest {
|
||||||
|
// A singleton allow list only forces the account provider when the user is not free to use another one.
|
||||||
|
val presenter = createPresenter(
|
||||||
|
enterpriseService = FakeEnterpriseService(
|
||||||
|
accountProviderAllowListResult = { listOf(anAccountProviderManaged(serverName = ACCOUNT_PROVIDER_FROM_CONFIG)) },
|
||||||
|
canConnectToAnyAccountProviderResult = { true },
|
||||||
|
),
|
||||||
|
)
|
||||||
|
presenter.test {
|
||||||
|
awaitItem().also {
|
||||||
|
assertThat(it.defaultAccountProvider).isNull()
|
||||||
|
assertThat(it.mustChooseAccountProvider).isFalse()
|
||||||
|
}
|
||||||
|
skipItems(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - the user must choose when several account providers are configured and enforced`() = runTest {
|
||||||
|
val presenter = createPresenter(
|
||||||
|
enterpriseService = FakeEnterpriseService(
|
||||||
|
accountProviderAllowListResult = {
|
||||||
|
listOf(
|
||||||
|
anAccountProviderManaged(serverName = ACCOUNT_PROVIDER_FROM_CONFIG),
|
||||||
|
anAccountProviderManaged(serverName = ACCOUNT_PROVIDER_FROM_CONFIG_2)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
canConnectToAnyAccountProviderResult = { false },
|
||||||
|
),
|
||||||
|
)
|
||||||
|
presenter.test {
|
||||||
|
awaitItem().also {
|
||||||
|
assertThat(it.defaultAccountProvider).isNull()
|
||||||
|
assertThat(it.mustChooseAccountProvider).isTrue()
|
||||||
|
}
|
||||||
|
skipItems(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - default account provider - login and clear error`() = runTest {
|
fun `present - default account provider - login and clear error`() = runTest {
|
||||||
val authenticationService = FakeMatrixAuthenticationService(
|
val authenticationService = FakeMatrixAuthenticationService(
|
||||||
@@ -259,7 +310,7 @@ class OnBoardingPresenterTest {
|
|||||||
showBackButton = false,
|
showBackButton = false,
|
||||||
),
|
),
|
||||||
enterpriseService = FakeEnterpriseService(
|
enterpriseService = FakeEnterpriseService(
|
||||||
isAllowedToConnectToHomeserverResult = { true },
|
isAllowedToConnectToAccountProviderResult = { true },
|
||||||
isElementProEnforcedResult = { false },
|
isElementProEnforcedResult = { false },
|
||||||
),
|
),
|
||||||
loginModePresenter = createLoginModePresenter(
|
loginModePresenter = createLoginModePresenter(
|
||||||
@@ -270,12 +321,12 @@ class OnBoardingPresenterTest {
|
|||||||
presenter.test {
|
presenter.test {
|
||||||
skipItems(3)
|
skipItems(3)
|
||||||
awaitItem().also {
|
awaitItem().also {
|
||||||
assertThat(it.defaultAccountProvider).isEqualTo(A_HOMESERVER_URL)
|
assertThat(it.defaultAccountProvider).isEqualTo(AccountProvider.Generic(A_HOMESERVER_URL))
|
||||||
assertThat(accountProviderDataSource.flow.first().url).isEqualTo(AuthenticationConfig.MATRIX_ORG_URL)
|
assertThat(accountProviderDataSource.flow.first()).isEqualTo(matrixOrgAccountProvider)
|
||||||
it.eventSink(OnBoardingEvent.OnSignIn(A_HOMESERVER_URL_2))
|
it.eventSink(OnBoardingEvent.OnSignIn(AccountProvider.Generic(A_HOMESERVER_URL_2)))
|
||||||
skipItems(1) // Loading
|
skipItems(1) // Loading
|
||||||
// Account data source has been updated
|
// Account data source has been updated
|
||||||
assertThat(accountProviderDataSource.flow.first().url).isEqualTo(A_HOMESERVER_URL_2)
|
assertThat(accountProviderDataSource.flow.first()).isEqualTo(AccountProvider.Generic(A_HOMESERVER_URL_2))
|
||||||
// Check an error was returned
|
// Check an error was returned
|
||||||
val submittedState = awaitItem()
|
val submittedState = awaitItem()
|
||||||
assertThat(submittedState.loginModeState.loginMode).isInstanceOf(AsyncData.Failure::class.java)
|
assertThat(submittedState.loginModeState.loginMode).isInstanceOf(AsyncData.Failure::class.java)
|
||||||
|
|||||||
+4
-3
@@ -23,6 +23,7 @@ import io.element.android.features.login.impl.R
|
|||||||
import io.element.android.features.login.impl.login.LoginMode
|
import io.element.android.features.login.impl.login.LoginMode
|
||||||
import io.element.android.features.login.impl.login.aLoginModeState
|
import io.element.android.features.login.impl.login.aLoginModeState
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
|
import io.element.android.libraries.matrix.api.accountprovider.AccountProvider
|
||||||
import io.element.android.libraries.matrix.api.auth.OAuthDetails
|
import io.element.android.libraries.matrix.api.auth.OAuthDetails
|
||||||
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
@@ -134,13 +135,13 @@ class OnboardingViewTest : RobolectricTestParameter() {
|
|||||||
val eventSink = EventsRecorder<OnBoardingEvent>()
|
val eventSink = EventsRecorder<OnBoardingEvent>()
|
||||||
setOnboardingView(
|
setOnboardingView(
|
||||||
state = anOnBoardingState(
|
state = anOnBoardingState(
|
||||||
defaultAccountProvider = "element.io",
|
defaultAccountProvider = AccountProvider.Generic("element.io"),
|
||||||
eventSink = eventSink,
|
eventSink = eventSink,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
val buttonText = activity!!.getString(R.string.screen_onboarding_sign_in_to, "element.io")
|
val buttonText = activity!!.getString(R.string.screen_onboarding_sign_in_to, "element.io")
|
||||||
onNodeWithText(buttonText).performClick()
|
onNodeWithText(buttonText).performClick()
|
||||||
eventSink.assertSingle(OnBoardingEvent.OnSignIn("element.io"))
|
eventSink.assertSingle(OnBoardingEvent.OnSignIn(AccountProvider.Generic("element.io")))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -148,7 +149,7 @@ class OnboardingViewTest : RobolectricTestParameter() {
|
|||||||
val eventSink = EventsRecorder<OnBoardingEvent>()
|
val eventSink = EventsRecorder<OnBoardingEvent>()
|
||||||
setOnboardingView(
|
setOnboardingView(
|
||||||
state = anOnBoardingState(
|
state = anOnBoardingState(
|
||||||
defaultAccountProvider = "element.io",
|
defaultAccountProvider = AccountProvider.Generic("element.io"),
|
||||||
loginModeState = aLoginModeState(loginMode = AsyncData.Failure(AN_EXCEPTION)),
|
loginModeState = aLoginModeState(loginMode = AsyncData.Failure(AN_EXCEPTION)),
|
||||||
eventSink = eventSink,
|
eventSink = eventSink,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ android {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(projects.libraries.architecture)
|
implementation(projects.libraries.architecture)
|
||||||
|
implementation(projects.libraries.matrix.api)
|
||||||
implementation(projects.features.login.api)
|
implementation(projects.features.login.api)
|
||||||
implementation(projects.tests.testutils)
|
implementation(projects.tests.testutils)
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-3
@@ -8,12 +8,13 @@
|
|||||||
package io.element.android.features.login.test.accesscontrol
|
package io.element.android.features.login.test.accesscontrol
|
||||||
|
|
||||||
import io.element.android.features.login.api.accesscontrol.AccountProviderAccessControl
|
import io.element.android.features.login.api.accesscontrol.AccountProviderAccessControl
|
||||||
|
import io.element.android.libraries.matrix.api.accountprovider.AccountProvider
|
||||||
import io.element.android.tests.testutils.lambda.lambdaError
|
import io.element.android.tests.testutils.lambda.lambdaError
|
||||||
|
|
||||||
class FakeAccountProviderAccessControl(
|
class FakeAccountProviderAccessControl(
|
||||||
private val isAllowedToConnectToAccountProviderResult: (String) -> Boolean = { lambdaError() },
|
private val isAllowedToConnectToAccountProviderResult: (AccountProvider) -> Boolean = { lambdaError() },
|
||||||
) : AccountProviderAccessControl {
|
) : AccountProviderAccessControl {
|
||||||
override suspend fun isAllowedToConnectToAccountProvider(accountProviderUrl: String): Boolean {
|
override suspend fun isAllowedToConnectToAccountProvider(accountProvider: AccountProvider): Boolean {
|
||||||
return isAllowedToConnectToAccountProviderResult(accountProviderUrl)
|
return isAllowedToConnectToAccountProviderResult(accountProvider)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
@@ -39,5 +39,6 @@ fun aVoiceMessagePreviewState() = VoiceMessageState.Preview(
|
|||||||
showCursor = false,
|
showCursor = false,
|
||||||
playbackProgress = 0f,
|
playbackProgress = 0f,
|
||||||
time = 10.seconds,
|
time = 10.seconds,
|
||||||
|
duration = 10.seconds,
|
||||||
waveform = WaveFormSamples.realisticWaveForm,
|
waveform = WaveFormSamples.realisticWaveForm,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ dependencies {
|
|||||||
implementation(projects.libraries.voicerecorder.api)
|
implementation(projects.libraries.voicerecorder.api)
|
||||||
implementation(projects.libraries.mediaplayer.api)
|
implementation(projects.libraries.mediaplayer.api)
|
||||||
implementation(projects.libraries.push.api)
|
implementation(projects.libraries.push.api)
|
||||||
|
implementation(projects.libraries.uiCommon)
|
||||||
implementation(projects.libraries.uiUtils)
|
implementation(projects.libraries.uiUtils)
|
||||||
implementation(projects.libraries.testtags)
|
implementation(projects.libraries.testtags)
|
||||||
implementation(projects.libraries.wellknown.api)
|
implementation(projects.libraries.wellknown.api)
|
||||||
|
|||||||
+2
@@ -18,6 +18,8 @@ sealed interface MessagesEvent {
|
|||||||
data class ToggleReaction(val emoji: String, val eventOrTransactionId: EventOrTransactionId) : MessagesEvent
|
data class ToggleReaction(val emoji: String, val eventOrTransactionId: EventOrTransactionId) : MessagesEvent
|
||||||
data class InviteDialogDismissed(val action: InviteDialogAction) : MessagesEvent
|
data class InviteDialogDismissed(val action: InviteDialogAction) : MessagesEvent
|
||||||
data class OnUserClicked(val user: MatrixUser) : MessagesEvent
|
data class OnUserClicked(val user: MatrixUser) : MessagesEvent
|
||||||
|
data class ConfirmRedact(val reason: String?) : MessagesEvent
|
||||||
|
data object CancelRedact : MessagesEvent
|
||||||
data object StopLiveLocationShare : MessagesEvent
|
data object StopLiveLocationShare : MessagesEvent
|
||||||
data object ShowLiveLocationShare : MessagesEvent
|
data object ShowLiveLocationShare : MessagesEvent
|
||||||
data object MarkAsFullyReadAndExit : MessagesEvent
|
data object MarkAsFullyReadAndExit : MessagesEvent
|
||||||
|
|||||||
+8
-1
@@ -51,6 +51,7 @@ import io.element.android.libraries.androidutils.system.toast
|
|||||||
import io.element.android.libraries.architecture.NodeInputs
|
import io.element.android.libraries.architecture.NodeInputs
|
||||||
import io.element.android.libraries.architecture.callback
|
import io.element.android.libraries.architecture.callback
|
||||||
import io.element.android.libraries.architecture.inputs
|
import io.element.android.libraries.architecture.inputs
|
||||||
|
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||||
import io.element.android.libraries.designsystem.utils.OnLifecycleEvent
|
import io.element.android.libraries.designsystem.utils.OnLifecycleEvent
|
||||||
import io.element.android.libraries.di.RoomScope
|
import io.element.android.libraries.di.RoomScope
|
||||||
import io.element.android.libraries.di.annotations.ApplicationContext
|
import io.element.android.libraries.di.annotations.ApplicationContext
|
||||||
@@ -103,6 +104,7 @@ class MessagesNode(
|
|||||||
private val roomMemberModerationRenderer: RoomMemberModerationRenderer,
|
private val roomMemberModerationRenderer: RoomMemberModerationRenderer,
|
||||||
private val eventContentValidationCache: EventContentValidationCache,
|
private val eventContentValidationCache: EventContentValidationCache,
|
||||||
private val emojiPickerRenderer: EmojiPickerRenderer,
|
private val emojiPickerRenderer: EmojiPickerRenderer,
|
||||||
|
private val dispatchers: CoroutineDispatchers,
|
||||||
) : Node(buildContext, plugins = plugins), MessagesNavigator {
|
) : Node(buildContext, plugins = plugins), MessagesNavigator {
|
||||||
data class Inputs(
|
data class Inputs(
|
||||||
val focusedEventId: EventId?,
|
val focusedEventId: EventId?,
|
||||||
@@ -111,7 +113,12 @@ class MessagesNode(
|
|||||||
private val inputs = inputs<Inputs>()
|
private val inputs = inputs<Inputs>()
|
||||||
private val callback: Callback = callback()
|
private val callback: Callback = callback()
|
||||||
|
|
||||||
private val timelineController = TimelineController(room, room.liveTimeline)
|
private val timelineController = TimelineController(
|
||||||
|
room = room,
|
||||||
|
liveTimeline = room.liveTimeline,
|
||||||
|
roomCoroutineScope = room.roomCoroutineScope,
|
||||||
|
dispatchers = dispatchers,
|
||||||
|
)
|
||||||
private val presenter = presenterFactory.create(
|
private val presenter = presenterFactory.create(
|
||||||
navigator = this,
|
navigator = this,
|
||||||
composerPresenter = messageComposerPresenterFactory.create(timelineController, this, threadRoot = null),
|
composerPresenter = messageComposerPresenterFactory.create(timelineController, this, threadRoot = null),
|
||||||
|
|||||||
+32
-3
@@ -58,6 +58,7 @@ import io.element.android.features.roomcall.api.RoomCallState
|
|||||||
import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvent
|
import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvent
|
||||||
import io.element.android.features.roommembermoderation.api.RoomMemberModerationState
|
import io.element.android.features.roommembermoderation.api.RoomMemberModerationState
|
||||||
import io.element.android.libraries.androidutils.clipboard.ClipboardHelper
|
import io.element.android.libraries.androidutils.clipboard.ClipboardHelper
|
||||||
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||||
@@ -84,6 +85,7 @@ import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibilit
|
|||||||
import io.element.android.libraries.matrix.api.room.powerlevels.permissionsAsState
|
import io.element.android.libraries.matrix.api.room.powerlevels.permissionsAsState
|
||||||
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.timeline.item.event.EventOrTransactionId
|
import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId
|
||||||
|
import io.element.android.libraries.matrix.api.timeline.item.event.toEventOrTransactionId
|
||||||
import io.element.android.libraries.matrix.ui.messages.reply.map
|
import io.element.android.libraries.matrix.ui.messages.reply.map
|
||||||
import io.element.android.libraries.matrix.ui.model.dmUserStatus
|
import io.element.android.libraries.matrix.ui.model.dmUserStatus
|
||||||
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
||||||
@@ -241,6 +243,8 @@ class MessagesPresenter(
|
|||||||
onPauseOrDispose {}
|
onPauseOrDispose {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val redactEventAction = remember { mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized) }
|
||||||
|
|
||||||
fun handleEvent(event: MessagesEvent) {
|
fun handleEvent(event: MessagesEvent) {
|
||||||
when (event) {
|
when (event) {
|
||||||
is MessagesEvent.HandleAction -> {
|
is MessagesEvent.HandleAction -> {
|
||||||
@@ -251,8 +255,21 @@ class MessagesPresenter(
|
|||||||
enableTextFormatting = composerState.showTextFormatting,
|
enableTextFormatting = composerState.showTextFormatting,
|
||||||
timelineState = timelineState,
|
timelineState = timelineState,
|
||||||
timelineProtectionState = timelineProtectionState,
|
timelineProtectionState = timelineProtectionState,
|
||||||
|
redactEventAction = redactEventAction,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
is MessagesEvent.ConfirmRedact -> {
|
||||||
|
val confirming = redactEventAction.value as? MessagesState.ConfirmingRedaction
|
||||||
|
redactEventAction.value = AsyncAction.Uninitialized
|
||||||
|
if (confirming != null) {
|
||||||
|
localCoroutineScope.launch {
|
||||||
|
redact(confirming.eventId.toEventOrTransactionId(), event.reason?.takeIf { it.isNotBlank() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MessagesEvent.CancelRedact -> {
|
||||||
|
redactEventAction.value = AsyncAction.Uninitialized
|
||||||
|
}
|
||||||
is MessagesEvent.ToggleReaction -> {
|
is MessagesEvent.ToggleReaction -> {
|
||||||
localCoroutineScope.toggleReaction(event.emoji, event.eventOrTransactionId)
|
localCoroutineScope.toggleReaction(event.emoji, event.eventOrTransactionId)
|
||||||
}
|
}
|
||||||
@@ -331,6 +348,7 @@ class MessagesPresenter(
|
|||||||
hasUnreadThreads = false,
|
hasUnreadThreads = false,
|
||||||
),
|
),
|
||||||
showLiveLocationShareBanner = isCurrentlySharingLiveLocationInRoom && timelineState.timelineMode !is Timeline.Mode.Thread,
|
showLiveLocationShareBanner = isCurrentlySharingLiveLocationInRoom && timelineState.timelineMode !is Timeline.Mode.Thread,
|
||||||
|
redactEventAction = redactEventAction.value,
|
||||||
eventSink = ::handleEvent,
|
eventSink = ::handleEvent,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -369,12 +387,13 @@ class MessagesPresenter(
|
|||||||
timelineProtectionState: TimelineProtectionState,
|
timelineProtectionState: TimelineProtectionState,
|
||||||
enableTextFormatting: Boolean,
|
enableTextFormatting: Boolean,
|
||||||
timelineState: TimelineState,
|
timelineState: TimelineState,
|
||||||
|
redactEventAction: MutableState<AsyncAction<Unit>>,
|
||||||
) = launch {
|
) = launch {
|
||||||
when (action) {
|
when (action) {
|
||||||
TimelineItemAction.CopyText -> handleCopyContents(targetEvent)
|
TimelineItemAction.CopyText -> handleCopyContents(targetEvent)
|
||||||
TimelineItemAction.CopyCaption -> handleCopyCaption(targetEvent)
|
TimelineItemAction.CopyCaption -> handleCopyCaption(targetEvent)
|
||||||
TimelineItemAction.CopyLink -> handleCopyLink(targetEvent)
|
TimelineItemAction.CopyLink -> handleCopyLink(targetEvent)
|
||||||
TimelineItemAction.Redact -> handleActionRedact(targetEvent)
|
TimelineItemAction.Redact -> handleActionRedact(targetEvent, redactEventAction)
|
||||||
TimelineItemAction.Edit,
|
TimelineItemAction.Edit,
|
||||||
TimelineItemAction.EditPoll -> handleActionEdit(targetEvent, composerState, enableTextFormatting)
|
TimelineItemAction.EditPoll -> handleActionEdit(targetEvent, composerState, enableTextFormatting)
|
||||||
TimelineItemAction.AddCaption -> handleActionAddCaption(targetEvent, composerState)
|
TimelineItemAction.AddCaption -> handleActionAddCaption(targetEvent, composerState)
|
||||||
@@ -496,9 +515,19 @@ class MessagesPresenter(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun handleActionRedact(event: TimelineItem.Event) {
|
private suspend fun handleActionRedact(event: TimelineItem.Event, redactEventAction: MutableState<AsyncAction<Unit>>) {
|
||||||
|
val eventId = event.eventId
|
||||||
|
if (eventId == null) {
|
||||||
|
// The message was never sent, so there is nobody to give a reason to.
|
||||||
|
redact(event.eventOrTransactionId, reason = null)
|
||||||
|
} else {
|
||||||
|
redactEventAction.value = MessagesState.ConfirmingRedaction(eventId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun redact(eventOrTransactionId: EventOrTransactionId, reason: String?) {
|
||||||
timelineController.invokeOnCurrentTimeline {
|
timelineController.invokeOnCurrentTimeline {
|
||||||
redactEvent(eventOrTransactionId = event.eventOrTransactionId, reason = null)
|
redactEvent(eventOrTransactionId = eventOrTransactionId, reason = reason)
|
||||||
.onFailure { Timber.e(it) }
|
.onFailure { Timber.e(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+8
@@ -21,9 +21,11 @@ import io.element.android.features.messages.impl.timeline.components.receipt.bot
|
|||||||
import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionState
|
import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionState
|
||||||
import io.element.android.features.roomcall.api.RoomCallState
|
import io.element.android.features.roomcall.api.RoomCallState
|
||||||
import io.element.android.features.roommembermoderation.api.RoomMemberModerationState
|
import io.element.android.features.roommembermoderation.api.RoomMemberModerationState
|
||||||
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
|
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
|
||||||
|
import io.element.android.libraries.matrix.api.core.EventId
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
|
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
|
||||||
import io.element.android.libraries.matrix.api.room.tombstone.SuccessorRoom
|
import io.element.android.libraries.matrix.api.room.tombstone.SuccessorRoom
|
||||||
@@ -61,6 +63,7 @@ data class MessagesState(
|
|||||||
val successorRoom: SuccessorRoom?,
|
val successorRoom: SuccessorRoom?,
|
||||||
val threads: Threads,
|
val threads: Threads,
|
||||||
val showLiveLocationShareBanner: Boolean,
|
val showLiveLocationShareBanner: Boolean,
|
||||||
|
val redactEventAction: AsyncAction<Unit>,
|
||||||
val eventSink: (MessagesEvent) -> Unit
|
val eventSink: (MessagesEvent) -> Unit
|
||||||
) {
|
) {
|
||||||
val isTombstoned = successorRoom != null
|
val isTombstoned = successorRoom != null
|
||||||
@@ -69,6 +72,11 @@ data class MessagesState(
|
|||||||
val hasThreads: Boolean,
|
val hasThreads: Boolean,
|
||||||
val hasUnreadThreads: Boolean,
|
val hasUnreadThreads: Boolean,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/** The user asked to remove [eventId] and has to confirm, optionally giving a reason. */
|
||||||
|
data class ConfirmingRedaction(
|
||||||
|
val eventId: EventId,
|
||||||
|
) : AsyncAction.Confirming
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Type of "shared history" icon to show in the top bar. */
|
/** Type of "shared history" icon to show in the top bar. */
|
||||||
|
|||||||
+5
@@ -41,10 +41,12 @@ import io.element.android.features.roomcall.api.aStandByCallState
|
|||||||
import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvent
|
import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvent
|
||||||
import io.element.android.features.roommembermoderation.api.RoomMemberModerationPermissions
|
import io.element.android.features.roommembermoderation.api.RoomMemberModerationPermissions
|
||||||
import io.element.android.features.roommembermoderation.api.RoomMemberModerationState
|
import io.element.android.features.roommembermoderation.api.RoomMemberModerationState
|
||||||
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
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.designsystem.preview.ROOM_NAME
|
import io.element.android.libraries.designsystem.preview.ROOM_NAME
|
||||||
|
import io.element.android.libraries.matrix.api.core.EventId
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.core.ThreadId
|
import io.element.android.libraries.matrix.api.core.ThreadId
|
||||||
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
|
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
|
||||||
@@ -92,6 +94,7 @@ open class MessagesStatePreviewParam : PreviewParameterProvider<MessagesState> {
|
|||||||
composerState = aMessageComposerState(textEditorState = aTextEditorStateMarkdown()),
|
composerState = aMessageComposerState(textEditorState = aTextEditorStateMarkdown()),
|
||||||
identityChangeState = anIdentityChangeState(listOf(aRoomMemberIdentityStateChange()))
|
identityChangeState = anIdentityChangeState(listOf(aRoomMemberIdentityStateChange()))
|
||||||
),
|
),
|
||||||
|
aMessagesState(redactEventAction = MessagesState.ConfirmingRedaction(EventId("\$anEventId"))),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,6 +134,7 @@ fun aMessagesState(
|
|||||||
isCurrentlySharingLiveLocationInRoom: Boolean = false,
|
isCurrentlySharingLiveLocationInRoom: Boolean = false,
|
||||||
dmUserStatus: DisplayedStatus? = null,
|
dmUserStatus: DisplayedStatus? = null,
|
||||||
eventSink: (MessagesEvent) -> Unit = {},
|
eventSink: (MessagesEvent) -> Unit = {},
|
||||||
|
redactEventAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||||
) = MessagesState(
|
) = MessagesState(
|
||||||
roomId = RoomId("!id:domain"),
|
roomId = RoomId("!id:domain"),
|
||||||
roomName = roomName,
|
roomName = roomName,
|
||||||
@@ -161,6 +165,7 @@ fun aMessagesState(
|
|||||||
threads = threads,
|
threads = threads,
|
||||||
showLiveLocationShareBanner = isCurrentlySharingLiveLocationInRoom,
|
showLiveLocationShareBanner = isCurrentlySharingLiveLocationInRoom,
|
||||||
dmUserStatus = dmUserStatus,
|
dmUserStatus = dmUserStatus,
|
||||||
|
redactEventAction = redactEventAction,
|
||||||
eventSink = eventSink,
|
eventSink = eventSink,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+15
@@ -104,6 +104,7 @@ import io.element.android.libraries.designsystem.atomic.molecules.ComposerAlertM
|
|||||||
import io.element.android.libraries.designsystem.components.ExpandableBottomSheetLayout
|
import io.element.android.libraries.designsystem.components.ExpandableBottomSheetLayout
|
||||||
import io.element.android.libraries.designsystem.components.ExpandableBottomSheetLayoutState
|
import io.element.android.libraries.designsystem.components.ExpandableBottomSheetLayoutState
|
||||||
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
|
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
|
||||||
|
import io.element.android.libraries.designsystem.components.dialogs.TextFieldDialog
|
||||||
import io.element.android.libraries.designsystem.components.rememberExpandableBottomSheetLayoutState
|
import io.element.android.libraries.designsystem.components.rememberExpandableBottomSheetLayoutState
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
@@ -455,6 +456,20 @@ fun MessagesView(
|
|||||||
state.timelineState.eventSink(TimelineEvent.HideSendFailureDialog)
|
state.timelineState.eventSink(TimelineEvent.HideSendFailureDialog)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (state.redactEventAction is MessagesState.ConfirmingRedaction) {
|
||||||
|
TextFieldDialog(
|
||||||
|
title = stringResource(R.string.screen_room_confirm_removal_title),
|
||||||
|
content = stringResource(R.string.screen_room_confirm_removal_message),
|
||||||
|
placeholder = stringResource(R.string.screen_room_confirm_removal_reason_placeholder_android),
|
||||||
|
supportingText = stringResource(R.string.screen_room_confirm_removal_reason_supporting_text),
|
||||||
|
value = null,
|
||||||
|
submitText = stringResource(CommonStrings.action_remove),
|
||||||
|
destructiveSubmit = true,
|
||||||
|
onSubmit = { reason -> state.eventSink(MessagesEvent.ConfirmRedact(reason)) },
|
||||||
|
onDismissRequest = { state.eventSink(MessagesEvent.CancelRedact) },
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
+1
-1
@@ -36,7 +36,6 @@ import io.element.android.features.messages.impl.timeline.components.TimelineIte
|
|||||||
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionBottomSheet
|
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionBottomSheet
|
||||||
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionEvent
|
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionEvent
|
||||||
import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView
|
import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView
|
||||||
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData
|
|
||||||
import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryEvent
|
import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryEvent
|
||||||
import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryView
|
import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryView
|
||||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||||
@@ -58,6 +57,7 @@ import io.element.android.libraries.emoji.api.picker.EmojiPickerRenderer
|
|||||||
import io.element.android.libraries.emoji.api.picker.NoOpEmojiPickerRenderer
|
import io.element.android.libraries.emoji.api.picker.NoOpEmojiPickerRenderer
|
||||||
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.user.MatrixUser
|
||||||
|
import io.element.android.libraries.ui.common.layout.ContentAvoidingLayoutData
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import io.element.android.services.analytics.compose.LocalAnalyticsService
|
import io.element.android.services.analytics.compose.LocalAnalyticsService
|
||||||
import io.element.android.services.analyticsproviders.api.trackers.captureInteraction
|
import io.element.android.services.analyticsproviders.api.trackers.captureInteraction
|
||||||
|
|||||||
+8
-1
@@ -54,6 +54,7 @@ import io.element.android.libraries.architecture.NodeInputs
|
|||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.architecture.callback
|
import io.element.android.libraries.architecture.callback
|
||||||
import io.element.android.libraries.architecture.inputs
|
import io.element.android.libraries.architecture.inputs
|
||||||
|
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||||
import io.element.android.libraries.designsystem.utils.OnLifecycleEvent
|
import io.element.android.libraries.designsystem.utils.OnLifecycleEvent
|
||||||
import io.element.android.libraries.di.RoomScope
|
import io.element.android.libraries.di.RoomScope
|
||||||
import io.element.android.libraries.emoji.api.picker.EmojiPickerRenderer
|
import io.element.android.libraries.emoji.api.picker.EmojiPickerRenderer
|
||||||
@@ -95,6 +96,7 @@ class ThreadedMessagesNode(
|
|||||||
private val appNavigationStateService: AppNavigationStateService,
|
private val appNavigationStateService: AppNavigationStateService,
|
||||||
private val roomMemberModerationRenderer: RoomMemberModerationRenderer,
|
private val roomMemberModerationRenderer: RoomMemberModerationRenderer,
|
||||||
private val emojiPickerRenderer: EmojiPickerRenderer,
|
private val emojiPickerRenderer: EmojiPickerRenderer,
|
||||||
|
private val dispatchers: CoroutineDispatchers,
|
||||||
) : Node(buildContext, plugins = plugins), MessagesNavigator {
|
) : Node(buildContext, plugins = plugins), MessagesNavigator {
|
||||||
data class Inputs(
|
data class Inputs(
|
||||||
val threadRootEventId: ThreadId,
|
val threadRootEventId: ThreadId,
|
||||||
@@ -113,7 +115,12 @@ class ThreadedMessagesNode(
|
|||||||
*/
|
*/
|
||||||
private suspend fun createPresenter(): Presenter<MessagesState> {
|
private suspend fun createPresenter(): Presenter<MessagesState> {
|
||||||
val threadedTimeline = room.createTimeline(CreateTimelineParams.Threaded(threadRootEventId = inputs.threadRootEventId)).getOrThrow()
|
val threadedTimeline = room.createTimeline(CreateTimelineParams.Threaded(threadRootEventId = inputs.threadRootEventId)).getOrThrow()
|
||||||
val timelineController = TimelineController(room, threadedTimeline)
|
val timelineController = TimelineController(
|
||||||
|
room,
|
||||||
|
liveTimeline = threadedTimeline,
|
||||||
|
roomCoroutineScope = room.roomCoroutineScope,
|
||||||
|
dispatchers = dispatchers,
|
||||||
|
)
|
||||||
this.timelineController = timelineController
|
this.timelineController = timelineController
|
||||||
return presenterFactory.create(
|
return presenterFactory.create(
|
||||||
navigator = this,
|
navigator = this,
|
||||||
|
|||||||
+6
-2
@@ -12,7 +12,10 @@ import dev.zacsweers.metro.ContributesBinding
|
|||||||
import dev.zacsweers.metro.SingleIn
|
import dev.zacsweers.metro.SingleIn
|
||||||
import dev.zacsweers.metro.binding
|
import dev.zacsweers.metro.binding
|
||||||
import io.element.android.features.messages.impl.timeline.di.LiveTimeline
|
import io.element.android.features.messages.impl.timeline.di.LiveTimeline
|
||||||
|
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||||
|
import io.element.android.libraries.core.coroutine.childScope
|
||||||
import io.element.android.libraries.di.RoomScope
|
import io.element.android.libraries.di.RoomScope
|
||||||
|
import io.element.android.libraries.di.annotations.RoomCoroutineScope
|
||||||
import io.element.android.libraries.matrix.api.core.EventId
|
import io.element.android.libraries.matrix.api.core.EventId
|
||||||
import io.element.android.libraries.matrix.api.core.ThreadId
|
import io.element.android.libraries.matrix.api.core.ThreadId
|
||||||
import io.element.android.libraries.matrix.api.room.CreateTimelineParams
|
import io.element.android.libraries.matrix.api.room.CreateTimelineParams
|
||||||
@@ -23,7 +26,6 @@ import io.element.android.libraries.matrix.api.timeline.TimelineProvider
|
|||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.SupervisorJob
|
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
@@ -47,8 +49,10 @@ import java.util.Optional
|
|||||||
class TimelineController(
|
class TimelineController(
|
||||||
private val room: JoinedRoom,
|
private val room: JoinedRoom,
|
||||||
@LiveTimeline private val liveTimeline: Timeline,
|
@LiveTimeline private val liveTimeline: Timeline,
|
||||||
|
@RoomCoroutineScope private val roomCoroutineScope: CoroutineScope,
|
||||||
|
dispatchers: CoroutineDispatchers,
|
||||||
) : Closeable, TimelineProvider {
|
) : Closeable, TimelineProvider {
|
||||||
private val coroutineScope = CoroutineScope(SupervisorJob())
|
private val coroutineScope = roomCoroutineScope.childScope(dispatchers.computation, "TimelineController")
|
||||||
|
|
||||||
private val liveTimelineFlow = flowOf(liveTimeline)
|
private val liveTimelineFlow = flowOf(liveTimeline)
|
||||||
private val detachedTimelineFlow = MutableStateFlow<Optional<Timeline>>(Optional.empty())
|
private val detachedTimelineFlow = MutableStateFlow<Optional<Timeline>>(Optional.empty())
|
||||||
|
|||||||
+2
-2
@@ -72,8 +72,6 @@ import io.element.android.features.messages.impl.timeline.TimelineEvent
|
|||||||
import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
|
import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
|
||||||
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
|
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
|
||||||
import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView
|
import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView
|
||||||
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayout
|
|
||||||
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData
|
|
||||||
import io.element.android.features.messages.impl.timeline.components.receipt.ReadReceiptViewState
|
import io.element.android.features.messages.impl.timeline.components.receipt.ReadReceiptViewState
|
||||||
import io.element.android.features.messages.impl.timeline.components.receipt.TimelineItemReadReceiptView
|
import io.element.android.features.messages.impl.timeline.components.receipt.TimelineItemReadReceiptView
|
||||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||||
@@ -139,6 +137,8 @@ import io.element.android.libraries.matrix.ui.messages.sender.SenderName
|
|||||||
import io.element.android.libraries.matrix.ui.messages.sender.SenderNameMode
|
import io.element.android.libraries.matrix.ui.messages.sender.SenderNameMode
|
||||||
import io.element.android.libraries.testtags.TestTags
|
import io.element.android.libraries.testtags.TestTags
|
||||||
import io.element.android.libraries.testtags.testTag
|
import io.element.android.libraries.testtags.testTag
|
||||||
|
import io.element.android.libraries.ui.common.layout.ContentAvoidingLayout
|
||||||
|
import io.element.android.libraries.ui.common.layout.ContentAvoidingLayoutData
|
||||||
import io.element.android.libraries.ui.strings.CommonPlurals
|
import io.element.android.libraries.ui.strings.CommonPlurals
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import io.element.android.libraries.ui.utils.a11y.isTalkbackActive
|
import io.element.android.libraries.ui.utils.a11y.isTalkbackActive
|
||||||
|
|||||||
+1
-1
@@ -23,7 +23,6 @@ import io.element.android.features.messages.impl.timeline.aRedactedMessagesGroup
|
|||||||
import io.element.android.features.messages.impl.timeline.aTimelineRoomInfo
|
import io.element.android.features.messages.impl.timeline.aTimelineRoomInfo
|
||||||
import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView
|
import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView
|
||||||
import io.element.android.features.messages.impl.timeline.components.group.GroupHeaderView
|
import io.element.android.features.messages.impl.timeline.components.group.GroupHeaderView
|
||||||
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData
|
|
||||||
import io.element.android.features.messages.impl.timeline.components.receipt.ReadReceiptViewState
|
import io.element.android.features.messages.impl.timeline.components.receipt.ReadReceiptViewState
|
||||||
import io.element.android.features.messages.impl.timeline.components.receipt.TimelineItemReadReceiptView
|
import io.element.android.features.messages.impl.timeline.components.receipt.TimelineItemReadReceiptView
|
||||||
import io.element.android.features.messages.impl.timeline.groups.isRedactedMessagesGroup
|
import io.element.android.features.messages.impl.timeline.groups.isRedactedMessagesGroup
|
||||||
@@ -35,6 +34,7 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
|||||||
import io.element.android.libraries.matrix.api.core.EventId
|
import io.element.android.libraries.matrix.api.core.EventId
|
||||||
import io.element.android.libraries.matrix.api.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.user.MatrixUser
|
||||||
|
import io.element.android.libraries.ui.common.layout.ContentAvoidingLayoutData
|
||||||
import io.element.android.wysiwyg.link.Link
|
import io.element.android.wysiwyg.link.Link
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
+1
-1
@@ -29,7 +29,6 @@ import io.element.android.compound.theme.ElementTheme
|
|||||||
import io.element.android.features.messages.impl.timeline.TimelineEvent
|
import io.element.android.features.messages.impl.timeline.TimelineEvent
|
||||||
import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
|
import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
|
||||||
import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView
|
import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView
|
||||||
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData
|
|
||||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.RtcNotificationState
|
import io.element.android.features.messages.impl.timeline.model.event.RtcNotificationState
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLegacyCallInviteContent
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLegacyCallInviteContent
|
||||||
@@ -46,6 +45,7 @@ import io.element.android.libraries.designsystem.text.toPx
|
|||||||
import io.element.android.libraries.matrix.api.core.EventId
|
import io.element.android.libraries.matrix.api.core.EventId
|
||||||
import io.element.android.libraries.matrix.api.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.user.MatrixUser
|
||||||
|
import io.element.android.libraries.ui.common.layout.ContentAvoidingLayoutData
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import io.element.android.libraries.ui.utils.a11y.isTalkbackActive
|
import io.element.android.libraries.ui.utils.a11y.isTalkbackActive
|
||||||
import io.element.android.wysiwyg.link.Link
|
import io.element.android.wysiwyg.link.Link
|
||||||
|
|||||||
+2
-2
@@ -22,12 +22,12 @@ import androidx.compose.ui.graphics.vector.ImageVector
|
|||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayout
|
|
||||||
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData
|
|
||||||
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
|
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
|
||||||
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.matrix.ui.media.contentvalidation.ContentValidationValue
|
import io.element.android.libraries.matrix.ui.media.contentvalidation.ContentValidationValue
|
||||||
|
import io.element.android.libraries.ui.common.layout.ContentAvoidingLayout
|
||||||
|
import io.element.android.libraries.ui.common.layout.ContentAvoidingLayoutData
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* package-private, you should only use TimelineItemFileView and TimelineItemAudioView.
|
* package-private, you should only use TimelineItemFileView and TimelineItemAudioView.
|
||||||
|
|||||||
+1
-1
@@ -12,11 +12,11 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||||
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData
|
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContent
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContent
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContentPreviewParam
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContentPreviewParam
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.matrix.ui.media.contentvalidation.ContentValidationValue
|
import io.element.android.libraries.matrix.ui.media.contentvalidation.ContentValidationValue
|
||||||
|
import io.element.android.libraries.ui.common.layout.ContentAvoidingLayoutData
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TimelineItemAudioView(
|
fun TimelineItemAudioView(
|
||||||
|
|||||||
+1
-1
@@ -12,7 +12,6 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData
|
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEncryptedContent
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEncryptedContent
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEncryptedContentPreviewParam
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEncryptedContentPreviewParam
|
||||||
import io.element.android.libraries.designsystem.icons.CompoundDrawables
|
import io.element.android.libraries.designsystem.icons.CompoundDrawables
|
||||||
@@ -20,6 +19,7 @@ import io.element.android.libraries.designsystem.preview.ElementPreview
|
|||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecryptContent
|
import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecryptContent
|
||||||
import io.element.android.libraries.matrix.api.timeline.item.event.UtdCause
|
import io.element.android.libraries.matrix.api.timeline.item.event.UtdCause
|
||||||
|
import io.element.android.libraries.ui.common.layout.ContentAvoidingLayoutData
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
+2
-2
@@ -24,8 +24,6 @@ import androidx.compose.ui.platform.LocalInspectionMode
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
import io.element.android.features.messages.impl.timeline.TimelineEvent
|
import io.element.android.features.messages.impl.timeline.TimelineEvent
|
||||||
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayout
|
|
||||||
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData
|
|
||||||
import io.element.android.features.messages.impl.timeline.di.LocalTimelineItemPresenterFactories
|
import io.element.android.features.messages.impl.timeline.di.LocalTimelineItemPresenterFactories
|
||||||
import io.element.android.features.messages.impl.timeline.di.rememberPresenter
|
import io.element.android.features.messages.impl.timeline.di.rememberPresenter
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAttachmentsContent
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAttachmentsContent
|
||||||
@@ -60,6 +58,8 @@ import io.element.android.libraries.matrix.ui.media.contentvalidation.NotFoundCo
|
|||||||
import io.element.android.libraries.matrix.ui.media.contentvalidation.collectOverallState
|
import io.element.android.libraries.matrix.ui.media.contentvalidation.collectOverallState
|
||||||
import io.element.android.libraries.matrix.ui.media.contentvalidation.rememberEventContentValidationState
|
import io.element.android.libraries.matrix.ui.media.contentvalidation.rememberEventContentValidationState
|
||||||
import io.element.android.libraries.textcomposer.ElementRichTextEditorStyle
|
import io.element.android.libraries.textcomposer.ElementRichTextEditorStyle
|
||||||
|
import io.element.android.libraries.ui.common.layout.ContentAvoidingLayout
|
||||||
|
import io.element.android.libraries.ui.common.layout.ContentAvoidingLayoutData
|
||||||
import io.element.android.libraries.voiceplayer.api.VoiceMessageState
|
import io.element.android.libraries.voiceplayer.api.VoiceMessageState
|
||||||
import io.element.android.wysiwyg.compose.EditorStyledText
|
import io.element.android.wysiwyg.compose.EditorStyledText
|
||||||
import io.element.android.wysiwyg.link.Link
|
import io.element.android.wysiwyg.link.Link
|
||||||
|
|||||||
+1
-1
@@ -13,11 +13,11 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||||
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData
|
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContentPreviewParam
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContentPreviewParam
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.matrix.ui.media.contentvalidation.ContentValidationValue
|
import io.element.android.libraries.matrix.ui.media.contentvalidation.ContentValidationValue
|
||||||
|
import io.element.android.libraries.ui.common.layout.ContentAvoidingLayoutData
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+1
-1
@@ -22,12 +22,12 @@ import androidx.compose.ui.layout.onSizeChanged
|
|||||||
import androidx.compose.ui.text.font.FontStyle
|
import androidx.compose.ui.text.font.FontStyle
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData
|
|
||||||
import io.element.android.libraries.designsystem.icons.CompoundDrawables
|
import io.element.android.libraries.designsystem.icons.CompoundDrawables
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
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.Text
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
|
import io.element.android.libraries.ui.common.layout.ContentAvoidingLayoutData
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TimelineItemInformativeView(
|
fun TimelineItemInformativeView(
|
||||||
|
|||||||
+1
-1
@@ -11,11 +11,11 @@ package io.element.android.features.messages.impl.timeline.components.event
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData
|
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent
|
||||||
import io.element.android.libraries.designsystem.icons.CompoundDrawables
|
import io.element.android.libraries.designsystem.icons.CompoundDrawables
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
|
import io.element.android.libraries.ui.common.layout.ContentAvoidingLayoutData
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
+2
-2
@@ -23,8 +23,6 @@ import androidx.compose.ui.semantics.semantics
|
|||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayout
|
|
||||||
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData
|
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.AN_EMOJI_ONLY_TEXT
|
import io.element.android.features.messages.impl.timeline.model.event.AN_EMOJI_ONLY_TEXT
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContentPreviewParam
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContentPreviewParam
|
||||||
@@ -36,6 +34,8 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
|||||||
import io.element.android.libraries.designsystem.utils.LocalUiTestMode
|
import io.element.android.libraries.designsystem.utils.LocalUiTestMode
|
||||||
import io.element.android.libraries.textcomposer.ElementRichTextEditorStyle
|
import io.element.android.libraries.textcomposer.ElementRichTextEditorStyle
|
||||||
import io.element.android.libraries.textcomposer.mentions.LocalMentionSpanUpdater
|
import io.element.android.libraries.textcomposer.mentions.LocalMentionSpanUpdater
|
||||||
|
import io.element.android.libraries.ui.common.layout.ContentAvoidingLayout
|
||||||
|
import io.element.android.libraries.ui.common.layout.ContentAvoidingLayoutData
|
||||||
import io.element.android.wysiwyg.compose.EditorStyledText
|
import io.element.android.wysiwyg.compose.EditorStyledText
|
||||||
import io.element.android.wysiwyg.link.Link
|
import io.element.android.wysiwyg.link.Link
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -11,11 +11,11 @@ package io.element.android.features.messages.impl.timeline.components.event
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData
|
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent
|
||||||
import io.element.android.libraries.designsystem.icons.CompoundDrawables
|
import io.element.android.libraries.designsystem.icons.CompoundDrawables
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
|
import io.element.android.libraries.ui.common.layout.ContentAvoidingLayoutData
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
+4
-1
@@ -40,7 +40,6 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||||
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData
|
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContentPreviewParam
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContentPreviewParam
|
||||||
import io.element.android.libraries.designsystem.atomic.atoms.PlaybackSpeedButton
|
import io.element.android.libraries.designsystem.atomic.atoms.PlaybackSpeedButton
|
||||||
@@ -52,6 +51,7 @@ import io.element.android.libraries.designsystem.theme.components.Icon
|
|||||||
import io.element.android.libraries.designsystem.theme.components.IconButton
|
import io.element.android.libraries.designsystem.theme.components.IconButton
|
||||||
import io.element.android.libraries.designsystem.theme.components.Text
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
import io.element.android.libraries.matrix.ui.media.contentvalidation.ContentValidationValue
|
import io.element.android.libraries.matrix.ui.media.contentvalidation.ContentValidationValue
|
||||||
|
import io.element.android.libraries.ui.common.layout.ContentAvoidingLayoutData
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import io.element.android.libraries.ui.utils.a11y.isTalkbackActive
|
import io.element.android.libraries.ui.utils.a11y.isTalkbackActive
|
||||||
import io.element.android.libraries.voiceplayer.api.VoiceMessageEvent
|
import io.element.android.libraries.voiceplayer.api.VoiceMessageEvent
|
||||||
@@ -142,6 +142,9 @@ fun TimelineItemVoiceView(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
.height(34.dp),
|
.height(34.dp),
|
||||||
|
isPlaying = state.isPlaying,
|
||||||
|
durationMs = state.durationMs,
|
||||||
|
playbackSpeed = state.playbackSpeed,
|
||||||
seekEnabled = !isTalkbackActive(),
|
seekEnabled = !isTalkbackActive(),
|
||||||
onSeek = { state.eventSink(VoiceMessageEvent.Seek(it)) },
|
onSeek = { state.eventSink(VoiceMessageEvent.Seek(it)) },
|
||||||
)
|
)
|
||||||
|
|||||||
+4
@@ -245,12 +245,16 @@ class DefaultVoiceMessageComposerPresenter(
|
|||||||
val playerTime by remember(playerState, recorderState) { derivedStateOf { displayTime(playerState, recorderState) } }
|
val playerTime by remember(playerState, recorderState) { derivedStateOf { displayTime(playerState, recorderState) } }
|
||||||
val waveform by remember(recorderState) { derivedStateOf { recorderState.finishedWaveform() } }
|
val waveform by remember(recorderState) { derivedStateOf { recorderState.finishedWaveform() } }
|
||||||
|
|
||||||
|
val recorderDuration = (recorderState as VoiceRecorderState.Finished).duration
|
||||||
|
val duration = playerState.durationMs?.milliseconds ?: recorderDuration
|
||||||
|
|
||||||
return VoiceMessageState.Preview(
|
return VoiceMessageState.Preview(
|
||||||
isSending = isSending,
|
isSending = isSending,
|
||||||
isPlaying = playerState.isPlaying,
|
isPlaying = playerState.isPlaying,
|
||||||
showCursor = showCursor,
|
showCursor = showCursor,
|
||||||
playbackProgress = playerState.progress,
|
playbackProgress = playerState.progress,
|
||||||
time = playerTime,
|
time = playerTime,
|
||||||
|
duration = duration,
|
||||||
waveform = waveform,
|
waveform = waveform,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+4
@@ -63,6 +63,7 @@ class VoiceMessageComposerPlayer(
|
|||||||
playState = it.playState,
|
playState = it.playState,
|
||||||
currentPosition = it.currentPosition,
|
currentPosition = it.currentPosition,
|
||||||
progress = calcProgress(it),
|
progress = calcProgress(it),
|
||||||
|
durationMs = if (it.playState != PlayState.Stopped) it.duration else null,
|
||||||
)
|
)
|
||||||
}.distinctUntilChanged()
|
}.distinctUntilChanged()
|
||||||
|
|
||||||
@@ -183,17 +184,20 @@ class VoiceMessageComposerPlayer(
|
|||||||
* @property playState Whether this player is currently playing. See [PlayState].
|
* @property playState Whether this player is currently playing. See [PlayState].
|
||||||
* @property currentPosition The elapsed time of this player in milliseconds.
|
* @property currentPosition The elapsed time of this player in milliseconds.
|
||||||
* @property progress The progress of this player between 0 and 1.
|
* @property progress The progress of this player between 0 and 1.
|
||||||
|
* @property durationMs The player-reported duration in milliseconds, when known and the player is not stopped.
|
||||||
*/
|
*/
|
||||||
data class State(
|
data class State(
|
||||||
val playState: PlayState,
|
val playState: PlayState,
|
||||||
val currentPosition: Long,
|
val currentPosition: Long,
|
||||||
val progress: Float,
|
val progress: Float,
|
||||||
|
val durationMs: Long?,
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
val Initial = State(
|
val Initial = State(
|
||||||
playState = PlayState.Stopped,
|
playState = PlayState.Stopped,
|
||||||
currentPosition = 0L,
|
currentPosition = 0L,
|
||||||
progress = 0f,
|
progress = 0f,
|
||||||
|
durationMs = null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,10 @@
|
|||||||
<string name="screen_room_attachment_source_location">"Udostępnij lokalizację"</string>
|
<string name="screen_room_attachment_source_location">"Udostępnij lokalizację"</string>
|
||||||
<string name="screen_room_attachment_source_poll">"Ankieta"</string>
|
<string name="screen_room_attachment_source_poll">"Ankieta"</string>
|
||||||
<string name="screen_room_attachment_text_formatting">"Formatowanie tekstu"</string>
|
<string name="screen_room_attachment_text_formatting">"Formatowanie tekstu"</string>
|
||||||
|
<string name="screen_room_confirm_removal_message">"Czy na pewno chcesz usunąć wiadomość?"</string>
|
||||||
|
<string name="screen_room_confirm_removal_reason_label">"Powód (opcjonalne)"</string>
|
||||||
|
<string name="screen_room_confirm_removal_reason_placeholder">"Powód…"</string>
|
||||||
|
<string name="screen_room_confirm_removal_title">"Potwierdź usunięcie"</string>
|
||||||
<string name="screen_room_encrypted_history_banner">"Historia wiadomości jest obecnie niedostępna."</string>
|
<string name="screen_room_encrypted_history_banner">"Historia wiadomości jest obecnie niedostępna."</string>
|
||||||
<string name="screen_room_encrypted_history_banner_unverified">"Historia wiadomości jest niedostępna w tym pokoju. Zweryfikuj to urządzenie, aby zobaczyć historię wiadomości."</string>
|
<string name="screen_room_encrypted_history_banner_unverified">"Historia wiadomości jest niedostępna w tym pokoju. Zweryfikuj to urządzenie, aby zobaczyć historię wiadomości."</string>
|
||||||
<string name="screen_room_invite_again_alert_message">"Czy chcesz zaprosić ich z powrotem?"</string>
|
<string name="screen_room_invite_again_alert_message">"Czy chcesz zaprosić ich z powrotem?"</string>
|
||||||
|
|||||||
@@ -48,6 +48,10 @@
|
|||||||
<string name="screen_room_attachment_source_location">"Поделиться местоположением"</string>
|
<string name="screen_room_attachment_source_location">"Поделиться местоположением"</string>
|
||||||
<string name="screen_room_attachment_source_poll">"Опрос"</string>
|
<string name="screen_room_attachment_source_poll">"Опрос"</string>
|
||||||
<string name="screen_room_attachment_text_formatting">"Форматирование текста"</string>
|
<string name="screen_room_attachment_text_formatting">"Форматирование текста"</string>
|
||||||
|
<string name="screen_room_confirm_removal_message">"Вы уверены, что хотите удалить это сообщение?"</string>
|
||||||
|
<string name="screen_room_confirm_removal_reason_label">"Причина (необязательно)"</string>
|
||||||
|
<string name="screen_room_confirm_removal_reason_placeholder">"Введите…"</string>
|
||||||
|
<string name="screen_room_confirm_removal_title">"Подтвердите удаление"</string>
|
||||||
<string name="screen_room_encrypted_history_banner">"В настоящее время история сообщений недоступна в этой комнате."</string>
|
<string name="screen_room_encrypted_history_banner">"В настоящее время история сообщений недоступна в этой комнате."</string>
|
||||||
<string name="screen_room_encrypted_history_banner_unverified">"История сообщений в этой комнате недоступна. Проверьте это устройство, чтобы увидеть историю сообщений."</string>
|
<string name="screen_room_encrypted_history_banner_unverified">"История сообщений в этой комнате недоступна. Проверьте это устройство, чтобы увидеть историю сообщений."</string>
|
||||||
<string name="screen_room_invite_again_alert_message">"Хотите пригласить их снова?"</string>
|
<string name="screen_room_invite_again_alert_message">"Хотите пригласить их снова?"</string>
|
||||||
|
|||||||
@@ -46,6 +46,10 @@
|
|||||||
<string name="screen_room_attachment_source_location">"共享位置"</string>
|
<string name="screen_room_attachment_source_location">"共享位置"</string>
|
||||||
<string name="screen_room_attachment_source_poll">"投票"</string>
|
<string name="screen_room_attachment_source_poll">"投票"</string>
|
||||||
<string name="screen_room_attachment_text_formatting">"文本格式化"</string>
|
<string name="screen_room_attachment_text_formatting">"文本格式化"</string>
|
||||||
|
<string name="screen_room_confirm_removal_message">"确定要移除此消息?"</string>
|
||||||
|
<string name="screen_room_confirm_removal_reason_label">"理由(可选)"</string>
|
||||||
|
<string name="screen_room_confirm_removal_reason_placeholder">"输入…"</string>
|
||||||
|
<string name="screen_room_confirm_removal_title">"确认移除"</string>
|
||||||
<string name="screen_room_encrypted_history_banner">"消息历史记录当前不可用。"</string>
|
<string name="screen_room_encrypted_history_banner">"消息历史记录当前不可用。"</string>
|
||||||
<string name="screen_room_encrypted_history_banner_unverified">"消息历史在此房间不可用。请验证此设备以查看。"</string>
|
<string name="screen_room_encrypted_history_banner_unverified">"消息历史在此房间不可用。请验证此设备以查看。"</string>
|
||||||
<string name="screen_room_invite_again_alert_message">"你想邀请他们回来吗?"</string>
|
<string name="screen_room_invite_again_alert_message">"你想邀请他们回来吗?"</string>
|
||||||
|
|||||||
@@ -47,6 +47,12 @@
|
|||||||
<string name="screen_room_attachment_source_location">"Share location"</string>
|
<string name="screen_room_attachment_source_location">"Share location"</string>
|
||||||
<string name="screen_room_attachment_source_poll">"Poll"</string>
|
<string name="screen_room_attachment_source_poll">"Poll"</string>
|
||||||
<string name="screen_room_attachment_text_formatting">"Text Formatting"</string>
|
<string name="screen_room_attachment_text_formatting">"Text Formatting"</string>
|
||||||
|
<string name="screen_room_confirm_removal_message">"Are you sure you wish to remove this message?"</string>
|
||||||
|
<string name="screen_room_confirm_removal_reason_label">"Reason (optional)"</string>
|
||||||
|
<string name="screen_room_confirm_removal_reason_placeholder">"Enter…"</string>
|
||||||
|
<string name="screen_room_confirm_removal_reason_placeholder_android">"Reason…"</string>
|
||||||
|
<string name="screen_room_confirm_removal_reason_supporting_text">"(Optional)"</string>
|
||||||
|
<string name="screen_room_confirm_removal_title">"Confirm removal"</string>
|
||||||
<string name="screen_room_encrypted_history_banner">"Message history is currently unavailable."</string>
|
<string name="screen_room_encrypted_history_banner">"Message history is currently unavailable."</string>
|
||||||
<string name="screen_room_encrypted_history_banner_unverified">"Message history is unavailable in this room. Verify this device to see your message history."</string>
|
<string name="screen_room_encrypted_history_banner_unverified">"Message history is unavailable in this room. Verify this device to see your message history."</string>
|
||||||
<string name="screen_room_invite_again_alert_message">"Would you like to invite them back?"</string>
|
<string name="screen_room_invite_again_alert_message">"Would you like to invite them back?"</string>
|
||||||
|
|||||||
+83
-3
@@ -45,6 +45,7 @@ import io.element.android.features.messages.test.timeline.voicemessages.composer
|
|||||||
import io.element.android.features.roomcall.api.aStandByCallState
|
import io.element.android.features.roomcall.api.aStandByCallState
|
||||||
import io.element.android.features.roommembermoderation.api.RoomMemberModerationState
|
import io.element.android.features.roommembermoderation.api.RoomMemberModerationState
|
||||||
import io.element.android.libraries.androidutils.clipboard.FakeClipboardHelper
|
import io.element.android.libraries.androidutils.clipboard.FakeClipboardHelper
|
||||||
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||||
@@ -78,10 +79,12 @@ import io.element.android.libraries.matrix.test.AN_AVATAR_URL
|
|||||||
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.AN_EXCEPTION
|
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
||||||
import io.element.android.libraries.matrix.test.A_CAPTION
|
import io.element.android.libraries.matrix.test.A_CAPTION
|
||||||
|
import io.element.android.libraries.matrix.test.A_REASON
|
||||||
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_SESSION_ID
|
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||||
import io.element.android.libraries.matrix.test.A_SESSION_ID_2
|
import io.element.android.libraries.matrix.test.A_SESSION_ID_2
|
||||||
import io.element.android.libraries.matrix.test.A_THREAD_ID
|
import io.element.android.libraries.matrix.test.A_THREAD_ID
|
||||||
|
import io.element.android.libraries.matrix.test.A_TRANSACTION_ID
|
||||||
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.A_USER_ID_2
|
import io.element.android.libraries.matrix.test.A_USER_ID_2
|
||||||
import io.element.android.libraries.matrix.test.core.FakeSendHandle
|
import io.element.android.libraries.matrix.test.core.FakeSendHandle
|
||||||
@@ -125,7 +128,6 @@ import org.junit.Test
|
|||||||
import kotlin.time.Duration.Companion.milliseconds
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
@Suppress("LargeClass")
|
|
||||||
class MessagesPresenterTest {
|
class MessagesPresenterTest {
|
||||||
@get:Rule
|
@get:Rule
|
||||||
val warmUpRule = WarmUpRule()
|
val warmUpRule = WarmUpRule()
|
||||||
@@ -557,13 +559,86 @@ class MessagesPresenterTest {
|
|||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
val messageEvent = aMessageEvent()
|
val messageEvent = aMessageEvent()
|
||||||
initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.Redact, messageEvent))
|
initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.Redact, messageEvent))
|
||||||
awaitItem()
|
advanceUntilIdle()
|
||||||
|
val confirmingState = expectMostRecentItem()
|
||||||
|
assertThat(confirmingState.redactEventAction).isEqualTo(MessagesState.ConfirmingRedaction(messageEvent.eventId!!))
|
||||||
|
assert(redactEventLambda).isNeverCalled()
|
||||||
|
confirmingState.eventSink(MessagesEvent.ConfirmRedact(A_REASON))
|
||||||
|
advanceUntilIdle()
|
||||||
|
assertThat(expectMostRecentItem().redactEventAction).isEqualTo(AsyncAction.Uninitialized)
|
||||||
|
assert(redactEventLambda)
|
||||||
|
.isCalledOnce()
|
||||||
|
.with(value(messageEvent.eventOrTransactionId), value(A_REASON))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - handle action redact - a blank reason is not sent`() = runTest {
|
||||||
|
val coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true)
|
||||||
|
val liveTimeline = FakeTimeline()
|
||||||
|
val redactEventLambda = lambdaRecorder { _: EventOrTransactionId, _: String? -> Result.success(Unit) }
|
||||||
|
liveTimeline.redactEventLambda = redactEventLambda
|
||||||
|
val presenter = createMessagesPresenter(
|
||||||
|
timeline = liveTimeline,
|
||||||
|
coroutineDispatchers = coroutineDispatchers,
|
||||||
|
)
|
||||||
|
presenter.testWithLifecycleOwner {
|
||||||
|
val initialState = awaitItem()
|
||||||
|
val messageEvent = aMessageEvent()
|
||||||
|
initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.Redact, messageEvent))
|
||||||
|
advanceUntilIdle()
|
||||||
|
expectMostRecentItem().eventSink(MessagesEvent.ConfirmRedact(" "))
|
||||||
|
advanceUntilIdle()
|
||||||
|
assertThat(expectMostRecentItem().redactEventAction).isEqualTo(AsyncAction.Uninitialized)
|
||||||
assert(redactEventLambda)
|
assert(redactEventLambda)
|
||||||
.isCalledOnce()
|
.isCalledOnce()
|
||||||
.with(value(messageEvent.eventOrTransactionId), value(null))
|
.with(value(messageEvent.eventOrTransactionId), value(null))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - handle action redact - cancelling does not redact`() = runTest {
|
||||||
|
val coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true)
|
||||||
|
val liveTimeline = FakeTimeline()
|
||||||
|
val redactEventLambda = lambdaRecorder { _: EventOrTransactionId, _: String? -> Result.success(Unit) }
|
||||||
|
liveTimeline.redactEventLambda = redactEventLambda
|
||||||
|
val presenter = createMessagesPresenter(
|
||||||
|
timeline = liveTimeline,
|
||||||
|
coroutineDispatchers = coroutineDispatchers,
|
||||||
|
)
|
||||||
|
presenter.testWithLifecycleOwner {
|
||||||
|
val initialState = awaitItem()
|
||||||
|
initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.Redact, aMessageEvent()))
|
||||||
|
advanceUntilIdle()
|
||||||
|
expectMostRecentItem().eventSink(MessagesEvent.CancelRedact)
|
||||||
|
advanceUntilIdle()
|
||||||
|
assertThat(expectMostRecentItem().redactEventAction).isEqualTo(AsyncAction.Uninitialized)
|
||||||
|
assert(redactEventLambda).isNeverCalled()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - handle action redact - a message which was never sent is redacted without asking`() = runTest {
|
||||||
|
val coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true)
|
||||||
|
val liveTimeline = FakeTimeline()
|
||||||
|
val redactEventLambda = lambdaRecorder { _: EventOrTransactionId, _: String? -> Result.success(Unit) }
|
||||||
|
liveTimeline.redactEventLambda = redactEventLambda
|
||||||
|
val presenter = createMessagesPresenter(
|
||||||
|
timeline = liveTimeline,
|
||||||
|
coroutineDispatchers = coroutineDispatchers,
|
||||||
|
)
|
||||||
|
presenter.testWithLifecycleOwner {
|
||||||
|
val initialState = awaitItem()
|
||||||
|
val localEcho = aMessageEvent(eventId = null, transactionId = A_TRANSACTION_ID)
|
||||||
|
initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.Redact, localEcho))
|
||||||
|
advanceUntilIdle()
|
||||||
|
assertThat(expectMostRecentItem().redactEventAction).isEqualTo(AsyncAction.Uninitialized)
|
||||||
|
assert(redactEventLambda)
|
||||||
|
.isCalledOnce()
|
||||||
|
.with(value(localEcho.eventOrTransactionId), value(null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - handle action report content`() = runTest {
|
fun `present - handle action report content`() = runTest {
|
||||||
val onReportContentClickLambda = lambdaRecorder { _: EventId, _: UserId -> }
|
val onReportContentClickLambda = lambdaRecorder { _: EventId, _: UserId -> }
|
||||||
@@ -1484,7 +1559,12 @@ class MessagesPresenterTest {
|
|||||||
clipboardHelper = clipboardHelper,
|
clipboardHelper = clipboardHelper,
|
||||||
htmlConverterProvider = FakeHtmlConverterProvider(),
|
htmlConverterProvider = FakeHtmlConverterProvider(),
|
||||||
buildMeta = aBuildMeta(),
|
buildMeta = aBuildMeta(),
|
||||||
timelineController = TimelineController(joinedRoom, timeline),
|
timelineController = TimelineController(
|
||||||
|
room = joinedRoom,
|
||||||
|
liveTimeline = timeline,
|
||||||
|
roomCoroutineScope = backgroundScope,
|
||||||
|
dispatchers = coroutineDispatchers,
|
||||||
|
),
|
||||||
permalinkParser = permalinkParser,
|
permalinkParser = permalinkParser,
|
||||||
analyticsService = analyticsService,
|
analyticsService = analyticsService,
|
||||||
encryptionService = encryptionService,
|
encryptionService = encryptionService,
|
||||||
|
|||||||
-1
@@ -90,7 +90,6 @@ import org.junit.Test
|
|||||||
import org.robolectric.annotation.Config
|
import org.robolectric.annotation.Config
|
||||||
import kotlin.time.Duration.Companion.milliseconds
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
|
||||||
@Suppress("LargeClass")
|
|
||||||
class MessagesViewTest : RobolectricTest() {
|
class MessagesViewTest : RobolectricTest() {
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on back invoke expected callback`() = runAndroidComposeUiTest {
|
fun `clicking on back invoke expected callback`() = runAndroidComposeUiTest {
|
||||||
|
|||||||
-1
@@ -54,7 +54,6 @@ import kotlinx.coroutines.test.runTest
|
|||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
@Suppress("LargeClass")
|
|
||||||
class ActionListPresenterTest {
|
class ActionListPresenterTest {
|
||||||
@get:Rule
|
@get:Rule
|
||||||
val warmUpRule = WarmUpRule()
|
val warmUpRule = WarmUpRule()
|
||||||
|
|||||||
-1
@@ -82,7 +82,6 @@ import org.junit.Test
|
|||||||
import java.io.File
|
import java.io.File
|
||||||
import kotlin.io.path.createTempFile
|
import kotlin.io.path.createTempFile
|
||||||
|
|
||||||
@Suppress("LargeClass")
|
|
||||||
class AttachmentsPreviewPresenterTest : RobolectricTest() {
|
class AttachmentsPreviewPresenterTest : RobolectricTest() {
|
||||||
@get:Rule
|
@get:Rule
|
||||||
val warmUpRule = WarmUpRule()
|
val warmUpRule = WarmUpRule()
|
||||||
|
|||||||
+7
-1
@@ -65,6 +65,7 @@ import io.element.android.tests.testutils.WarmUpRule
|
|||||||
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
|
||||||
import io.element.android.tests.testutils.test
|
import io.element.android.tests.testutils.test
|
||||||
|
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.test.TestScope
|
import kotlinx.coroutines.test.TestScope
|
||||||
@@ -307,7 +308,12 @@ class MessageComposerPresenterSlashCommandTest {
|
|||||||
permissionsPresenterFactory = FakePermissionsPresenterFactory(permissionPresenter),
|
permissionsPresenterFactory = FakePermissionsPresenterFactory(permissionPresenter),
|
||||||
permalinkParser = permalinkParser,
|
permalinkParser = permalinkParser,
|
||||||
permalinkBuilder = permalinkBuilder,
|
permalinkBuilder = permalinkBuilder,
|
||||||
timelineController = TimelineController(room, timeline),
|
timelineController = TimelineController(
|
||||||
|
room = room,
|
||||||
|
liveTimeline = timeline,
|
||||||
|
roomCoroutineScope = backgroundScope,
|
||||||
|
dispatchers = testCoroutineDispatchers(),
|
||||||
|
),
|
||||||
draftService = draftService,
|
draftService = draftService,
|
||||||
mentionSpanProvider = mentionSpanProvider,
|
mentionSpanProvider = mentionSpanProvider,
|
||||||
pillificationHelper = textPillificationHelper,
|
pillificationHelper = textPillificationHelper,
|
||||||
|
|||||||
+7
-2
@@ -113,6 +113,7 @@ import io.element.android.tests.testutils.lambda.lambdaRecorder
|
|||||||
import io.element.android.tests.testutils.lambda.value
|
import io.element.android.tests.testutils.lambda.value
|
||||||
import io.element.android.tests.testutils.robolectric.RobolectricTest
|
import io.element.android.tests.testutils.robolectric.RobolectricTest
|
||||||
import io.element.android.tests.testutils.test
|
import io.element.android.tests.testutils.test
|
||||||
|
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||||
import io.element.android.tests.testutils.waitForPredicate
|
import io.element.android.tests.testutils.waitForPredicate
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
@@ -126,7 +127,6 @@ import org.junit.Test
|
|||||||
import uniffi.wysiwyg_composer.MentionsState
|
import uniffi.wysiwyg_composer.MentionsState
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
@Suppress("LargeClass")
|
|
||||||
class MessageComposerPresenterTest : RobolectricTest() {
|
class MessageComposerPresenterTest : RobolectricTest() {
|
||||||
@get:Rule
|
@get:Rule
|
||||||
val warmUpRule = WarmUpRule()
|
val warmUpRule = WarmUpRule()
|
||||||
@@ -1707,7 +1707,12 @@ class MessageComposerPresenterTest : RobolectricTest() {
|
|||||||
permissionsPresenterFactory = FakePermissionsPresenterFactory(permissionPresenter),
|
permissionsPresenterFactory = FakePermissionsPresenterFactory(permissionPresenter),
|
||||||
permalinkParser = permalinkParser,
|
permalinkParser = permalinkParser,
|
||||||
permalinkBuilder = permalinkBuilder,
|
permalinkBuilder = permalinkBuilder,
|
||||||
timelineController = TimelineController(room, timeline),
|
timelineController = TimelineController(
|
||||||
|
room = room,
|
||||||
|
liveTimeline = timeline,
|
||||||
|
roomCoroutineScope = backgroundScope,
|
||||||
|
dispatchers = testCoroutineDispatchers(),
|
||||||
|
),
|
||||||
draftService = draftService,
|
draftService = draftService,
|
||||||
mentionSpanProvider = mentionSpanProvider,
|
mentionSpanProvider = mentionSpanProvider,
|
||||||
pillificationHelper = textPillificationHelper,
|
pillificationHelper = textPillificationHelper,
|
||||||
|
|||||||
+34
-9
@@ -21,8 +21,10 @@ import io.element.android.libraries.matrix.test.timeline.FakeTimeline
|
|||||||
import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem
|
import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem
|
||||||
import io.element.android.tests.testutils.lambda.lambdaError
|
import io.element.android.tests.testutils.lambda.lambdaError
|
||||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||||
|
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.flowOf
|
import kotlinx.coroutines.flow.flowOf
|
||||||
|
import kotlinx.coroutines.test.TestScope
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
@@ -35,7 +37,10 @@ class TimelineControllerTest {
|
|||||||
liveTimeline = liveTimeline,
|
liveTimeline = liveTimeline,
|
||||||
createTimelineResult = { Result.success(detachedTimeline) }
|
createTimelineResult = { Result.success(detachedTimeline) }
|
||||||
)
|
)
|
||||||
val sut = TimelineController(room = joinedRoom, liveTimeline = liveTimeline)
|
val sut = createTimelineController(
|
||||||
|
room = joinedRoom,
|
||||||
|
liveTimeline = liveTimeline,
|
||||||
|
)
|
||||||
|
|
||||||
sut.activeTimelineFlow().test {
|
sut.activeTimelineFlow().test {
|
||||||
awaitItem().also { state ->
|
awaitItem().also { state ->
|
||||||
@@ -74,7 +79,10 @@ class TimelineControllerTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
val sut = TimelineController(joinedRoom, liveTimeline)
|
val sut = createTimelineController(
|
||||||
|
room = joinedRoom,
|
||||||
|
liveTimeline = liveTimeline,
|
||||||
|
)
|
||||||
|
|
||||||
sut.activeTimelineFlow().test {
|
sut.activeTimelineFlow().test {
|
||||||
awaitItem().also { state ->
|
awaitItem().also { state ->
|
||||||
@@ -102,7 +110,10 @@ class TimelineControllerTest {
|
|||||||
val joinedRoom = FakeJoinedRoom(
|
val joinedRoom = FakeJoinedRoom(
|
||||||
liveTimeline = liveTimeline
|
liveTimeline = liveTimeline
|
||||||
)
|
)
|
||||||
val sut = TimelineController(room = joinedRoom, liveTimeline = liveTimeline)
|
val sut = createTimelineController(
|
||||||
|
room = joinedRoom,
|
||||||
|
liveTimeline = liveTimeline,
|
||||||
|
)
|
||||||
sut.activeTimelineFlow().test {
|
sut.activeTimelineFlow().test {
|
||||||
awaitItem().also { state ->
|
awaitItem().also { state ->
|
||||||
assertThat(state).isEqualTo(liveTimeline)
|
assertThat(state).isEqualTo(liveTimeline)
|
||||||
@@ -121,7 +132,10 @@ class TimelineControllerTest {
|
|||||||
liveTimeline = liveTimeline,
|
liveTimeline = liveTimeline,
|
||||||
createTimelineResult = { Result.success(detachedTimeline) }
|
createTimelineResult = { Result.success(detachedTimeline) }
|
||||||
)
|
)
|
||||||
val sut = TimelineController(room = joinedRoom, liveTimeline = liveTimeline)
|
val sut = createTimelineController(
|
||||||
|
room = joinedRoom,
|
||||||
|
liveTimeline = liveTimeline,
|
||||||
|
)
|
||||||
sut.activeTimelineFlow().test {
|
sut.activeTimelineFlow().test {
|
||||||
awaitItem().also { state ->
|
awaitItem().also { state ->
|
||||||
assertThat(state).isEqualTo(liveTimeline)
|
assertThat(state).isEqualTo(liveTimeline)
|
||||||
@@ -149,7 +163,10 @@ class TimelineControllerTest {
|
|||||||
val joinedRoom = FakeJoinedRoom(
|
val joinedRoom = FakeJoinedRoom(
|
||||||
liveTimeline = liveTimeline
|
liveTimeline = liveTimeline
|
||||||
)
|
)
|
||||||
val sut = TimelineController(room = joinedRoom, liveTimeline = liveTimeline)
|
val sut = createTimelineController(
|
||||||
|
room = joinedRoom,
|
||||||
|
liveTimeline = liveTimeline,
|
||||||
|
)
|
||||||
assertThat(sut.timelineItems().first()).hasSize(1)
|
assertThat(sut.timelineItems().first()).hasSize(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,7 +188,10 @@ class TimelineControllerTest {
|
|||||||
liveTimeline = liveTimeline,
|
liveTimeline = liveTimeline,
|
||||||
createTimelineResult = { Result.success(detachedTimeline) }
|
createTimelineResult = { Result.success(detachedTimeline) }
|
||||||
)
|
)
|
||||||
val sut = TimelineController(room = joinedRoom, liveTimeline = liveTimeline)
|
val sut = createTimelineController(
|
||||||
|
room = joinedRoom,
|
||||||
|
liveTimeline = liveTimeline,
|
||||||
|
)
|
||||||
sut.activeTimelineFlow().test {
|
sut.activeTimelineFlow().test {
|
||||||
sut.focusOnEvent(AN_EVENT_ID, null)
|
sut.focusOnEvent(AN_EVENT_ID, null)
|
||||||
awaitItem().also { state ->
|
awaitItem().also { state ->
|
||||||
@@ -196,7 +216,10 @@ class TimelineControllerTest {
|
|||||||
liveTimeline = liveTimeline,
|
liveTimeline = liveTimeline,
|
||||||
createTimelineResult = { Result.success(detachedTimeline) }
|
createTimelineResult = { Result.success(detachedTimeline) }
|
||||||
)
|
)
|
||||||
val sut = TimelineController(room = joinedRoom, liveTimeline = liveTimeline)
|
val sut = createTimelineController(
|
||||||
|
room = joinedRoom,
|
||||||
|
liveTimeline = liveTimeline,
|
||||||
|
)
|
||||||
|
|
||||||
sut.activeTimelineFlow().test {
|
sut.activeTimelineFlow().test {
|
||||||
awaitItem().also { state ->
|
awaitItem().also { state ->
|
||||||
@@ -220,12 +243,14 @@ class TimelineControllerTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun createTimelineController(
|
internal fun TestScope.createTimelineController(
|
||||||
room: FakeJoinedRoom = FakeJoinedRoom(liveTimeline = FakeTimeline()),
|
room: FakeJoinedRoom = FakeJoinedRoom(liveTimeline = FakeTimeline()),
|
||||||
liveTimeline: Timeline = FakeTimeline(name = "live"),
|
liveTimeline: Timeline = FakeTimeline(name = "live"),
|
||||||
): TimelineController {
|
): TimelineController {
|
||||||
return TimelineController(
|
return TimelineController(
|
||||||
room = room,
|
room = room,
|
||||||
liveTimeline = liveTimeline
|
liveTimeline = liveTimeline,
|
||||||
|
roomCoroutineScope = backgroundScope,
|
||||||
|
dispatchers = testCoroutineDispatchers(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user