diff --git a/.github/workflows/build_enterprise.yml b/.github/workflows/build_enterprise.yml
index 7547d5291c..602b43a7e1 100644
--- a/.github/workflows/build_enterprise.yml
+++ b/.github/workflows/build_enterprise.yml
@@ -17,7 +17,7 @@ jobs:
name: Build Enterprise APKs
runs-on: ubuntu-latest
# Skip in forks
- if: github.repository == 'element-hq/element-x-android'
+ if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
strategy:
matrix:
variant: [debug, release, nightly]
diff --git a/.github/workflows/danger.yml b/.github/workflows/danger.yml
index b4fd79e5dd..6b6ac8243e 100644
--- a/.github/workflows/danger.yml
+++ b/.github/workflows/danger.yml
@@ -6,6 +6,8 @@ jobs:
build:
runs-on: ubuntu-latest
name: Danger main check
+ # Skip in forks, it doesn't work even with the fallback token
+ if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
steps:
- uses: actions/checkout@v4
- name: Add SSH private keys for submodule repositories
@@ -13,7 +15,7 @@ jobs:
with:
ssh-private-key: ${{ secrets.ELEMENT_ENTERPRISE_DEPLOY_KEY }}
- name: Clone submodules
- if: github.repository == '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: |
npm install --save-dev @babel/plugin-transform-flow-strip-types
diff --git a/.github/workflows/generate_github_pages.yml b/.github/workflows/generate_github_pages.yml
index 96f64eb1f7..46494ba76a 100644
--- a/.github/workflows/generate_github_pages.yml
+++ b/.github/workflows/generate_github_pages.yml
@@ -9,7 +9,7 @@ jobs:
generate-github-pages:
runs-on: ubuntu-latest
# Skip in forks
- if: github.repository == 'element-hq/element-x-android'
+ if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
steps:
- name: ⏬ Checkout with LFS
uses: nschloe/action-cached-lfs-checkout@v1.2.2
diff --git a/.github/workflows/gradle-wrapper-update.yml b/.github/workflows/gradle-wrapper-update.yml
index b3493e3bf7..d35bace705 100644
--- a/.github/workflows/gradle-wrapper-update.yml
+++ b/.github/workflows/gradle-wrapper-update.yml
@@ -12,7 +12,7 @@ jobs:
- name: Update Gradle Wrapper
uses: gradle-update/update-gradle-wrapper-action@v1
# Skip in forks
- if: github.repository == 'element-hq/element-x-android'
+ if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
target-branch: develop
diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml
index ff435ee988..a0643c426f 100644
--- a/.github/workflows/quality.yml
+++ b/.github/workflows/quality.yml
@@ -20,10 +20,11 @@ jobs:
- uses: actions/checkout@v4
- name: Add SSH private keys for submodule repositories
uses: webfactory/ssh-agent@v0.9.0
+ if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
with:
ssh-private-key: ${{ secrets.ELEMENT_ENTERPRISE_DEPLOY_KEY }}
- name: Clone submodules
- if: github.repository == '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
- name: Run code quality check suite
run: ./tools/check/check_code_quality.sh
@@ -77,10 +78,11 @@ jobs:
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }}
- name: Add SSH private keys for submodule repositories
uses: webfactory/ssh-agent@v0.9.0
+ if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
with:
ssh-private-key: ${{ secrets.ELEMENT_ENTERPRISE_DEPLOY_KEY }}
- name: Clone submodules
- if: github.repository == '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
- name: Use JDK 17
uses: actions/setup-java@v4
@@ -116,10 +118,11 @@ jobs:
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }}
- name: Add SSH private keys for submodule repositories
uses: webfactory/ssh-agent@v0.9.0
+ if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
with:
ssh-private-key: ${{ secrets.ELEMENT_ENTERPRISE_DEPLOY_KEY }}
- name: Clone submodules
- if: github.repository == '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
- name: Use JDK 17
uses: actions/setup-java@v4
@@ -159,10 +162,11 @@ jobs:
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }}
- name: Add SSH private keys for submodule repositories
uses: webfactory/ssh-agent@v0.9.0
+ if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
with:
ssh-private-key: ${{ secrets.ELEMENT_ENTERPRISE_DEPLOY_KEY }}
- name: Clone submodules
- if: github.repository == '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
- name: Use JDK 17
uses: actions/setup-java@v4
@@ -198,10 +202,11 @@ jobs:
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }}
- name: Add SSH private keys for submodule repositories
uses: webfactory/ssh-agent@v0.9.0
+ if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
with:
ssh-private-key: ${{ secrets.ELEMENT_ENTERPRISE_DEPLOY_KEY }}
- name: Clone submodules
- if: github.repository == '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
- name: Use JDK 17
uses: actions/setup-java@v4
@@ -237,10 +242,11 @@ jobs:
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }}
- name: Add SSH private keys for submodule repositories
uses: webfactory/ssh-agent@v0.9.0
+ if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
with:
ssh-private-key: ${{ secrets.ELEMENT_ENTERPRISE_DEPLOY_KEY }}
- name: Clone submodules
- if: github.repository == '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
- name: Use JDK 17
uses: actions/setup-java@v4
@@ -271,6 +277,7 @@ jobs:
name: Project Check Suite
runs-on: ubuntu-latest
needs: [konsist, lint, ktlint, detekt]
+ if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
steps:
- uses: actions/checkout@v4
with:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index b58f2f3348..38be804fc1 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -42,6 +42,7 @@ jobs:
enterprise:
name: Create App Bundle Enterprise
runs-on: ubuntu-latest
+ if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
concurrency:
group: ${{ format('build-release-main-gplay-{0}', github.sha) }}
cancel-in-progress: true
@@ -49,6 +50,7 @@ jobs:
- uses: actions/checkout@v4
- name: Add SSH private keys for submodule repositories
uses: webfactory/ssh-agent@v0.9.0
+ if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
with:
ssh-private-key: ${{ secrets.ELEMENT_ENTERPRISE_DEPLOY_KEY }}
- name: Clone submodules
diff --git a/.github/workflows/sync-localazy.yml b/.github/workflows/sync-localazy.yml
index 347923a6c0..5f99cac17a 100644
--- a/.github/workflows/sync-localazy.yml
+++ b/.github/workflows/sync-localazy.yml
@@ -9,7 +9,7 @@ jobs:
sync-localazy:
runs-on: ubuntu-latest
# Skip in forks
- if: github.repository == 'element-hq/element-x-android'
+ if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
steps:
- uses: actions/checkout@v4
- name: Use JDK 17
diff --git a/.github/workflows/sync-sas-strings.yml b/.github/workflows/sync-sas-strings.yml
index f9bff2905b..d06cfe268a 100644
--- a/.github/workflows/sync-sas-strings.yml
+++ b/.github/workflows/sync-sas-strings.yml
@@ -9,7 +9,7 @@ jobs:
sync-sas-strings:
runs-on: ubuntu-latest
# Skip in forks
- if: github.repository == 'element-hq/element-x-android'
+ if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
# No concurrency required, runs every time on a schedule.
steps:
- uses: actions/checkout@v4
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index a132fd7d14..fa274baf89 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -40,10 +40,11 @@ jobs:
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }}
- name: Add SSH private keys for submodule repositories
uses: webfactory/ssh-agent@v0.9.0
+ if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
with:
ssh-private-key: ${{ secrets.ELEMENT_ENTERPRISE_DEPLOY_KEY }}
- name: Clone submodules
- if: github.repository == '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
- name: ☕️ Use JDK 17
uses: actions/setup-java@v4
diff --git a/CHANGES.md b/CHANGES.md
index c2c2641b34..2661597461 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,3 +1,80 @@
+Changes in Element X v0.5.0 (2024-07-24)
+=========================================
+
+### 🙌 Improvements
+* Add icon for "Mark as read" and "Mark as unread" actions. by @bmarty in https://github.com/element-hq/element-x-android/pull/3144
+* Add support for Picture In Picture for Element Call by @bmarty in https://github.com/element-hq/element-x-android/pull/3159
+* Set pin grace period to 2 minutes by @bmarty in https://github.com/element-hq/element-x-android/pull/3172
+* Unify the way we decide whether a room is a DM or a group room by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3100
+* Subscribe to `RoomListItems` in the visible range by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3169
+* Improve pip and add feature flag. by @bmarty in https://github.com/element-hq/element-x-android/pull/3199
+* Open Source licenses: add color for links. by @bmarty in https://github.com/element-hq/element-x-android/pull/3215
+* Cancel ringing call notification on call cancellation by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3047
+
+### 🐛 Bugfixes
+* Fix `MainActionButton` layout for long texts by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3158
+* Always follow the desired theme for Pin, Incoming Call and Element Call screens by @bmarty in https://github.com/element-hq/element-x-android/pull/3165
+* Fix empty screen issue after clearing the cache by @bmarty in https://github.com/element-hq/element-x-android/pull/3163
+* Restore intentional mentions in the markdown/plain text editor by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3193
+* Fix crash in the room list after a forced log out in background by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3180
+* Clear existing notification when a room is marked as read by @bmarty in https://github.com/element-hq/element-x-android/pull/3203
+* Fix crash when Pin code screen is displayed by @bmarty in https://github.com/element-hq/element-x-android/pull/3205
+* Fix pillification not working for non formatted message bodies by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3201
+* Update grammar on Matrix Ids to be more spec compliant and render error instead of infinite loading in room member list screen by @bmarty in https://github.com/element-hq/element-x-android/pull/3206
+* Reduce the risk of text truncation in buttons. by @bmarty in https://github.com/element-hq/element-x-android/pull/3209
+* Ensure that the manual dark theme is rendering correctly regarding -night resource and keyboard by @bmarty in https://github.com/element-hq/element-x-android/pull/3216
+* Fix rendering issue of SunsetPage in dark mode by @bmarty in https://github.com/element-hq/element-x-android/pull/3217
+* Fix linkification not working for `Spanned` strings in text messages by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3233
+* Edit : fallback to room.edit when timeline item is not found. by @ganfra in https://github.com/element-hq/element-x-android/pull/3239
+
+### 🗣 Translations
+* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/3156
+* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/3192
+* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/3232
+
+### 🧱 Build
+* Remove Showkase processor not found warning from Danger by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3148
+* Set targetSDK to 34 by @bmarty in https://github.com/element-hq/element-x-android/pull/3149
+* Add a local copy of `inplace-fix.py` and `fix-pg-map-id.py` by @bmarty in https://github.com/element-hq/element-x-android/pull/3167
+* Only add private SSH keys and clone submodules in the original repo by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3225
+* Fix CI for forks by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3226
+
+### Dependency upgrades
+* Update dependency io.element.android:compound-android to v0.0.7 by @renovate in https://github.com/element-hq/element-x-android/pull/3143
+* Update dependency org.matrix.rustcomponents:sdk-android to v0.2.31 by @renovate in https://github.com/element-hq/element-x-android/pull/3145
+* Update dependency com.squareup:kotlinpoet to v1.18.0 by @renovate in https://github.com/element-hq/element-x-android/pull/3150
+* Update dependency org.robolectric:robolectric to v4.13 by @renovate in https://github.com/element-hq/element-x-android/pull/3157
+* Update plugin dependencycheck to v10.0.2 by @renovate in https://github.com/element-hq/element-x-android/pull/3154
+* Update wysiwyg to v2.37.5 by @renovate in https://github.com/element-hq/element-x-android/pull/3162
+* Update plugin sonarqube to v5.1.0.4882 by @renovate in https://github.com/element-hq/element-x-android/pull/3139
+* Update dependency org.jsoup:jsoup to v1.18.1 by @renovate in https://github.com/element-hq/element-x-android/pull/3171
+* Update dependency com.google.firebase:firebase-bom to v33.1.2 by @renovate in https://github.com/element-hq/element-x-android/pull/3178
+* Update telephoto to v0.12.0 by @renovate in https://github.com/element-hq/element-x-android/pull/3191
+* Update dependency com.google.truth:truth to v1.4.4 by @renovate in https://github.com/element-hq/element-x-android/pull/3187
+* Update dependency com.squareup:kotlinpoet to v1.18.1 by @renovate in https://github.com/element-hq/element-x-android/pull/3194
+* Update dependency io.mockk:mockk to v1.13.12 by @renovate in https://github.com/element-hq/element-x-android/pull/3198
+* Update dependency io.sentry:sentry-android to v7.12.0 by @renovate in https://github.com/element-hq/element-x-android/pull/3200
+* Update plugin dependencycheck to v10.0.3 by @renovate in https://github.com/element-hq/element-x-android/pull/3204
+* Update dependency gradle to v8.9 by @renovate in https://github.com/element-hq/element-x-android/pull/3177
+* Update dependency org.matrix.rustcomponents:sdk-android to v0.2.32 by @renovate in https://github.com/element-hq/element-x-android/pull/3202
+* Update coil to v2.7.0 by @renovate in https://github.com/element-hq/element-x-android/pull/3210
+* Update dependency org.matrix.rustcomponents:sdk-android to v0.2.33 by @renovate in https://github.com/element-hq/element-x-android/pull/3220
+* Update wysiwyg to v2.37.7 by @renovate in https://github.com/element-hq/element-x-android/pull/3218
+* Update telephoto to v0.12.1 by @renovate in https://github.com/element-hq/element-x-android/pull/3230
+* Update dependency org.matrix.rustcomponents:sdk-android to v0.2.34 by @renovate in https://github.com/element-hq/element-x-android/pull/3237
+
+### Others
+* Reduce delay when selecting room list filters by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3160
+* Add `--alignment-preserved true` when signing APK for F-Droid. by @bmarty in https://github.com/element-hq/element-x-android/pull/3161
+* Ensure that all callback plugins are invoked. by @bmarty in https://github.com/element-hq/element-x-android/pull/3146
+* Add generated screen to show open source licenses in Gplay variant by @bmarty in https://github.com/element-hq/element-x-android/pull/3207
+* Performance : improve time to open a room. by @ganfra in https://github.com/element-hq/element-x-android/pull/3186
+* Add logging to help debug forced logout issues by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3208
+* Use the right filename for log files so they're sorted in rageshakes by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3219
+* Compose : add immutability to some Reaction classes by @ganfra in https://github.com/element-hq/element-x-android/pull/3224
+* Fix stickers display text on room summary by @surakin in https://github.com/element-hq/element-x-android/pull/3221
+* Rework FakeMatrixRoom so that it contains only lambdas. by @bmarty in https://github.com/element-hq/element-x-android/pull/3229
+
Changes in Element X v0.4.16 (2024-07-05)
=========================================
diff --git a/app/src/main/res/xml/locales_config.xml b/app/src/main/res/xml/locales_config.xml
index 2884d43c88..fa0e1e2199 100644
--- a/app/src/main/res/xml/locales_config.xml
+++ b/app/src/main/res/xml/locales_config.xml
@@ -13,7 +13,9 @@
+
+
diff --git a/appnav/src/test/kotlin/io/element/android/appnav/JoinRoomLoadedFlowNodeTest.kt b/appnav/src/test/kotlin/io/element/android/appnav/JoinRoomLoadedFlowNodeTest.kt
index d307f851cd..b1009f20da 100644
--- a/appnav/src/test/kotlin/io/element/android/appnav/JoinRoomLoadedFlowNodeTest.kt
+++ b/appnav/src/test/kotlin/io/element/android/appnav/JoinRoomLoadedFlowNodeTest.kt
@@ -123,7 +123,9 @@ class JoinRoomLoadedFlowNodeTest {
@Test
fun `given a room flow node when initialized then it loads messages entry point`() = runTest {
// GIVEN
- val room = FakeMatrixRoom()
+ val room = FakeMatrixRoom(
+ updateMembersResult = { }
+ )
val fakeMessagesEntryPoint = FakeMessagesEntryPoint()
val inputs = JoinedRoomLoadedFlowNode.Inputs(room, RoomNavigationTarget.Messages())
val roomFlowNode = createJoinedRoomLoadedFlowNode(
@@ -144,7 +146,9 @@ class JoinRoomLoadedFlowNodeTest {
@Test
fun `given a room flow node when callback on room details is triggered then it loads room details entry point`() = runTest {
// GIVEN
- val room = FakeMatrixRoom()
+ val room = FakeMatrixRoom(
+ updateMembersResult = { }
+ )
val fakeMessagesEntryPoint = FakeMessagesEntryPoint()
val fakeRoomDetailsEntryPoint = FakeRoomDetailsEntryPoint()
val inputs = JoinedRoomLoadedFlowNode.Inputs(room, RoomNavigationTarget.Messages())
diff --git a/appnav/src/test/kotlin/io/element/android/appnav/loggedin/SendQueuesTest.kt b/appnav/src/test/kotlin/io/element/android/appnav/loggedin/SendQueuesTest.kt
index bb4e8f3dbf..91f0aee517 100644
--- a/appnav/src/test/kotlin/io/element/android/appnav/loggedin/SendQueuesTest.kt
+++ b/appnav/src/test/kotlin/io/element/android/appnav/loggedin/SendQueuesTest.kt
@@ -33,7 +33,6 @@ import org.junit.Test
@OptIn(ExperimentalCoroutinesApi::class) class SendQueuesTest {
private val matrixClient = FakeMatrixClient()
- private val room = FakeMatrixRoom()
private val networkMonitor = FakeNetworkMonitor()
private val sut = SendQueues(matrixClient, networkMonitor)
@@ -43,11 +42,11 @@ import org.junit.Test
val setAllSendQueuesEnabledLambda = lambdaRecorder { _: Boolean -> }
matrixClient.sendQueueDisabledFlow = sendQueueDisabledFlow
matrixClient.setAllSendQueuesEnabledLambda = setAllSendQueuesEnabledLambda
- matrixClient.givenGetRoomResult(room.roomId, room)
-
val setRoomSendQueueEnabledLambda = lambdaRecorder { _: Boolean -> }
- room.setSendQueueEnabledLambda = setRoomSendQueueEnabledLambda
-
+ val room = FakeMatrixRoom(
+ setSendQueueEnabledLambda = setRoomSendQueueEnabledLambda
+ )
+ matrixClient.givenGetRoomResult(room.roomId, room)
sut.launchIn(backgroundScope)
sendQueueDisabledFlow.emit(room.roomId)
@@ -72,10 +71,11 @@ import org.junit.Test
matrixClient.sendQueueDisabledFlow = sendQueueDisabledFlow
matrixClient.setAllSendQueuesEnabledLambda = setAllSendQueuesEnabledLambda
networkMonitor.connectivity.value = NetworkStatus.Offline
- matrixClient.givenGetRoomResult(room.roomId, room)
-
val setRoomSendQueueEnabledLambda = lambdaRecorder { _: Boolean -> }
- room.setSendQueueEnabledLambda = setRoomSendQueueEnabledLambda
+ val room = FakeMatrixRoom(
+ setSendQueueEnabledLambda = setRoomSendQueueEnabledLambda
+ )
+ matrixClient.givenGetRoomResult(room.roomId, room)
sut.launchIn(backgroundScope)
diff --git a/fastlane/metadata/android/en-US/changelogs/40005000.txt b/fastlane/metadata/android/en-US/changelogs/40005000.txt
new file mode 100644
index 0000000000..dd8c30a1b9
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/40005000.txt
@@ -0,0 +1,2 @@
+Main changes in this version: mostly bug fixes and performance improvements.
+Full changelog: https://github.com/element-hq/element-x-android/releases
\ No newline at end of file
diff --git a/features/analytics/api/src/main/res/values-pl/translations.xml b/features/analytics/api/src/main/res/values-pl/translations.xml
new file mode 100644
index 0000000000..26aa9c6073
--- /dev/null
+++ b/features/analytics/api/src/main/res/values-pl/translations.xml
@@ -0,0 +1,7 @@
+
+
+ "Udostępniaj anonimowe dane dotyczące użytkowania, aby pomóc nam identyfikować problemy."
+ "Możesz przeczytać wszystkie nasze warunki %1$s."
+ "tutaj"
+ "Udostępniaj dane analityczne"
+
diff --git a/features/analytics/api/src/main/res/values-pt-rBR/translations.xml b/features/analytics/api/src/main/res/values-pt-rBR/translations.xml
new file mode 100644
index 0000000000..4e7dc9775f
--- /dev/null
+++ b/features/analytics/api/src/main/res/values-pt-rBR/translations.xml
@@ -0,0 +1,7 @@
+
+
+ "Compartilhe dados de uso anônimos para nos ajudar a identificar problemas."
+ "Você pode ler todos os nossos termos %1$s ."
+ "aqui"
+ "Compartilhar dados de utilização"
+
diff --git a/features/analytics/impl/src/main/res/values-pl/translations.xml b/features/analytics/impl/src/main/res/values-pl/translations.xml
new file mode 100644
index 0000000000..99e3e441bb
--- /dev/null
+++ b/features/analytics/impl/src/main/res/values-pl/translations.xml
@@ -0,0 +1,10 @@
+
+
+ "Nie będziemy rejestrować ani profilować żadnych danych osobistych"
+ "Udostępniaj anonimowe dane dotyczące użytkowania, aby pomóc nam identyfikować problemy."
+ "Możesz przeczytać wszystkie nasze warunki %1$s."
+ "tutaj"
+ "Możesz to wyłączyć w dowolnym momencie"
+ "Nie będziemy udostępniać Twoich danych podmiotom trzecim"
+ "Pomóż nam ulepszyć %1$s"
+
diff --git a/features/analytics/impl/src/main/res/values-pt-rBR/translations.xml b/features/analytics/impl/src/main/res/values-pt-rBR/translations.xml
new file mode 100644
index 0000000000..b2fe6e00bc
--- /dev/null
+++ b/features/analytics/impl/src/main/res/values-pt-rBR/translations.xml
@@ -0,0 +1,10 @@
+
+
+ "Não registraremos nem criaremos perfil baseado em qualquer dado pessoal"
+ "Compartilhe dados de uso anônimos para nos ajudar a identificar problemas."
+ "Você pode ler todos os nossos termos %1$s ."
+ "aqui"
+ "Você pode desativar isso a qualquer momento"
+ "Não compartilharemos seus dados com terceiros"
+ "Ajude a melhorar o %1$s"
+
diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/ActiveCallManager.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/ActiveCallManager.kt
index c02006cb54..1e217da7a8 100644
--- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/ActiveCallManager.kt
+++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/ActiveCallManager.kt
@@ -25,14 +25,25 @@ import io.element.android.features.call.impl.notifications.CallNotificationData
import io.element.android.features.call.impl.notifications.RingingCallNotificationCreator
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.di.SingleIn
+import io.element.android.libraries.matrix.api.MatrixClientProvider
import io.element.android.libraries.push.api.notifications.ForegroundServiceType
import io.element.android.libraries.push.api.notifications.NotificationIdProvider
import io.element.android.libraries.push.api.notifications.OnMissedCallNotificationHandler
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.drop
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
@@ -79,11 +90,16 @@ class DefaultActiveCallManager @Inject constructor(
private val onMissedCallNotificationHandler: OnMissedCallNotificationHandler,
private val ringingCallNotificationCreator: RingingCallNotificationCreator,
private val notificationManagerCompat: NotificationManagerCompat,
+ private val matrixClientProvider: MatrixClientProvider,
) : ActiveCallManager {
private var timedOutCallJob: Job? = null
override val activeCall = MutableStateFlow(null)
+ init {
+ observeRingingCall()
+ }
+
override fun registerIncomingCall(notificationData: CallNotificationData) {
if (activeCall.value != null) {
displayMissedCallNotification(notificationData)
@@ -173,6 +189,35 @@ class DefaultActiveCallManager @Inject constructor(
)
}
}
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ private fun observeRingingCall() {
+ // This will observe ringing calls and ensure they're terminated if the room call is cancelled
+ activeCall
+ .filterNotNull()
+ .filter { it.callState is CallState.Ringing && it.callType is CallType.RoomCall }
+ .flatMapLatest { activeCall ->
+ val callType = activeCall.callType as CallType.RoomCall
+ // Get a flow of updated `hasRoomCall` values for the room
+ matrixClientProvider.getOrRestore(callType.sessionId).getOrNull()
+ ?.getRoom(callType.roomId)
+ ?.roomInfoFlow
+ ?.map { it.hasRoomCall }
+ ?: flowOf()
+ }
+ // We only want to check if the room active call status changes
+ .distinctUntilChanged()
+ // Skip the first one, we're not interested in it (if the check below passes, it had to be active anyway)
+ .drop(1)
+ .onEach { roomHasActiveCall ->
+ if (!roomHasActiveCall) {
+ // The call was cancelled
+ timedOutCallJob?.cancel()
+ incomingCallTimedOut()
+ }
+ }
+ .launchIn(coroutineScope)
+ }
}
/**
diff --git a/features/call/impl/src/main/res/values-de/translations.xml b/features/call/impl/src/main/res/values-de/translations.xml
index d58a616780..6429dbe956 100644
--- a/features/call/impl/src/main/res/values-de/translations.xml
+++ b/features/call/impl/src/main/res/values-de/translations.xml
@@ -3,4 +3,5 @@
"Laufender Anruf"
"Tippen, um zum Anruf zurückzukehren"
"☎️ Anruf läuft"
+ "Eingehender Element Call"
diff --git a/features/call/impl/src/main/res/values-pl/translations.xml b/features/call/impl/src/main/res/values-pl/translations.xml
new file mode 100644
index 0000000000..27133cd91b
--- /dev/null
+++ b/features/call/impl/src/main/res/values-pl/translations.xml
@@ -0,0 +1,6 @@
+
+
+ "Połączenie w trakcie"
+ "Stuknij, aby wrócić do rozmowy"
+ "☎️ Rozmowa w toku"
+
diff --git a/features/call/impl/src/main/res/values-pt-rBR/translations.xml b/features/call/impl/src/main/res/values-pt-rBR/translations.xml
new file mode 100644
index 0000000000..c3d90e4bb0
--- /dev/null
+++ b/features/call/impl/src/main/res/values-pt-rBR/translations.xml
@@ -0,0 +1,6 @@
+
+
+ "Chamada em andamento"
+ "Toque para retornar à chamada"
+ "☎️ Chamada em andamento"
+
diff --git a/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/DefaultActiveCallManagerTest.kt b/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/DefaultActiveCallManagerTest.kt
index d62f55c23d..b6f9e84a60 100644
--- a/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/DefaultActiveCallManagerTest.kt
+++ b/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/DefaultActiveCallManagerTest.kt
@@ -32,7 +32,10 @@ import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_ROOM_ID_2
import io.element.android.libraries.matrix.test.A_SESSION_ID
+import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.FakeMatrixClientProvider
+import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
+import io.element.android.libraries.matrix.test.room.aRoomInfo
import io.element.android.libraries.push.api.notifications.ForegroundServiceType
import io.element.android.libraries.push.api.notifications.NotificationIdProvider
import io.element.android.libraries.push.test.notifications.FakeImageLoaderHolder
@@ -42,7 +45,11 @@ import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.lambda.value
import io.mockk.mockk
import io.mockk.verify
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.launch
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
@@ -59,26 +66,28 @@ class DefaultActiveCallManagerTest {
@Test
fun `registerIncomingCall - sets the incoming call as active`() = runTest {
val notificationManagerCompat = mockk(relaxed = true)
- val manager = createActiveCallManager(notificationManagerCompat = notificationManagerCompat)
+ inCancellableScope {
+ val manager = createActiveCallManager(notificationManagerCompat = notificationManagerCompat)
- assertThat(manager.activeCall.value).isNull()
+ assertThat(manager.activeCall.value).isNull()
- val callNotificationData = aCallNotificationData()
- manager.registerIncomingCall(callNotificationData)
+ val callNotificationData = aCallNotificationData()
+ manager.registerIncomingCall(callNotificationData)
- assertThat(manager.activeCall.value).isEqualTo(
- ActiveCall(
- callType = CallType.RoomCall(
- sessionId = callNotificationData.sessionId,
- roomId = callNotificationData.roomId,
- ),
- callState = CallState.Ringing(callNotificationData)
+ assertThat(manager.activeCall.value).isEqualTo(
+ ActiveCall(
+ callType = CallType.RoomCall(
+ sessionId = callNotificationData.sessionId,
+ roomId = callNotificationData.roomId,
+ ),
+ callState = CallState.Ringing(callNotificationData)
+ )
)
- )
- runCurrent()
+ runCurrent()
- verify { notificationManagerCompat.notify(notificationId, any()) }
+ verify { notificationManagerCompat.notify(notificationId, any()) }
+ }
}
@OptIn(ExperimentalCoroutinesApi::class)
@@ -86,38 +95,42 @@ class DefaultActiveCallManagerTest {
fun `registerIncomingCall - when there is an already active call adds missed call notification`() = runTest {
val addMissedCallNotificationLambda = lambdaRecorder { _, _, _ -> }
val onMissedCallNotificationHandler = FakeOnMissedCallNotificationHandler(addMissedCallNotificationLambda = addMissedCallNotificationLambda)
- val manager = createActiveCallManager(
- onMissedCallNotificationHandler = onMissedCallNotificationHandler,
- )
+ inCancellableScope {
+ val manager = createActiveCallManager(
+ onMissedCallNotificationHandler = onMissedCallNotificationHandler,
+ )
- // Register existing call
- val callNotificationData = aCallNotificationData()
- manager.registerIncomingCall(callNotificationData)
- val activeCall = manager.activeCall.value
+ // Register existing call
+ val callNotificationData = aCallNotificationData()
+ manager.registerIncomingCall(callNotificationData)
+ val activeCall = manager.activeCall.value
- // Now add a new call
- manager.registerIncomingCall(aCallNotificationData(roomId = A_ROOM_ID_2))
+ // Now add a new call
+ manager.registerIncomingCall(aCallNotificationData(roomId = A_ROOM_ID_2))
- assertThat(manager.activeCall.value).isEqualTo(activeCall)
- assertThat((manager.activeCall.value?.callType as? CallType.RoomCall)?.roomId).isNotEqualTo(A_ROOM_ID_2)
+ assertThat(manager.activeCall.value).isEqualTo(activeCall)
+ assertThat((manager.activeCall.value?.callType as? CallType.RoomCall)?.roomId).isNotEqualTo(A_ROOM_ID_2)
- advanceTimeBy(1)
+ advanceTimeBy(1)
- addMissedCallNotificationLambda.assertions()
- .isCalledOnce()
- .with(value(A_SESSION_ID), value(A_ROOM_ID_2), value(AN_EVENT_ID))
+ addMissedCallNotificationLambda.assertions()
+ .isCalledOnce()
+ .with(value(A_SESSION_ID), value(A_ROOM_ID_2), value(AN_EVENT_ID))
+ }
}
@Test
fun `incomingCallTimedOut - when there isn't an active call does nothing`() = runTest {
val addMissedCallNotificationLambda = lambdaRecorder { _, _, _ -> }
- val manager = createActiveCallManager(
- onMissedCallNotificationHandler = FakeOnMissedCallNotificationHandler(addMissedCallNotificationLambda = addMissedCallNotificationLambda)
- )
+ inCancellableScope {
+ val manager = createActiveCallManager(
+ onMissedCallNotificationHandler = FakeOnMissedCallNotificationHandler(addMissedCallNotificationLambda = addMissedCallNotificationLambda)
+ )
- manager.incomingCallTimedOut()
+ manager.incomingCallTimedOut()
- addMissedCallNotificationLambda.assertions().isNeverCalled()
+ addMissedCallNotificationLambda.assertions().isNeverCalled()
+ }
}
@OptIn(ExperimentalCoroutinesApi::class)
@@ -125,82 +138,167 @@ class DefaultActiveCallManagerTest {
fun `incomingCallTimedOut - when there is an active call removes it and adds a missed call notification`() = runTest {
val notificationManagerCompat = mockk(relaxed = true)
val addMissedCallNotificationLambda = lambdaRecorder { _, _, _ -> }
- val manager = createActiveCallManager(
- onMissedCallNotificationHandler = FakeOnMissedCallNotificationHandler(addMissedCallNotificationLambda = addMissedCallNotificationLambda),
- notificationManagerCompat = notificationManagerCompat,
- )
+ inCancellableScope {
+ val manager = createActiveCallManager(
+ onMissedCallNotificationHandler = FakeOnMissedCallNotificationHandler(addMissedCallNotificationLambda = addMissedCallNotificationLambda),
+ notificationManagerCompat = notificationManagerCompat,
+ )
- manager.registerIncomingCall(aCallNotificationData())
- assertThat(manager.activeCall.value).isNotNull()
+ manager.registerIncomingCall(aCallNotificationData())
+ assertThat(manager.activeCall.value).isNotNull()
- manager.incomingCallTimedOut()
- advanceTimeBy(1)
+ manager.incomingCallTimedOut()
+ advanceTimeBy(1)
- assertThat(manager.activeCall.value).isNull()
- addMissedCallNotificationLambda.assertions().isCalledOnce()
- verify { notificationManagerCompat.cancel(notificationId) }
+ assertThat(manager.activeCall.value).isNull()
+ addMissedCallNotificationLambda.assertions().isCalledOnce()
+ verify { notificationManagerCompat.cancel(notificationId) }
+ }
}
@Test
fun `hungUpCall - removes existing call if the CallType matches`() = runTest {
val notificationManagerCompat = mockk(relaxed = true)
- val manager = createActiveCallManager(notificationManagerCompat = notificationManagerCompat)
+ // Create a cancellable coroutine scope to cancel the test when needed
+ inCancellableScope {
+ val manager = createActiveCallManager(notificationManagerCompat = notificationManagerCompat)
- val notificationData = aCallNotificationData()
- manager.registerIncomingCall(notificationData)
- assertThat(manager.activeCall.value).isNotNull()
+ val notificationData = aCallNotificationData()
+ manager.registerIncomingCall(notificationData)
+ assertThat(manager.activeCall.value).isNotNull()
- manager.hungUpCall(CallType.RoomCall(notificationData.sessionId, notificationData.roomId))
- assertThat(manager.activeCall.value).isNull()
+ manager.hungUpCall(CallType.RoomCall(notificationData.sessionId, notificationData.roomId))
+ assertThat(manager.activeCall.value).isNull()
- verify { notificationManagerCompat.cancel(notificationId) }
+ verify { notificationManagerCompat.cancel(notificationId) }
+ }
}
@Test
fun `hungUpCall - does nothing if the CallType doesn't match`() = runTest {
val notificationManagerCompat = mockk(relaxed = true)
- val manager = createActiveCallManager(notificationManagerCompat = notificationManagerCompat)
+ // Create a cancellable coroutine scope to cancel the test when needed
+ inCancellableScope {
+ val manager = createActiveCallManager(notificationManagerCompat = notificationManagerCompat)
- manager.registerIncomingCall(aCallNotificationData())
- assertThat(manager.activeCall.value).isNotNull()
+ manager.registerIncomingCall(aCallNotificationData())
+ assertThat(manager.activeCall.value).isNotNull()
- manager.hungUpCall(CallType.ExternalUrl("https://example.com"))
- assertThat(manager.activeCall.value).isNotNull()
+ manager.hungUpCall(CallType.ExternalUrl("https://example.com"))
+ assertThat(manager.activeCall.value).isNotNull()
- verify(exactly = 0) { notificationManagerCompat.cancel(notificationId) }
+ verify(exactly = 0) { notificationManagerCompat.cancel(notificationId) }
+ }
}
@OptIn(ExperimentalCoroutinesApi::class)
@Test
fun `joinedCall - register an ongoing call and tries sending the call notify event`() = runTest {
val notificationManagerCompat = mockk(relaxed = true)
- val manager = createActiveCallManager(
- notificationManagerCompat = notificationManagerCompat,
- )
- assertThat(manager.activeCall.value).isNull()
+ inCancellableScope {
+ val manager = createActiveCallManager(notificationManagerCompat = notificationManagerCompat)
+ assertThat(manager.activeCall.value).isNull()
- manager.joinedCall(CallType.RoomCall(A_SESSION_ID, A_ROOM_ID))
- assertThat(manager.activeCall.value).isEqualTo(
- ActiveCall(
- callType = CallType.RoomCall(
- sessionId = A_SESSION_ID,
- roomId = A_ROOM_ID,
- ),
- callState = CallState.InCall,
+ manager.joinedCall(CallType.RoomCall(A_SESSION_ID, A_ROOM_ID))
+ assertThat(manager.activeCall.value).isEqualTo(
+ ActiveCall(
+ callType = CallType.RoomCall(
+ sessionId = A_SESSION_ID,
+ roomId = A_ROOM_ID,
+ ),
+ callState = CallState.InCall,
+ )
)
- )
- runCurrent()
+ runCurrent()
- verify { notificationManagerCompat.cancel(notificationId) }
+ verify { notificationManagerCompat.cancel(notificationId) }
+ }
}
- private fun TestScope.createActiveCallManager(
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun `observeRingingCalls - will cancel the active ringing call if the call is cancelled`() = runTest {
+ val room = FakeMatrixRoom().apply {
+ givenRoomInfo(aRoomInfo())
+ }
+ val client = FakeMatrixClient().apply {
+ givenGetRoomResult(A_ROOM_ID, room)
+ }
+ // Create a cancellable coroutine scope to cancel the test when needed
+ inCancellableScope {
+ val matrixClientProvider = FakeMatrixClientProvider(getClient = { Result.success(client) })
+ val manager = createActiveCallManager(matrixClientProvider = matrixClientProvider)
+
+ manager.registerIncomingCall(aCallNotificationData())
+
+ // Call is active (the other user join the call)
+ room.givenRoomInfo(aRoomInfo(hasRoomCall = true))
+ advanceTimeBy(1)
+ // Call is cancelled (the other user left the call)
+ room.givenRoomInfo(aRoomInfo(hasRoomCall = false))
+ advanceTimeBy(1)
+
+ assertThat(manager.activeCall.value).isNull()
+ }
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun `observeRingingCalls - will do nothing if either the session or the room are not found`() = runTest {
+ val room = FakeMatrixRoom().apply {
+ givenRoomInfo(aRoomInfo())
+ }
+ val client = FakeMatrixClient().apply {
+ givenGetRoomResult(A_ROOM_ID, room)
+ }
+ // Create a cancellable coroutine scope to cancel the test when needed
+ inCancellableScope {
+ val matrixClientProvider = FakeMatrixClientProvider(getClient = { Result.failure(IllegalStateException("Matrix client not found")) })
+ val manager = createActiveCallManager(matrixClientProvider = matrixClientProvider)
+
+ // No matrix client
+
+ manager.registerIncomingCall(aCallNotificationData())
+
+ room.givenRoomInfo(aRoomInfo(hasRoomCall = true))
+ advanceTimeBy(1)
+ room.givenRoomInfo(aRoomInfo(hasRoomCall = false))
+ advanceTimeBy(1)
+
+ // The call should still be active
+ assertThat(manager.activeCall.value).isNotNull()
+
+ // No room
+ client.givenGetRoomResult(A_ROOM_ID, null)
+ matrixClientProvider.getClient = { Result.success(client) }
+
+ manager.registerIncomingCall(aCallNotificationData())
+
+ room.givenRoomInfo(aRoomInfo(hasRoomCall = true))
+ advanceTimeBy(1)
+ room.givenRoomInfo(aRoomInfo(hasRoomCall = false))
+ advanceTimeBy(1)
+
+ // The call should still be active
+ assertThat(manager.activeCall.value).isNotNull()
+ }
+ }
+
+ private fun TestScope.inCancellableScope(block: suspend CoroutineScope.() -> Unit) {
+ launch(SupervisorJob()) {
+ block()
+ cancel()
+ }
+ }
+
+ private fun CoroutineScope.createActiveCallManager(
matrixClientProvider: FakeMatrixClientProvider = FakeMatrixClientProvider(),
onMissedCallNotificationHandler: FakeOnMissedCallNotificationHandler = FakeOnMissedCallNotificationHandler(),
notificationManagerCompat: NotificationManagerCompat = mockk(relaxed = true),
+ coroutineScope: CoroutineScope = this,
) = DefaultActiveCallManager(
- coroutineScope = this,
+ coroutineScope = coroutineScope,
onMissedCallNotificationHandler = onMissedCallNotificationHandler,
ringingCallNotificationCreator = RingingCallNotificationCreator(
context = InstrumentationRegistry.getInstrumentation().targetContext,
@@ -209,5 +307,6 @@ class DefaultActiveCallManagerTest {
notificationBitmapLoader = FakeNotificationBitmapLoader(),
),
notificationManagerCompat = notificationManagerCompat,
+ matrixClientProvider = matrixClientProvider,
)
}
diff --git a/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/DefaultCallWidgetProviderTest.kt b/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/DefaultCallWidgetProviderTest.kt
index 7eb426c35e..a0c2f9a80e 100644
--- a/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/DefaultCallWidgetProviderTest.kt
+++ b/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/DefaultCallWidgetProviderTest.kt
@@ -54,9 +54,9 @@ class DefaultCallWidgetProviderTest {
@Test
fun `getWidget - fails if it can't generate the URL for the widget`() = runTest {
- val room = FakeMatrixRoom().apply {
- givenGenerateWidgetWebViewUrlResult(Result.failure(Exception("Can't generate URL for widget")))
- }
+ val room = FakeMatrixRoom(
+ generateWidgetWebViewUrlResult = { _, _, _, _ -> Result.failure(Exception("Can't generate URL for widget")) }
+ )
val client = FakeMatrixClient().apply {
givenGetRoomResult(A_ROOM_ID, room)
}
@@ -66,10 +66,10 @@ class DefaultCallWidgetProviderTest {
@Test
fun `getWidget - fails if it can't get the widget driver`() = runTest {
- val room = FakeMatrixRoom().apply {
- givenGenerateWidgetWebViewUrlResult(Result.success("url"))
- givenGetWidgetDriverResult(Result.failure(Exception("Can't get a widget driver")))
- }
+ val room = FakeMatrixRoom(
+ generateWidgetWebViewUrlResult = { _, _, _, _ -> Result.success("url") },
+ getWidgetDriverResult = { Result.failure(Exception("Can't get a widget driver")) }
+ )
val client = FakeMatrixClient().apply {
givenGetRoomResult(A_ROOM_ID, room)
}
@@ -79,10 +79,10 @@ class DefaultCallWidgetProviderTest {
@Test
fun `getWidget - returns a widget driver when all steps are successful`() = runTest {
- val room = FakeMatrixRoom().apply {
- givenGenerateWidgetWebViewUrlResult(Result.success("url"))
- givenGetWidgetDriverResult(Result.success(FakeMatrixWidgetDriver()))
- }
+ val room = FakeMatrixRoom(
+ generateWidgetWebViewUrlResult = { _, _, _, _ -> Result.success("url") },
+ getWidgetDriverResult = { Result.success(FakeMatrixWidgetDriver()) },
+ )
val client = FakeMatrixClient().apply {
givenGetRoomResult(A_ROOM_ID, room)
}
@@ -92,10 +92,10 @@ class DefaultCallWidgetProviderTest {
@Test
fun `getWidget - will use a custom base url if it exists`() = runTest {
- val room = FakeMatrixRoom().apply {
- givenGenerateWidgetWebViewUrlResult(Result.success("url"))
- givenGetWidgetDriverResult(Result.success(FakeMatrixWidgetDriver()))
- }
+ val room = FakeMatrixRoom(
+ generateWidgetWebViewUrlResult = { _, _, _, _ -> Result.success("url") },
+ getWidgetDriverResult = { Result.success(FakeMatrixWidgetDriver()) },
+ )
val client = FakeMatrixClient().apply {
givenGetRoomResult(A_ROOM_ID, room)
}
@@ -120,10 +120,10 @@ class DefaultCallWidgetProviderTest {
val elementCallBaseUrlProvider = FakeElementCallBaseUrlProvider { matrixClient ->
providesLambda(matrixClient)
}
- val room = FakeMatrixRoom().apply {
- givenGenerateWidgetWebViewUrlResult(Result.success("url"))
- givenGetWidgetDriverResult(Result.success(FakeMatrixWidgetDriver()))
- }
+ val room = FakeMatrixRoom(
+ generateWidgetWebViewUrlResult = { _, _, _, _ -> Result.success("url") },
+ getWidgetDriverResult = { Result.success(FakeMatrixWidgetDriver()) },
+ )
val client = FakeMatrixClient().apply {
givenGetRoomResult(A_ROOM_ID, room)
}
diff --git a/features/createroom/impl/src/main/res/values-pl/translations.xml b/features/createroom/impl/src/main/res/values-pl/translations.xml
new file mode 100644
index 0000000000..eb64b46d8c
--- /dev/null
+++ b/features/createroom/impl/src/main/res/values-pl/translations.xml
@@ -0,0 +1,14 @@
+
+
+ "Nowy pokój"
+ "Zaproś znajomych"
+ "Wystąpił błąd podczas tworzenia pokoju"
+ "Wiadomości w tym pokoju są szyfrowane. Szyfrowania nie można później wyłączyć."
+ "Pokój prywatny (tylko zaproszenie)"
+ "Wiadomości nie są szyfrowane i każdy może je odczytać. Możesz aktywować szyfrowanie później."
+ "Pokój publiczny (każdy)"
+ "Nazwa pokoju"
+ "Utwórz pokój"
+ "Temat (opcjonalnie)"
+ "Wystąpił błąd podczas próby rozpoczęcia czatu"
+
diff --git a/features/createroom/impl/src/main/res/values-pt-rBR/translations.xml b/features/createroom/impl/src/main/res/values-pt-rBR/translations.xml
new file mode 100644
index 0000000000..bc61411edc
--- /dev/null
+++ b/features/createroom/impl/src/main/res/values-pt-rBR/translations.xml
@@ -0,0 +1,14 @@
+
+
+ "Nova sala"
+ "Convidar pessoas"
+ "Ocorreu um erro ao criar a sala"
+ "As mensagens nesta sala serão criptografadas. A criptografia não pode ser desativada posteriormente."
+ "Sala privativa (somente por convite)"
+ "As mensagens não serão criptografadas e qualquer pessoa pode lê-las. Você pode ativar a criptografia posteriormente."
+ "Sala pública (qualquer pessoa)"
+ "Nome da sala"
+ "Criar uma sala"
+ "Tópico (opcional)"
+ "Ocorreu um erro ao tentar iniciar um chat"
+
diff --git a/features/ftue/impl/src/main/res/values-pl/translations.xml b/features/ftue/impl/src/main/res/values-pl/translations.xml
new file mode 100644
index 0000000000..b1c1909ac2
--- /dev/null
+++ b/features/ftue/impl/src/main/res/values-pl/translations.xml
@@ -0,0 +1,11 @@
+
+
+ "Możesz zmienić ustawienia później."
+ "Zezwól na powiadomienia i nie przegap żadnej wiadomości"
+ "Połączenia, ankiety, wyszukiwanie i inne zostaną dodane później w tym roku."
+ "Historia wiadomości dla pokoi szyfrowanych nie jest jeszcze dostępna."
+ "Chętnie poznamy Twoją opinię. Daj nam znać, co myślisz na stronie ustawień."
+ "Naprzód!"
+ "Oto, co musisz wiedzieć:"
+ "Witamy w %1$s!"
+
diff --git a/features/ftue/impl/src/main/res/values-pt-rBR/translations.xml b/features/ftue/impl/src/main/res/values-pt-rBR/translations.xml
new file mode 100644
index 0000000000..1528027bd4
--- /dev/null
+++ b/features/ftue/impl/src/main/res/values-pt-rBR/translations.xml
@@ -0,0 +1,11 @@
+
+
+ "Você pode alterar suas configurações mais tarde."
+ "Permita notificações e nunca perca uma mensagem"
+ "Chamadas, enquetes, pesquisa e muito mais serão adicionadas ainda este ano."
+ "O histórico de mensagens para salas criptografadas ainda não está disponível."
+ "Adoraríamos ouvir sua opinião. Deixe-nos saber o que você pensa através da página de configurações."
+ "Vamos lá!"
+ "Aqui está o que você precisa saber:"
+ "Bem-vindo ao %1$s!"
+
diff --git a/features/invite/impl/src/main/res/values-pl/translations.xml b/features/invite/impl/src/main/res/values-pl/translations.xml
new file mode 100644
index 0000000000..e33b1ae106
--- /dev/null
+++ b/features/invite/impl/src/main/res/values-pl/translations.xml
@@ -0,0 +1,9 @@
+
+
+ "Czy na pewno chcesz odrzucić zaproszenie do dołączenia do %1$s?"
+ "Odrzuć zaproszenie"
+ "Czy na pewno chcesz odrzucić rozmowę prywatną z %1$s?"
+ "Odrzuć czat"
+ "Brak zaproszeń"
+ "%1$s (%2$s) zaprosił Cię"
+
diff --git a/features/invite/impl/src/main/res/values-pt-rBR/translations.xml b/features/invite/impl/src/main/res/values-pt-rBR/translations.xml
new file mode 100644
index 0000000000..9cd284690c
--- /dev/null
+++ b/features/invite/impl/src/main/res/values-pt-rBR/translations.xml
@@ -0,0 +1,9 @@
+
+
+ "Tem certeza de que deseja recusar o convite para ingressar em %1$s?"
+ "Recusar convite"
+ "Tem certeza de que deseja recusar esse chat privado com %1$s?"
+ "Recusar chat"
+ "Sem convites"
+ "%1$s(%2$s) convidou você"
+
diff --git a/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInvitePresenterTest.kt b/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInvitePresenterTest.kt
index 9a12e33b33..39a0321b01 100644
--- a/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInvitePresenterTest.kt
+++ b/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInvitePresenterTest.kt
@@ -92,9 +92,9 @@ class AcceptDeclineInvitePresenterTest {
val client = FakeMatrixClient().apply {
givenGetRoomResult(
roomId = A_ROOM_ID,
- result = FakeMatrixRoom().apply {
+ result = FakeMatrixRoom(
leaveRoomLambda = declineInviteFailure
- }
+ )
)
}
val presenter = createAcceptDeclineInvitePresenter(client = client)
@@ -142,9 +142,9 @@ class AcceptDeclineInvitePresenterTest {
val client = FakeMatrixClient().apply {
givenGetRoomResult(
roomId = A_ROOM_ID,
- result = FakeMatrixRoom().apply {
+ result = FakeMatrixRoom(
leaveRoomLambda = declineInviteSuccess
- }
+ )
)
}
val presenter = createAcceptDeclineInvitePresenter(
diff --git a/features/leaveroom/api/src/main/res/values-pl/translations.xml b/features/leaveroom/api/src/main/res/values-pl/translations.xml
new file mode 100644
index 0000000000..0fd8769389
--- /dev/null
+++ b/features/leaveroom/api/src/main/res/values-pl/translations.xml
@@ -0,0 +1,7 @@
+
+
+ "Czy na pewno chcesz opuścić tę konwersację? Konwersacja nie jest publiczna i nie będziesz mógł dołączyć ponownie bez zaproszenia."
+ "Jesteś pewien, że chcesz opuścić ten pokój? Jesteś tu jedyną osobą. Jeśli wyjdziesz, nikt nie będzie mógł dołączyć, w tym Ty."
+ "Czy na pewno chcesz opuścić ten pokój? Ten pokój nie jest publiczny i nie będziesz mógł do niego wrócić bez zaproszenia."
+ "Jesteś pewien, że chcesz wyjść z tego pokoju?"
+
diff --git a/features/leaveroom/api/src/main/res/values-pt-rBR/translations.xml b/features/leaveroom/api/src/main/res/values-pt-rBR/translations.xml
new file mode 100644
index 0000000000..bb4f35d07f
--- /dev/null
+++ b/features/leaveroom/api/src/main/res/values-pt-rBR/translations.xml
@@ -0,0 +1,6 @@
+
+
+ "Tem certeza de que deseja sair desta sala? Você é a única pessoa aqui. Se você sair, ninguém poderá ingressar no futuro, inclusive você."
+ "Tem certeza de que deseja sair desta sala? Esta sala não é pública e você não poderá entrar novamente sem um convite."
+ "Tem certeza de que deseja sair da sala?"
+
diff --git a/features/leaveroom/impl/src/test/kotlin/io/element/android/features/leaveroom/impl/DefaultLeaveRoomPresenterTest.kt b/features/leaveroom/impl/src/test/kotlin/io/element/android/features/leaveroom/impl/DefaultLeaveRoomPresenterTest.kt
index 49b44851de..a063b9ca90 100644
--- a/features/leaveroom/impl/src/test/kotlin/io/element/android/features/leaveroom/impl/DefaultLeaveRoomPresenterTest.kt
+++ b/features/leaveroom/impl/src/test/kotlin/io/element/android/features/leaveroom/impl/DefaultLeaveRoomPresenterTest.kt
@@ -140,7 +140,9 @@ class DefaultLeaveRoomPresenterTest {
client = FakeMatrixClient().apply {
givenGetRoomResult(
roomId = A_ROOM_ID,
- result = FakeMatrixRoom(),
+ result = FakeMatrixRoom(
+ leaveRoomLambda = { Result.success(Unit) }
+ ),
)
},
roomMembershipObserver = roomMembershipObserver
@@ -162,9 +164,9 @@ class DefaultLeaveRoomPresenterTest {
client = FakeMatrixClient().apply {
givenGetRoomResult(
roomId = A_ROOM_ID,
- result = FakeMatrixRoom().apply {
- this.leaveRoomLambda = { Result.failure(RuntimeException("Blimey!")) }
- },
+ result = FakeMatrixRoom(
+ leaveRoomLambda = { Result.failure(RuntimeException("Blimey!")) }
+ ),
)
}
)
@@ -186,7 +188,9 @@ class DefaultLeaveRoomPresenterTest {
client = FakeMatrixClient().apply {
givenGetRoomResult(
roomId = A_ROOM_ID,
- result = FakeMatrixRoom(),
+ result = FakeMatrixRoom(
+ leaveRoomLambda = { Result.success(Unit) }
+ ),
)
}
)
@@ -208,9 +212,9 @@ class DefaultLeaveRoomPresenterTest {
client = FakeMatrixClient().apply {
givenGetRoomResult(
roomId = A_ROOM_ID,
- result = FakeMatrixRoom().apply {
- this.leaveRoomLambda = { Result.failure(RuntimeException("Blimey!")) }
- },
+ result = FakeMatrixRoom(
+ leaveRoomLambda = { Result.failure(RuntimeException("Blimey!")) }
+ ),
)
}
)
diff --git a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/send/SendLocationPresenterTest.kt b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/send/SendLocationPresenterTest.kt
index 93e15f7868..24fcd3537c 100644
--- a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/send/SendLocationPresenterTest.kt
+++ b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/send/SendLocationPresenterTest.kt
@@ -29,13 +29,15 @@ import io.element.android.features.location.impl.common.permissions.PermissionsE
import io.element.android.features.location.impl.common.permissions.PermissionsPresenter
import io.element.android.features.location.impl.common.permissions.PermissionsState
import io.element.android.features.messages.test.FakeMessageComposerContext
+import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.location.AssetType
import io.element.android.libraries.matrix.test.core.aBuildMeta
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
-import io.element.android.libraries.matrix.test.room.SendLocationInvocation
import io.element.android.libraries.textcomposer.model.MessageComposerMode
import io.element.android.services.analytics.test.FakeAnalyticsService
import io.element.android.tests.testutils.WarmUpRule
+import io.element.android.tests.testutils.lambda.lambdaRecorder
+import io.element.android.tests.testutils.lambda.value
import kotlinx.coroutines.delay
import kotlinx.coroutines.test.runTest
import org.junit.Rule
@@ -46,16 +48,18 @@ class SendLocationPresenterTest {
val warmUpRule = WarmUpRule()
private val fakePermissionsPresenter = FakePermissionsPresenter()
- private val fakeMatrixRoom = FakeMatrixRoom()
private val fakeAnalyticsService = FakeAnalyticsService()
private val fakeMessageComposerContext = FakeMessageComposerContext()
private val fakeLocationActions = FakeLocationActions()
private val fakeBuildMeta = aBuildMeta(applicationName = "app name")
- private val sendLocationPresenter: SendLocationPresenter = SendLocationPresenter(
+
+ private fun createSendLocationPresenter(
+ matrixRoom: MatrixRoom = FakeMatrixRoom(),
+ ): SendLocationPresenter = SendLocationPresenter(
permissionsPresenterFactory = object : PermissionsPresenter.Factory {
override fun create(permissions: List): PermissionsPresenter = fakePermissionsPresenter
},
- room = fakeMatrixRoom,
+ room = matrixRoom,
analyticsService = fakeAnalyticsService,
messageComposerContext = fakeMessageComposerContext,
locationActions = fakeLocationActions,
@@ -64,6 +68,7 @@ class SendLocationPresenterTest {
@Test
fun `initial state with permissions granted`() = runTest {
+ val sendLocationPresenter = createSendLocationPresenter()
fakePermissionsPresenter.givenState(
aPermissionsState(
permissions = PermissionsState.Permissions.AllGranted,
@@ -90,6 +95,7 @@ class SendLocationPresenterTest {
@Test
fun `initial state with permissions partially granted`() = runTest {
+ val sendLocationPresenter = createSendLocationPresenter()
fakePermissionsPresenter.givenState(
aPermissionsState(
permissions = PermissionsState.Permissions.SomeGranted,
@@ -116,6 +122,7 @@ class SendLocationPresenterTest {
@Test
fun `initial state with permissions denied`() = runTest {
+ val sendLocationPresenter = createSendLocationPresenter()
fakePermissionsPresenter.givenState(
aPermissionsState(
permissions = PermissionsState.Permissions.NoneGranted,
@@ -142,6 +149,7 @@ class SendLocationPresenterTest {
@Test
fun `initial state with permissions denied once`() = runTest {
+ val sendLocationPresenter = createSendLocationPresenter()
fakePermissionsPresenter.givenState(
aPermissionsState(
permissions = PermissionsState.Permissions.NoneGranted,
@@ -168,6 +176,7 @@ class SendLocationPresenterTest {
@Test
fun `rationale dialog dismiss`() = runTest {
+ val sendLocationPresenter = createSendLocationPresenter()
fakePermissionsPresenter.givenState(
aPermissionsState(
permissions = PermissionsState.Permissions.NoneGranted,
@@ -199,6 +208,7 @@ class SendLocationPresenterTest {
@Test
fun `rationale dialog continue`() = runTest {
+ val sendLocationPresenter = createSendLocationPresenter()
fakePermissionsPresenter.givenState(
aPermissionsState(
permissions = PermissionsState.Permissions.NoneGranted,
@@ -227,6 +237,7 @@ class SendLocationPresenterTest {
@Test
fun `permission denied dialog dismiss`() = runTest {
+ val sendLocationPresenter = createSendLocationPresenter()
fakePermissionsPresenter.givenState(
aPermissionsState(
permissions = PermissionsState.Permissions.NoneGranted,
@@ -258,6 +269,13 @@ class SendLocationPresenterTest {
@Test
fun `share sender location`() = runTest {
+ val sendLocationResult = lambdaRecorder> { _, _, _, _, _ ->
+ Result.success(Unit)
+ }
+ val matrixRoom = FakeMatrixRoom(
+ sendLocationResult = sendLocationResult,
+ )
+ val sendLocationPresenter = createSendLocationPresenter(matrixRoom)
fakePermissionsPresenter.givenState(
aPermissionsState(
permissions = PermissionsState.Permissions.AllGranted,
@@ -289,16 +307,14 @@ class SendLocationPresenterTest {
delay(1) // Wait for the coroutine to finish
- assertThat(fakeMatrixRoom.sentLocations.size).isEqualTo(1)
- assertThat(fakeMatrixRoom.sentLocations.last()).isEqualTo(
- SendLocationInvocation(
- body = "Location was shared at geo:3.0,4.0;u=5.0",
- geoUri = "geo:3.0,4.0;u=5.0",
- description = null,
- zoomLevel = 15,
- assetType = AssetType.SENDER
+ sendLocationResult.assertions().isCalledOnce()
+ .with(
+ value("Location was shared at geo:3.0,4.0;u=5.0"),
+ value("geo:3.0,4.0;u=5.0"),
+ value(null),
+ value(15),
+ value(AssetType.SENDER),
)
- )
assertThat(fakeAnalyticsService.capturedEvents.size).isEqualTo(1)
assertThat(fakeAnalyticsService.capturedEvents.last()).isEqualTo(
@@ -314,6 +330,13 @@ class SendLocationPresenterTest {
@Test
fun `share pin location`() = runTest {
+ val sendLocationResult = lambdaRecorder> { _, _, _, _, _ ->
+ Result.success(Unit)
+ }
+ val matrixRoom = FakeMatrixRoom(
+ sendLocationResult = sendLocationResult,
+ )
+ val sendLocationPresenter = createSendLocationPresenter(matrixRoom)
fakePermissionsPresenter.givenState(
aPermissionsState(
permissions = PermissionsState.Permissions.NoneGranted,
@@ -345,16 +368,14 @@ class SendLocationPresenterTest {
delay(1) // Wait for the coroutine to finish
- assertThat(fakeMatrixRoom.sentLocations.size).isEqualTo(1)
- assertThat(fakeMatrixRoom.sentLocations.last()).isEqualTo(
- SendLocationInvocation(
- body = "Location was shared at geo:0.0,1.0",
- geoUri = "geo:0.0,1.0",
- description = null,
- zoomLevel = 15,
- assetType = AssetType.PIN
+ sendLocationResult.assertions().isCalledOnce()
+ .with(
+ value("Location was shared at geo:0.0,1.0"),
+ value("geo:0.0,1.0"),
+ value(null),
+ value(15),
+ value(AssetType.PIN),
)
- )
assertThat(fakeAnalyticsService.capturedEvents.size).isEqualTo(1)
assertThat(fakeAnalyticsService.capturedEvents.last()).isEqualTo(
@@ -370,6 +391,13 @@ class SendLocationPresenterTest {
@Test
fun `composer context passes through analytics`() = runTest {
+ val sendLocationResult = lambdaRecorder> { _, _, _, _, _ ->
+ Result.success(Unit)
+ }
+ val matrixRoom = FakeMatrixRoom(
+ sendLocationResult = sendLocationResult,
+ )
+ val sendLocationPresenter = createSendLocationPresenter(matrixRoom)
fakePermissionsPresenter.givenState(
aPermissionsState(
permissions = PermissionsState.Permissions.NoneGranted,
@@ -418,6 +446,7 @@ class SendLocationPresenterTest {
@Test
fun `open settings activity`() = runTest {
+ val sendLocationPresenter = createSendLocationPresenter()
fakePermissionsPresenter.givenState(
aPermissionsState(
permissions = PermissionsState.Permissions.NoneGranted,
@@ -452,6 +481,7 @@ class SendLocationPresenterTest {
@Test
fun `application name is in state`() = runTest {
+ val sendLocationPresenter = createSendLocationPresenter()
moleculeFlow(RecompositionMode.Immediate) {
sendLocationPresenter.present()
}.test {
diff --git a/features/lockscreen/impl/src/main/res/values-ka/translations.xml b/features/lockscreen/impl/src/main/res/values-ka/translations.xml
index 26fd97b671..022d4ee33c 100644
--- a/features/lockscreen/impl/src/main/res/values-ka/translations.xml
+++ b/features/lockscreen/impl/src/main/res/values-ka/translations.xml
@@ -1,12 +1,26 @@
+ "ბიომეტრიული ავტორიზაცია"
+ "ბიომეტრიული განბლოკვა"
+ "განბლოკვა ბიომეტრიით"
"დაგავიწყდათ PIN?"
"PIN კოდის შეცვლა"
"ბიომეტრიული განბლოკვის დაშვება"
"პინ კოდის წაშლა"
"დარწმუნებული ხართ, რომ გსურთ PIN-ის წაშლა?"
"გსურთ PIN-ის წაშლა?"
+ "%1$s დაშვება"
+ "მირჩევნია PIN-ის გამოყენება"
+ "დაზოგეთ დრო და გამოიყენეთ %1$s აპლიკაციის განსაბლოკად."
+ "აირჩიეთ PIN"
"დაადასტურეთ PIN"
+ "თქვენი ჩატების დამატებითი უსაფრთხოებისათვის დაბლოკეთ %1$s.
+
+აირჩიეთ რაიმე ისეთი, რაც დაგამახსოვრდებათ. თუ დაგავიწყდებათ ეს PIN, აპლიკაციიდან გამოხვალთ."
+ "თქვენ არ შეგიძლიათ აირჩიოთ ეს PIN კოდი უსაფრთხოების მიზეზების გამო"
+ "აირჩიეთ სხვა PIN"
+ "გთხოვთ შეიყვანოთ იგივე PIN ორჯერ"
+ "PIN-ები არ ემთხვევა"
"გასაგრძელებლად საჭიროა ხელახლა შესვლა და ახალი PIN-ის შექმნა"
"თქვენ ახლა გადიხართ…"
@@ -17,5 +31,7 @@
- "არასწორი PIN. თქვენ %1$d შანსი დაგრჩათ"
- "არასწორი PIN. თქვენ %1$d შანსი დაგრჩათ"
+ "გამოიყენეთ ბიომეტრია"
+ "გამოიყენეთ PIN"
"გასვლა…"
diff --git a/features/lockscreen/impl/src/main/res/values-pl/translations.xml b/features/lockscreen/impl/src/main/res/values-pl/translations.xml
new file mode 100644
index 0000000000..1eb904b13e
--- /dev/null
+++ b/features/lockscreen/impl/src/main/res/values-pl/translations.xml
@@ -0,0 +1,39 @@
+
+
+ "uwierzytelnienie biometryczne"
+ "odblokowanie biometryczne"
+ "Odblokuj za pomocą biometrii"
+ "Nie pamiętasz kodu PIN?"
+ "Zmień kod PIN"
+ "Zezwól na uwierzytelnienie biometryczne"
+ "Usuń PIN"
+ "Czy na pewno chcesz usunąć PIN?"
+ "Usunąć PIN?"
+ "Zezwól na %1$s"
+ "Wolę korzystać z kodu PIN"
+ "Zaoszczędź sobie trochę czasu i korzystaj z %1$s do odblokowywania aplikacji"
+ "Wybierz PIN"
+ "Potwierdź PIN"
+ "Zablokuj %1$s, aby zwiększyć bezpieczeństwo swoich czatów.
+
+Wybierz coś łatwego do zapamiętania. Jeśli zapomnisz tego PINU, zostaniesz wylogowany z aplikacji."
+ "Nie możesz wybrać tego PINU ze względów bezpieczeństwa"
+ "Wybierz inny kod PIN"
+ "Wprowadź ten sam kod PIN dwa razy"
+ "PINY nie pasują do siebie"
+ "Aby kontynuować, zaloguj się ponownie i utwórz nowy kod PIN"
+ "Trwa wylogowywanie"
+
+ - "Masz %1$d próbę, żeby odblokować"
+ - "Masz %1$d próby, żeby odblokować"
+ - "Masz %1$d prób, żeby odblokować"
+
+
+ - "Błędny PIN. Pozostała %1$d próba"
+ - "Błędny PIN. Pozostały %1$d próby"
+ - "Błędny PIN. Pozostało %1$d prób"
+
+ "Użyj biometrii"
+ "Użyj kodu PIN"
+ "Wylogowywanie…"
+
diff --git a/features/lockscreen/impl/src/main/res/values-pt-rBR/translations.xml b/features/lockscreen/impl/src/main/res/values-pt-rBR/translations.xml
new file mode 100644
index 0000000000..b7af1aba25
--- /dev/null
+++ b/features/lockscreen/impl/src/main/res/values-pt-rBR/translations.xml
@@ -0,0 +1,22 @@
+
+
+ "Esqueceu o PIN?"
+ "Mudar código de PIN"
+ "Permitir desbloqueio biométrico"
+ "Remover PIN"
+ "Tem certeza de que quer remover o PIN?"
+ "Remover PIN?"
+ "Escolher PIN"
+ "Confirmar PIN"
+ "Os PINs não correspondem"
+ "Você está sendo desconectado"
+
+ - "Você tem %1$d tentativa de debloqueio"
+ - "Você tem %1$d tentativas de debloqueio"
+
+
+ - "PIN incorreto. Você tem mais %1$d chance"
+ - "PIN incorreto. Você tem mais %1$d chances"
+
+ "Saindo…"
+
diff --git a/features/login/impl/src/main/res/values-ka/translations.xml b/features/login/impl/src/main/res/values-ka/translations.xml
index 2187fc9e95..84e97e0a34 100644
--- a/features/login/impl/src/main/res/values-ka/translations.xml
+++ b/features/login/impl/src/main/res/values-ka/translations.xml
@@ -27,6 +27,7 @@
"Matrix არის ღია ქსელი უსაფრთხო, დეცენტრალიზებული კომუნიკაციისთვის."
"კეთილი იყოს თქვენი მობრძანება!"
"შესვლა %1$s-ში"
+ "ხელახლა ცდა"
"შეცვალეთ ანგარიშის მომწოდებელი"
"კერძო სერვერი Element-ის თანამშრომლებისთვის."
"Matrix არის ღია ქსელი უსაფრთხო, დეცენტრალიზებული კომუნიკაციისთვის."
diff --git a/features/login/impl/src/main/res/values-pl/translations.xml b/features/login/impl/src/main/res/values-pl/translations.xml
new file mode 100644
index 0000000000..8adfbcb8a8
--- /dev/null
+++ b/features/login/impl/src/main/res/values-pl/translations.xml
@@ -0,0 +1,43 @@
+
+
+ "Zmień dostawcę konta"
+ "Adres serwera domowego"
+ "Wprowadź wyszukiwane hasło lub adres domeny."
+ "Szukaj serwera firmowego, społeczności lub prywatnego."
+ "Znajdź dostawcę konta"
+ "Tutaj będą przechowywane Twoje konwersacje - w podobnej formie jak wiadomości widnieją na skrzynce e-mail."
+ "Zamierzasz się zalogować %s"
+ "Tutaj będą przechowywane Twoje konwersacje - w podobnej formie jak wiadomości widnieją na skrzynce e-mail."
+ "Zamierzasz założyć konto na %s"
+ "Matrix.org jest ogromnym i darmowym serwerem na publicznej sieci Matrix zapewniający bezpieczną i zdecentralizowaną komunikację zarządzaną przez Fundację Matrix.org."
+ "Inne"
+ "Użyj innego dostawcy konta, takiego jak własny serwer lub konta służbowego."
+ "Zmień dostawcę konta"
+ "Nie mogliśmy połączyć się z tym serwerem domowym. Sprawdź, czy adres URL serwera został wprowadzony poprawnie. Jeśli adres URL jest poprawny, skontaktuj się z administratorem serwera w celu uzyskania dalszej pomocy."
+ "Ten serwer obecnie nie obsługuje technologii Sliding Sync."
+ "Adres URL serwera domowego"
+ "Możesz połączyć się tylko z serwerem, który obsługuje technologię Sliding Sync. Administrator serwera domowego będzie musiał ją skonfigurować. %1$s"
+ "Jaki jest adres Twojego serwera?"
+ "Wybierz swój serwer"
+ "To konto zostało dezaktywowane."
+ "Nieprawidłowa nazwa użytkownika i/lub hasło"
+ "To nie jest prawidłowy identyfikator użytkownika. Oczekiwany format: \'@user:homeserver.org\'"
+ "Wybrany serwer domowy nie obsługuje uwierzytelniania hasłem, ani OIDC. Skontaktuj się z jego administratorem lub wybierz inny serwer domowy."
+ "Wprowadź swoje dane"
+ "Matrix to otwarta sieć do bezpiecznej i zdecentralizowanej komunikacji."
+ "Witaj ponownie!"
+ "Zaloguj się do %1$s"
+ "Spróbuj ponownie"
+ "Zmień dostawcę konta"
+ "Serwer prywatny dla pracowników Element."
+ "Matrix to otwarta sieć do bezpiecznej i zdecentralizowanej komunikacji."
+ "Tutaj będą przechowywane Twoje konwersacje - w podobnej formie jak wiadomości widnieją na skrzynce e-mail."
+ "Zamierzasz się zalogować do %1$s"
+ "Zamierzasz utworzyć konto na %1$s"
+ "Obecnie istnieje duże zapotrzebowanie na %1$s na %2$s. Wróć do aplikacji za kilka dni i spróbuj ponownie.
+
+Dziękujemy za Twoją cierpliwość!"
+ "Witamy w %1$s!"
+ "Już prawie gotowe!"
+ "Witamy!"
+
diff --git a/features/login/impl/src/main/res/values-pt-rBR/translations.xml b/features/login/impl/src/main/res/values-pt-rBR/translations.xml
new file mode 100644
index 0000000000..4cb9b4f7b0
--- /dev/null
+++ b/features/login/impl/src/main/res/values-pt-rBR/translations.xml
@@ -0,0 +1,43 @@
+
+
+ "Alterar provedor da conta"
+ "Endereço do servidor"
+ "Insira um termo de pesquisa ou um endereço de domínio."
+ "Procure uma empresa, comunidade ou servidor privado."
+ "Encontre um provedor de contas"
+ "Aqui é onde suas conversas vão ficar — assim como você usa um provedor de e-mails para manter seus e-mails."
+ "Você está prestes a entrar em %s"
+ "Aqui é onde suas conversas vão ficar — assim como você usa um provedor de e-mails para manter seus e-mails."
+ "Você está prestes a criar uma conta em %s"
+ "O Matrix.org é um grande servidor gratuito na rede pública Matrix para comunicação segura e descentralizada, administrado pela Fundação Matrix.org."
+ "Outro"
+ "Use um provedor de conta diferente, como seu próprio servidor privado ou uma conta corporativa."
+ "Alterar provedor da conta"
+ "Não conseguimos acessar esse servidor. Verifique se você inseriu a URL do servidor corretamente. Se a URL estiver correta, entre em contato com o administrador do servidor para obter mais ajuda."
+ "Este servidor atualmente não oferece suporte à tecnologia sliding sync."
+ "URL do servidor"
+ "Você só pode se conectar a um servidor existente que ofereça suporte à tecnologia sliding sync. O administrador do seu servidor precisará configurá-lo. %1$s"
+ "Qual é o endereço do seu servidor?"
+ "Selecione seu servidor"
+ "Essa conta foi desativada."
+ "Nome de usuário e/ou senha incorretos"
+ "Esse não é um identificador de usuário válido. Formato esperado: \'@usuário:servidor.org\'"
+ "O servidor selecionado não suporta senha ou login no OIDC. Entre em contato com o administrador ou escolha outro servidor."
+ "Insira seus dados"
+ "A Matrix é uma rede aberta para comunicação segura e descentralizada."
+ "Bem-vindo de volta!"
+ "Iniciar sessão em %1$s"
+ "Tente novamente"
+ "Alterar provedor da conta"
+ "Um servidor privado para funcionários do Element."
+ "A Matrix é uma rede aberta para comunicação segura e descentralizada."
+ "Aqui é onde suas conversas vão ficar — assim como você usa um provedor de e-mails para manter seus e-mails."
+ "Você está prestes a fazer login em %1$s"
+ "Você está prestes a criar uma conta em %1$s"
+ "Há uma grande demanda por %1$s sobre %2$s no momento. Volte ao aplicativo em alguns dias e tente novamente.
+
+Obrigado pela sua paciência!"
+ "Bem-vindo ao %1$s!"
+ "Você está quase lá."
+ "Você está dentro."
+
diff --git a/features/logout/impl/src/main/res/values-ka/translations.xml b/features/logout/impl/src/main/res/values-ka/translations.xml
index 2226fd240f..adf15b8e46 100644
--- a/features/logout/impl/src/main/res/values-ka/translations.xml
+++ b/features/logout/impl/src/main/res/values-ka/translations.xml
@@ -4,5 +4,15 @@
"გამოსვლა"
"გამოსვლა"
"გასვლა…"
+ "თქვენ აპირებთ გასვლას თქვენი ბოლო სესიიდან. თუ ახლა გამოხვალთ, დაკარგავთ წვდომას თქვენს დაშიფრულ შეტყობინებებზე."
+ "თქვენ გამორთეთ სარეზერვო ასლი"
+ "თქვენი გასაღებების სარეზერვო ასლის შექმნა მიმდინარეობდა იმ დროს, როდესაც გამოხვედით. დაკავშირდით ისევ ისე, რომ სარეზერვო ასლი შეიქმნას ანგარიშიდან გამოსვლის გარეშე."
+ "თქვენი გასაღებების სარეზერვო ასლი ჯერ კიდევ შექმნის პროცესშია"
+ "გთხოვთ დაელოდეთ ამის დასრულებას სისტემიდან გამოსვლამდე."
+ "თქვენი გასაღებების სარეზერვო ასლი ჯერ კიდევ შექმნის პროცესშია"
"გამოსვლა"
+ "თქვენ აპირებთ გასვლას თქვენი ბოლო სესიიდან. თუ ახლა გამოხვალთ, დაკარგავთ წვდომას თქვენს დაშიფრულ შეტყობინებებზე."
+ "აღდგენა არ არის დაყენებული"
+ "თქვენ აპირებთ გასვლას თქვენი ბოლო სესიიდან. თუ ახლა გამოხვალთ, შესაძლოა დაკარგოთ წვდომა თქვენს დაშიფრულ შეტყობინებებზე."
+ "შეინახეთ თქვენი აღდგენის გასაღები?"
diff --git a/features/logout/impl/src/main/res/values-pl/translations.xml b/features/logout/impl/src/main/res/values-pl/translations.xml
new file mode 100644
index 0000000000..ca0d9dc4af
--- /dev/null
+++ b/features/logout/impl/src/main/res/values-pl/translations.xml
@@ -0,0 +1,18 @@
+
+
+ "Czy na pewno chcesz się wylogować?"
+ "Wyloguj się"
+ "Wyloguj się"
+ "Wylogowywanie…"
+ "Zamierzasz wylogować się ze swojej ostatniej sesji. Jeśli wylogujesz się teraz, stracisz dostęp do swoich wiadomości szyfrowanych."
+ "Wyłączyłeś backup"
+ "Twoje klucze były nadal archiwizowane po przejściu w tryb offline. Połącz się ponownie, aby zapisać w chmurze przed wylogowaniem."
+ "Twoje klucze są nadal archiwizowane"
+ "Zanim się wylogujesz, poczekaj na zakończenie operacji."
+ "Twoje klucze są nadal archiwizowane"
+ "Wyloguj się"
+ "Zamierzasz wylogować się ze swojej ostatniej sesji. Jeśli wylogujesz się teraz, stracisz dostęp do swoich wiadomości szyfrowanych."
+ "Nie ustawiono przywracania"
+ "Zamierzasz wylogować się ze swojej ostatniej sesji. Jeśli wylogujesz się teraz, stracisz dostęp do swoich wiadomości szyfrowanych."
+ "Czy zapisałeś swój klucz przywracania?"
+
diff --git a/features/logout/impl/src/main/res/values-pt-rBR/translations.xml b/features/logout/impl/src/main/res/values-pt-rBR/translations.xml
new file mode 100644
index 0000000000..65ccf4ca6f
--- /dev/null
+++ b/features/logout/impl/src/main/res/values-pt-rBR/translations.xml
@@ -0,0 +1,8 @@
+
+
+ "Você tem certeza que deseja sair?"
+ "Sair"
+ "Sair"
+ "Saindo…"
+ "Sair"
+
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt
index 7bd6bd1867..d0e50d114c 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt
@@ -60,6 +60,7 @@ import io.element.android.libraries.matrix.api.room.Mention
import io.element.android.libraries.matrix.api.room.draft.ComposerDraft
import io.element.android.libraries.matrix.api.room.draft.ComposerDraftType
import io.element.android.libraries.matrix.api.room.isDm
+import io.element.android.libraries.matrix.api.timeline.TimelineException
import io.element.android.libraries.matrix.ui.messages.RoomMemberProfilesCache
import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails
import io.element.android.libraries.matrix.ui.messages.reply.map
@@ -436,7 +437,14 @@ class MessageComposerPresenter @Inject constructor(
val eventId = capturedMode.eventId
val transactionId = capturedMode.transactionId
timelineController.invokeOnCurrentTimeline {
+ // First try to edit the message in the current timeline
editMessage(eventId, transactionId, message.markdown, message.html, message.mentions)
+ .onFailure { cause ->
+ if (cause is TimelineException.EventNotFound && eventId != null) {
+ // if the event is not found in the timeline, try to edit the message directly
+ room.editMessage(eventId, message.markdown, message.html, message.mentions)
+ }
+ }
}
}
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryPresenter.kt
index 5f9a2ea428..bba17d7f9d 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryPresenter.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryPresenter.kt
@@ -47,7 +47,7 @@ class ReactionSummaryPresenter @Inject constructor(
fun handleEvents(event: ReactionSummaryEvents) {
when (event) {
is ReactionSummaryEvents.ShowReactionSummary -> target.value = ReactionSummaryState.Summary(
- reactions = event.reactions,
+ reactions = event.reactions.toImmutableList(),
selectedKey = event.selectedKey,
selectedEventId = event.eventId
)
@@ -73,8 +73,8 @@ class ReactionSummaryPresenter @Inject constructor(
avatarUrl = member?.avatarUrl
)
sender.copy(user = user)
- })
- })
+ }.toImmutableList())
+ }.toImmutableList())
}
}
}
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryState.kt
index 1d1606e53d..1ccfdc9abf 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryState.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryState.kt
@@ -18,13 +18,14 @@ package io.element.android.features.messages.impl.timeline.components.reactionsu
import io.element.android.features.messages.impl.timeline.model.AggregatedReaction
import io.element.android.libraries.matrix.api.core.EventId
+import kotlinx.collections.immutable.ImmutableList
data class ReactionSummaryState(
val target: Summary?,
val eventSink: (ReactionSummaryEvents) -> Unit
) {
data class Summary(
- val reactions: List,
+ val reactions: ImmutableList,
val selectedKey: String,
val selectedEventId: EventId
)
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt
index 48141ccc03..9d9542d595 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt
@@ -264,27 +264,27 @@ class TimelineItemContentMessageFactory @Inject constructor(
}
private fun CharSequence.withFixedURLSpans(): CharSequence {
- if (this !is Spannable) return this
+ val spannable = this.toSpannable()
// Get all URL spans, as they will be removed by LinkifyCompat.addLinks
- val oldURLSpans = getSpans(0, length).associateWith {
- val start = getSpanStart(it)
- val end = getSpanEnd(it)
+ val oldURLSpans = spannable.getSpans(0, length).associateWith {
+ val start = spannable.getSpanStart(it)
+ val end = spannable.getSpanEnd(it)
Pair(start, end)
}
// Find and set as URLSpans any links present in the text
- LinkifyCompat.addLinks(this, Linkify.WEB_URLS or Linkify.PHONE_NUMBERS or Linkify.EMAIL_ADDRESSES)
+ LinkifyCompat.addLinks(spannable, Linkify.WEB_URLS or Linkify.PHONE_NUMBERS or Linkify.EMAIL_ADDRESSES)
// Restore old spans, remove new ones if there is a conflict
for ((urlSpan, location) in oldURLSpans) {
val (start, end) = location
- val addedSpans = getSpans(start, end).orEmpty()
+ val addedSpans = spannable.getSpans(start, end).orEmpty()
if (addedSpans.isNotEmpty()) {
for (span in addedSpans) {
- removeSpan(span)
+ spannable.removeSpan(span)
}
}
- setSpan(urlSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
+ spannable.setSpan(urlSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
- return this
+ return spannable
}
}
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt
index aea1019f11..c5d303b3f1 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt
@@ -117,6 +117,7 @@ class TimelineItemEventFactory @Inject constructor(
sentTime = timeFormatter.format(date),
)
}
+ .toImmutableList()
)
}
// Sort aggregated reactions by count and then timestamp ascending, using
@@ -127,7 +128,9 @@ class TimelineItemEventFactory @Inject constructor(
compareByDescending { it.count }
.thenBy { it.senders[0].timestamp }
)
- return TimelineItemReactions(aggregatedReactions.toImmutableList())
+ return TimelineItemReactions(
+ reactions = aggregatedReactions.toImmutableList()
+ )
}
private fun MatrixTimelineItem.Event.computeReadReceiptState(
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/AggregatedReaction.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/AggregatedReaction.kt
index 59c52ed8cf..cc6e956a1c 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/AggregatedReaction.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/AggregatedReaction.kt
@@ -18,6 +18,7 @@ package io.element.android.features.messages.impl.timeline.model
import io.element.android.libraries.core.extensions.ellipsize
import io.element.android.libraries.matrix.api.core.UserId
+import kotlinx.collections.immutable.ImmutableList
/**
* Length at which we ellipsize a reaction key for display
@@ -35,28 +36,22 @@ private const val MAX_DISPLAY_CHARS = 16
data class AggregatedReaction(
val currentUserId: UserId,
val key: String,
- val senders: List
+ val senders: ImmutableList
) {
/**
* The key to be displayed on screen.
*
* See [MAX_DISPLAY_CHARS].
*/
- val displayKey: String by lazy {
- key.ellipsize(MAX_DISPLAY_CHARS)
- }
+ val displayKey: String = key.ellipsize(MAX_DISPLAY_CHARS)
/**
* The number of users who reacted with this key.
*/
- val count: Int by lazy {
- senders.count()
- }
+ val count: Int = senders.count()
/**
* True if the reaction has (also) been sent by the current user.
*/
- val isHighlighted: Boolean by lazy {
- senders.any { it.senderId.value == currentUserId.value }
- }
+ val isHighlighted: Boolean = senders.any { it.senderId == currentUserId }
}
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/AggregatedReactionProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/AggregatedReactionProvider.kt
index dcd6bb105c..087de1753a 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/AggregatedReactionProvider.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/AggregatedReactionProvider.kt
@@ -18,6 +18,7 @@ package io.element.android.features.messages.impl.timeline.model
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.libraries.matrix.api.core.UserId
+import kotlinx.collections.immutable.toImmutableList
import java.text.DateFormat
import java.util.Date
@@ -53,6 +54,6 @@ fun anAggregatedReaction(
return AggregatedReaction(
currentUserId = userId,
key = key,
- senders = senders
+ senders = senders.toImmutableList()
)
}
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/AggregatedReactionSender.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/AggregatedReactionSender.kt
index 276ee0b266..987474bf04 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/AggregatedReactionSender.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/AggregatedReactionSender.kt
@@ -16,10 +16,12 @@
package io.element.android.features.messages.impl.timeline.model
+import androidx.compose.runtime.Immutable
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.user.MatrixUser
import java.util.Date
+@Immutable
data class AggregatedReactionSender(
val senderId: UserId,
val timestamp: Date,
diff --git a/features/messages/impl/src/main/res/values-ka/translations.xml b/features/messages/impl/src/main/res/values-ka/translations.xml
index 080696776f..47261affcc 100644
--- a/features/messages/impl/src/main/res/values-ka/translations.xml
+++ b/features/messages/impl/src/main/res/values-ka/translations.xml
@@ -21,8 +21,10 @@
"გამოკითხვა"
"ტექსტის ფორმატირება"
"შეტყობინებების ისტორია ამჟამად მიუწვდომელია."
+ "შეტყობინებების ისტორია ამ ოთახში მიუწვდომელია. დაადასტურეთ ეს მოწყობილობა თქვენი შეტყობინებების ისტორიის სანახავად."
"გსურთ მათი კვლავ მოწვევა?"
"თქვენ მარტო ხართ ამ ჩატში"
+ "მთელი ოთახისათვის შეტყობინება"
"ყველა"
"Ხელახლა გაგზავნა"
"თქვენი შეტყობინების გაგზავნა ვერ მოხერხდა"
diff --git a/features/messages/impl/src/main/res/values-pl/translations.xml b/features/messages/impl/src/main/res/values-pl/translations.xml
new file mode 100644
index 0000000000..ebb2b54369
--- /dev/null
+++ b/features/messages/impl/src/main/res/values-pl/translations.xml
@@ -0,0 +1,50 @@
+
+
+ "Aktywności"
+ "Flagi"
+ "Jedzenie i napoje"
+ "Zwierzęta i natura"
+ "Obiekty"
+ "Buźki i osoby"
+ "Podróż i miejsca"
+ "Symbole"
+ "Zablokuj użytkownika"
+ "Sprawdź, czy chcesz ukryć wszystkie bieżące i przyszłe wiadomości od tego użytkownika."
+ "Ta wiadomość zostanie zgłoszona do administratora Twojego serwera domowego. Nie będzie mógł on przeczytać żadnych zaszyfrowanych wiadomości."
+ "Powód zgłoszenia treści"
+ "Kamera"
+ "Zrób zdjęcie"
+ "Nagraj film"
+ "Załącznik"
+ "Zdjęcia i filmy"
+ "Lokalizacja"
+ "Ankieta"
+ "Formatowanie tekstu"
+ "Historia wiadomości jest obecnie niedostępna."
+ "Historia wiadomości jest niedostępna w tym pokoju. Zweryfikuj to urządzenie, aby zobaczyć historię wiadomości."
+ "Czy chcesz zaprosić ich z powrotem?"
+ "Jesteś sam na tym czacie"
+ "Powiadom cały pokój"
+ "Wszyscy"
+ "Wyślij ponownie"
+ "Nie udało się wysłać wiadomości"
+ "Dodaj emoji"
+ "To jest początek %1$s"
+ "To jest początek tej konwersacji"
+ "Pokaż mniej"
+ "Skopiowano wiadomość"
+ "Nie masz uprawnień, aby pisać w tym pokoju"
+ "Pokaż mniej"
+ "Pokaż więcej"
+ "Nowe"
+
+ - "%1$d zmiana pokoju"
+ - "%1$d zmian pokoju"
+ - "%1$d zmiany pokoju"
+
+
+ - "%1$s piszę"
+ - "%1$s piszą"
+ - "%1$s piszą"
+
+
diff --git a/features/messages/impl/src/main/res/values-pt-rBR/translations.xml b/features/messages/impl/src/main/res/values-pt-rBR/translations.xml
new file mode 100644
index 0000000000..6d324fd695
--- /dev/null
+++ b/features/messages/impl/src/main/res/values-pt-rBR/translations.xml
@@ -0,0 +1,42 @@
+
+
+ "Atividades"
+ "Bandeiras"
+ "Comida & Bebida"
+ "Animais & Natureza"
+ "Objetos"
+ "Sorrisos & Pessoas"
+ "Viagens & Lugares"
+ "Símbolos"
+ "Bloquear usuário"
+ "Marque se você deseja ocultar todas as mensagens atuais e futuras desse usuário"
+ "Essa mensagem será reportada ao administrador do seu homeserver. Eles não conseguirão ler nenhuma mensagem criptografada."
+ "Motivo para denunciar este conteúdo"
+ "Câmera"
+ "Tirar foto"
+ "Gravar vídeo"
+ "Anexo"
+ "Biblioteca de fotos e vídeos"
+ "Localização"
+ "Enquete"
+ "Formatação de texto"
+ "O histórico de mensagens não está disponível no momento."
+ "Gostaria de convidá-los de volta?"
+ "Você está sozinho neste chat"
+ "Todos"
+ "Enviar novamente"
+ "Sua mensagem não foi enviada"
+ "Adicionar emoji"
+ "Este é o início do %1$s."
+ "Este é o início desta conversa."
+ "Mostrar menos"
+ "Mensagem copiada"
+ "Você não tem permissão para postar nesta sala"
+ "Mostrar menos"
+ "Mostrar mais"
+ "Novo"
+
+ - "%1$d mudança de sala"
+ - "%1$d mudanças de salas"
+
+
diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt
index 2d08b8d4a2..091305893e 100644
--- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt
+++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt
@@ -65,6 +65,7 @@ import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.TransactionId
+import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.media.MediaSource
import io.element.android.libraries.matrix.api.permalink.PermalinkParser
import io.element.android.libraries.matrix.api.room.MatrixRoom
@@ -103,6 +104,7 @@ import io.element.android.tests.testutils.WarmUpRule
import io.element.android.tests.testutils.consumeItemsUntilPredicate
import io.element.android.tests.testutils.consumeItemsUntilTimeout
import io.element.android.tests.testutils.lambda.assert
+import io.element.android.tests.testutils.lambda.lambdaError
import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.lambda.value
import io.element.android.tests.testutils.testCoroutineDispatchers
@@ -136,7 +138,7 @@ class MessagesPresenterTest {
assertThat(initialState.roomAvatar)
.isEqualTo(AsyncData.Success(AvatarData(id = A_ROOM_ID.value, name = "", url = AN_AVATAR_URL, size = AvatarSize.TimelineRoom)))
assertThat(initialState.userHasPermissionToSendMessage).isTrue()
- assertThat(initialState.userHasPermissionToRedactOwn).isFalse()
+ assertThat(initialState.userHasPermissionToRedactOwn).isTrue()
assertThat(initialState.hasNetworkConnection).isTrue()
assertThat(initialState.snackbarMessage).isNull()
assertThat(initialState.inviteProgress).isEqualTo(AsyncData.Uninitialized)
@@ -147,7 +149,13 @@ class MessagesPresenterTest {
@OptIn(ExperimentalCoroutinesApi::class)
@Test
fun `present - check that the room's unread flag is removed`() = runTest {
- val room = FakeMatrixRoom()
+ val room = FakeMatrixRoom(
+ canUserSendMessageResult = { _, _ -> Result.success(true) },
+ canRedactOwnResult = { Result.success(true) },
+ canRedactOtherResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ typingNoticeResult = { Result.success(Unit) },
+ )
assertThat(room.markAsReadCalls).isEmpty()
val presenter = createMessagesPresenter(matrixRoom = room)
moleculeFlow(RecompositionMode.Immediate) {
@@ -161,8 +169,13 @@ class MessagesPresenterTest {
@Test
fun `present - call is disabled if user cannot join it even if there is an ongoing call`() = runTest {
- val room = FakeMatrixRoom().apply {
- givenCanUserJoinCall(Result.success(false))
+ val room = FakeMatrixRoom(
+ canUserJoinCallResult = { Result.success(false) },
+ canUserSendMessageResult = { _, _ -> Result.success(true) },
+ canRedactOwnResult = { Result.success(true) },
+ canRedactOtherResult = { Result.success(true) },
+ typingNoticeResult = { Result.success(Unit) },
+ ).apply {
givenRoomInfo(aRoomInfo(hasRoomCall = true))
}
val presenter = createMessagesPresenter(matrixRoom = room)
@@ -183,7 +196,14 @@ class MessagesPresenterTest {
val timeline = FakeTimeline().apply {
this.toggleReactionLambda = toggleReactionSuccess
}
- val room = FakeMatrixRoom(liveTimeline = timeline)
+ val room = FakeMatrixRoom(
+ liveTimeline = timeline,
+ canUserSendMessageResult = { _, _ -> Result.success(true) },
+ canRedactOwnResult = { Result.success(true) },
+ canRedactOtherResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ typingNoticeResult = { Result.success(Unit) },
+ )
val presenter = createMessagesPresenter(matrixRoom = room, coroutineDispatchers = coroutineDispatchers)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -213,7 +233,14 @@ class MessagesPresenterTest {
val timeline = FakeTimeline().apply {
this.toggleReactionLambda = toggleReactionSuccess
}
- val room = FakeMatrixRoom(liveTimeline = timeline)
+ val room = FakeMatrixRoom(
+ liveTimeline = timeline,
+ canUserSendMessageResult = { _, _ -> Result.success(true) },
+ canRedactOwnResult = { Result.success(true) },
+ canRedactOtherResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ typingNoticeResult = { Result.success(Unit) },
+ )
val presenter = createMessagesPresenter(matrixRoom = room, coroutineDispatchers = coroutineDispatchers)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -266,6 +293,11 @@ class MessagesPresenterTest {
val event = aMessageEvent()
val matrixRoom = FakeMatrixRoom(
eventPermalinkResult = { Result.success("a link") },
+ canUserSendMessageResult = { _, _ -> Result.success(true) },
+ canRedactOwnResult = { Result.success(true) },
+ canRedactOtherResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ typingNoticeResult = { Result.success(Unit) },
)
val presenter = createMessagesPresenter(
clipboardHelper = clipboardHelper,
@@ -448,7 +480,14 @@ class MessagesPresenterTest {
val coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true)
val liveTimeline = FakeTimeline()
- val matrixRoom = FakeMatrixRoom(liveTimeline = liveTimeline)
+ val matrixRoom = FakeMatrixRoom(
+ liveTimeline = liveTimeline,
+ canUserSendMessageResult = { _, _ -> Result.success(true) },
+ canRedactOwnResult = { Result.success(true) },
+ canRedactOtherResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ typingNoticeResult = { Result.success(Unit) },
+ )
val redactEventLambda = lambdaRecorder { _: EventId?, _: TransactionId?, _: String? -> Result.success(true) }
liveTimeline.redactEventLambda = redactEventLambda
@@ -513,7 +552,16 @@ class MessagesPresenterTest {
@Test
fun `present - shows prompt to reinvite users in DM`() = runTest {
- val room = FakeMatrixRoom(sessionId = A_SESSION_ID, isDirect = true, activeMemberCount = 1L)
+ val room = FakeMatrixRoom(
+ sessionId = A_SESSION_ID,
+ isDirect = true,
+ activeMemberCount = 1L,
+ canUserSendMessageResult = { _, _ -> Result.success(true) },
+ canRedactOwnResult = { Result.success(true) },
+ canRedactOtherResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ typingNoticeResult = { Result.success(Unit) },
+ )
val presenter = createMessagesPresenter(matrixRoom = room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -539,7 +587,16 @@ class MessagesPresenterTest {
@Test
fun `present - doesn't show reinvite prompt in non-direct room`() = runTest {
- val room = FakeMatrixRoom(sessionId = A_SESSION_ID, isDirect = false, activeMemberCount = 1L)
+ val room = FakeMatrixRoom(
+ sessionId = A_SESSION_ID,
+ isDirect = false,
+ activeMemberCount = 1L,
+ canUserSendMessageResult = { _, _ -> Result.success(true) },
+ canRedactOwnResult = { Result.success(true) },
+ canRedactOtherResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ typingNoticeResult = { Result.success(Unit) },
+ )
val presenter = createMessagesPresenter(matrixRoom = room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -554,7 +611,16 @@ class MessagesPresenterTest {
@Test
fun `present - doesn't show reinvite prompt if other party is present`() = runTest {
- val room = FakeMatrixRoom(sessionId = A_SESSION_ID, isDirect = true, activeMemberCount = 2L)
+ val room = FakeMatrixRoom(
+ sessionId = A_SESSION_ID,
+ isDirect = true,
+ activeMemberCount = 2L,
+ canUserSendMessageResult = { _, _ -> Result.success(true) },
+ canRedactOwnResult = { Result.success(true) },
+ canRedactOtherResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ typingNoticeResult = { Result.success(Unit) },
+ )
val presenter = createMessagesPresenter(matrixRoom = room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -569,7 +635,16 @@ class MessagesPresenterTest {
@Test
fun `present - handle reinviting other user when memberlist is ready`() = runTest {
- val room = FakeMatrixRoom(sessionId = A_SESSION_ID)
+ val inviteUserResult = lambdaRecorder { _: UserId -> Result.success(Unit) }
+ val room = FakeMatrixRoom(
+ sessionId = A_SESSION_ID,
+ inviteUserResult = inviteUserResult,
+ canUserSendMessageResult = { _, _ -> Result.success(true) },
+ canRedactOwnResult = { Result.success(true) },
+ canRedactOtherResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ typingNoticeResult = { Result.success(Unit) },
+ )
room.givenRoomMembersState(
MatrixRoomMembersState.Ready(
persistentListOf(
@@ -589,13 +664,22 @@ class MessagesPresenterTest {
assertThat(loadingState.inviteProgress.isLoading()).isTrue()
val newState = awaitItem()
assertThat(newState.inviteProgress.isSuccess()).isTrue()
- assertThat(room.invitedUserId).isEqualTo(A_SESSION_ID_2)
+ inviteUserResult.assertions().isCalledOnce().with(value(A_SESSION_ID_2))
}
}
@Test
fun `present - handle reinviting other user when memberlist is error`() = runTest {
- val room = FakeMatrixRoom(sessionId = A_SESSION_ID)
+ val inviteUserResult = lambdaRecorder { _: UserId -> Result.success(Unit) }
+ val room = FakeMatrixRoom(
+ sessionId = A_SESSION_ID,
+ inviteUserResult = inviteUserResult,
+ canUserSendMessageResult = { _, _ -> Result.success(true) },
+ canRedactOwnResult = { Result.success(true) },
+ canRedactOtherResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ typingNoticeResult = { Result.success(Unit) },
+ )
room.givenRoomMembersState(
MatrixRoomMembersState.Error(
failure = Throwable(),
@@ -618,13 +702,20 @@ class MessagesPresenterTest {
assertThat(loadingState.inviteProgress.isLoading()).isTrue()
val newState = awaitItem()
assertThat(newState.inviteProgress.isSuccess()).isTrue()
- assertThat(room.invitedUserId).isEqualTo(A_SESSION_ID_2)
+ inviteUserResult.assertions().isCalledOnce().with(value(A_SESSION_ID_2))
}
}
@Test
fun `present - handle reinviting other user when memberlist is not ready`() = runTest {
- val room = FakeMatrixRoom(sessionId = A_SESSION_ID)
+ val room = FakeMatrixRoom(
+ sessionId = A_SESSION_ID,
+ canUserSendMessageResult = { _, _ -> Result.success(true) },
+ canRedactOwnResult = { Result.success(true) },
+ canRedactOtherResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ typingNoticeResult = { Result.success(Unit) },
+ )
room.givenRoomMembersState(MatrixRoomMembersState.Unknown)
val presenter = createMessagesPresenter(matrixRoom = room)
moleculeFlow(RecompositionMode.Immediate) {
@@ -642,7 +733,15 @@ class MessagesPresenterTest {
@Test
fun `present - handle reinviting other user when inviting fails`() = runTest {
- val room = FakeMatrixRoom(sessionId = A_SESSION_ID)
+ val room = FakeMatrixRoom(
+ sessionId = A_SESSION_ID,
+ inviteUserResult = { Result.failure(Throwable("Oops!")) },
+ canUserSendMessageResult = { _, _ -> Result.success(true) },
+ canRedactOwnResult = { Result.success(true) },
+ canRedactOtherResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ typingNoticeResult = { Result.success(Unit) },
+ )
room.givenRoomMembersState(
MatrixRoomMembersState.Ready(
persistentListOf(
@@ -651,7 +750,6 @@ class MessagesPresenterTest {
)
)
)
- room.givenInviteUserResult(Result.failure(Throwable("Oops!")))
val presenter = createMessagesPresenter(matrixRoom = room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -671,8 +769,19 @@ class MessagesPresenterTest {
@Test
fun `present - permission to post`() = runTest {
- val matrixRoom = FakeMatrixRoom()
- matrixRoom.givenCanSendEventResult(MessageEventType.ROOM_MESSAGE, Result.success(true))
+ val matrixRoom = FakeMatrixRoom(
+ canUserSendMessageResult = { _, messageEventType ->
+ when (messageEventType) {
+ MessageEventType.ROOM_MESSAGE -> Result.success(true)
+ MessageEventType.REACTION -> Result.success(true)
+ else -> lambdaError()
+ }
+ },
+ canRedactOwnResult = { Result.success(true) },
+ canRedactOtherResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ typingNoticeResult = { Result.success(Unit) },
+ )
val presenter = createMessagesPresenter(matrixRoom = matrixRoom)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -684,8 +793,19 @@ class MessagesPresenterTest {
@Test
fun `present - no permission to post`() = runTest {
- val matrixRoom = FakeMatrixRoom()
- matrixRoom.givenCanSendEventResult(MessageEventType.ROOM_MESSAGE, Result.success(false))
+ val matrixRoom = FakeMatrixRoom(
+ canUserSendMessageResult = { _, messageEventType ->
+ when (messageEventType) {
+ MessageEventType.ROOM_MESSAGE -> Result.success(false)
+ MessageEventType.REACTION -> Result.success(false)
+ else -> lambdaError()
+ }
+ },
+ canRedactOwnResult = { Result.success(true) },
+ canRedactOtherResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ typingNoticeResult = { Result.success(Unit) },
+ )
val presenter = createMessagesPresenter(matrixRoom = matrixRoom)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -700,7 +820,13 @@ class MessagesPresenterTest {
@Test
fun `present - permission to redact own`() = runTest {
- val matrixRoom = FakeMatrixRoom(canRedactOwn = true)
+ val matrixRoom = FakeMatrixRoom(
+ canRedactOwnResult = { Result.success(true) },
+ canUserSendMessageResult = { _, _ -> Result.success(true) },
+ canRedactOtherResult = { Result.success(false) },
+ canUserJoinCallResult = { Result.success(true) },
+ typingNoticeResult = { Result.success(Unit) },
+ )
val presenter = createMessagesPresenter(matrixRoom = matrixRoom)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -714,7 +840,13 @@ class MessagesPresenterTest {
@Test
fun `present - permission to redact other`() = runTest {
- val matrixRoom = FakeMatrixRoom(canRedactOther = true)
+ val matrixRoom = FakeMatrixRoom(
+ canRedactOtherResult = { Result.success(true) },
+ canUserSendMessageResult = { _, _ -> Result.success(true) },
+ canRedactOwnResult = { Result.success(false) },
+ canUserJoinCallResult = { Result.success(true) },
+ typingNoticeResult = { Result.success(Unit) },
+ )
val presenter = createMessagesPresenter(matrixRoom = matrixRoom)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -754,7 +886,13 @@ class MessagesPresenterTest {
private fun TestScope.createMessagesPresenter(
coroutineDispatchers: CoroutineDispatchers = testCoroutineDispatchers(),
- matrixRoom: MatrixRoom = FakeMatrixRoom().apply {
+ matrixRoom: MatrixRoom = FakeMatrixRoom(
+ canUserSendMessageResult = { _, _ -> Result.success(true) },
+ canRedactOwnResult = { Result.success(true) },
+ canRedactOtherResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ typingNoticeResult = { Result.success(Unit) },
+ ).apply {
givenRoomInfo(aRoomInfo(id = roomId, name = ""))
},
navigator: FakeMessagesNavigator = FakeMessagesNavigator(),
diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/AttachmentsPreviewPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/AttachmentsPreviewPresenterTest.kt
index 0168fcbde5..e2fbeebe3a 100644
--- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/AttachmentsPreviewPresenterTest.kt
+++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/AttachmentsPreviewPresenterTest.kt
@@ -26,7 +26,9 @@ import com.google.common.truth.Truth.assertThat
import io.element.android.features.messages.impl.attachments.preview.AttachmentsPreviewEvents
import io.element.android.features.messages.impl.attachments.preview.AttachmentsPreviewPresenter
import io.element.android.features.messages.impl.attachments.preview.SendActionState
+import io.element.android.libraries.matrix.api.core.ProgressCallback
import io.element.android.libraries.matrix.api.room.MatrixRoom
+import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import io.element.android.libraries.mediaupload.api.MediaPreProcessor
import io.element.android.libraries.mediaupload.api.MediaSender
@@ -34,6 +36,7 @@ import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor
import io.element.android.libraries.mediaviewer.api.local.LocalMedia
import io.element.android.libraries.mediaviewer.test.viewer.aLocalMedia
import io.element.android.tests.testutils.WarmUpRule
+import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.mockk.mockk
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
@@ -49,13 +52,16 @@ class AttachmentsPreviewPresenterTest {
@Test
fun `present - send media success scenario`() = runTest {
- val room = FakeMatrixRoom()
- room.givenProgressCallbackValues(
- listOf(
+ val sendMediaResult = lambdaRecorder> {
+ Result.success(FakeMediaUploadHandler())
+ }
+ val room = FakeMatrixRoom(
+ progressCallbackValues = listOf(
Pair(0, 10),
Pair(5, 10),
Pair(10, 10)
- )
+ ),
+ sendMediaResult = sendMediaResult,
)
val presenter = createAttachmentsPreviewPresenter(room = room)
moleculeFlow(RecompositionMode.Immediate) {
@@ -70,15 +76,19 @@ class AttachmentsPreviewPresenterTest {
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Uploading(1f))
val successState = awaitItem()
assertThat(successState.sendActionState).isEqualTo(SendActionState.Done)
- assertThat(room.sendMediaCount).isEqualTo(1)
+ sendMediaResult.assertions().isCalledOnce()
}
}
@Test
fun `present - send media failure scenario`() = runTest {
- val room = FakeMatrixRoom()
val failure = MediaPreProcessor.Failure(null)
- room.givenSendMediaResult(Result.failure(failure))
+ val sendMediaResult = lambdaRecorder> {
+ Result.failure(failure)
+ }
+ val room = FakeMatrixRoom(
+ sendMediaResult = sendMediaResult,
+ )
val presenter = createAttachmentsPreviewPresenter(room = room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -90,7 +100,7 @@ class AttachmentsPreviewPresenterTest {
assertThat(loadingState.sendActionState).isEqualTo(SendActionState.Sending.Processing)
val failureState = awaitItem()
assertThat(failureState.sendActionState).isEqualTo(SendActionState.Failure(failure))
- assertThat(room.sendMediaCount).isEqualTo(0)
+ sendMediaResult.assertions().isCalledOnce()
failureState.eventSink(AttachmentsPreviewEvents.ClearSendState)
val clearedState = awaitItem()
assertThat(clearedState.sendActionState).isEqualTo(SendActionState.Idle)
diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/report/ReportMessagePresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/report/ReportMessagePresenterTest.kt
index b5408434de..a15ea45a6c 100644
--- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/report/ReportMessagePresenterTest.kt
+++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/report/ReportMessagePresenterTest.kt
@@ -22,11 +22,14 @@ import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
+import io.element.android.libraries.matrix.api.core.EventId
+import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.libraries.matrix.test.A_USER_ID
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import io.element.android.tests.testutils.WarmUpRule
+import io.element.android.tests.testutils.lambda.lambdaRecorder
import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.Test
@@ -81,7 +84,12 @@ class ReportMessagePresenterTest {
@Test
fun `presenter - handle successful report and block user`() = runTest {
- val room = FakeMatrixRoom()
+ val reportContentResult = lambdaRecorder> { _, _, _ ->
+ Result.success(Unit)
+ }
+ val room = FakeMatrixRoom(
+ reportContentResult = reportContentResult
+ )
val presenter = createReportMessagePresenter(matrixRoom = room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -92,13 +100,18 @@ class ReportMessagePresenterTest {
initialState.eventSink(ReportMessageEvents.Report)
assertThat(awaitItem().result).isInstanceOf(AsyncAction.Loading::class.java)
assertThat(awaitItem().result).isInstanceOf(AsyncAction.Success::class.java)
- assertThat(room.reportedContentCount).isEqualTo(1)
+ reportContentResult.assertions().isCalledOnce()
}
}
@Test
fun `presenter - handle successful report`() = runTest {
- val room = FakeMatrixRoom()
+ val reportContentResult = lambdaRecorder> { _, _, _ ->
+ Result.success(Unit)
+ }
+ val room = FakeMatrixRoom(
+ reportContentResult = reportContentResult
+ )
val presenter = createReportMessagePresenter(matrixRoom = room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -107,15 +120,18 @@ class ReportMessagePresenterTest {
initialState.eventSink(ReportMessageEvents.Report)
assertThat(awaitItem().result).isInstanceOf(AsyncAction.Loading::class.java)
assertThat(awaitItem().result).isInstanceOf(AsyncAction.Success::class.java)
- assertThat(room.reportedContentCount).isEqualTo(1)
+ reportContentResult.assertions().isCalledOnce()
}
}
@Test
fun `presenter - handle failed report`() = runTest {
- val room = FakeMatrixRoom().apply {
- givenReportContentResult(Result.failure(Exception("Failed to report content")))
+ val reportContentResult = lambdaRecorder> { _, _, _ ->
+ Result.failure(Exception("Failed to report content"))
}
+ val room = FakeMatrixRoom(
+ reportContentResult = reportContentResult
+ )
val presenter = createReportMessagePresenter(matrixRoom = room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -125,7 +141,7 @@ class ReportMessagePresenterTest {
assertThat(awaitItem().result).isInstanceOf(AsyncAction.Loading::class.java)
val resultState = awaitItem()
assertThat(resultState.result).isInstanceOf(AsyncAction.Failure::class.java)
- assertThat(room.reportedContentCount).isEqualTo(1)
+ reportContentResult.assertions().isCalledOnce()
resultState.eventSink(ReportMessageEvents.ClearError)
assertThat(awaitItem().result).isInstanceOf(AsyncAction.Uninitialized::class.java)
diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt
index de48ee62b6..dcb09fb7c8 100644
--- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt
+++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt
@@ -43,6 +43,7 @@ import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
import io.element.android.libraries.matrix.api.core.EventId
+import io.element.android.libraries.matrix.api.core.ProgressCallback
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.TransactionId
import io.element.android.libraries.matrix.api.media.ImageInfo
@@ -55,6 +56,7 @@ import io.element.android.libraries.matrix.api.room.Mention
import io.element.android.libraries.matrix.api.room.RoomMembershipState
import io.element.android.libraries.matrix.api.room.draft.ComposerDraft
import io.element.android.libraries.matrix.api.room.draft.ComposerDraftType
+import io.element.android.libraries.matrix.api.timeline.TimelineException
import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo
import io.element.android.libraries.matrix.test.ANOTHER_MESSAGE
import io.element.android.libraries.matrix.test.AN_EVENT_ID
@@ -67,6 +69,7 @@ import io.element.android.libraries.matrix.test.A_USER_ID_2
import io.element.android.libraries.matrix.test.A_USER_ID_3
import io.element.android.libraries.matrix.test.A_USER_ID_4
import io.element.android.libraries.matrix.test.core.aBuildMeta
+import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler
import io.element.android.libraries.matrix.test.permalink.FakePermalinkBuilder
import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
@@ -297,7 +300,13 @@ class MessageComposerPresenterTest {
@Test
fun `present - send message with rich text enabled`() = runTest {
- val presenter = createPresenter(this)
+ val presenter = createPresenter(
+ coroutineScope = this,
+ room = FakeMatrixRoom(
+ sendMessageResult = { _, _, _ -> Result.success(Unit) },
+ typingNoticeResult = { Result.success(Unit) }
+ ),
+ )
moleculeFlow(RecompositionMode.Immediate) {
val state = presenter.present()
remember(state, state.textEditorState.messageHtml()) { state }
@@ -324,7 +333,14 @@ class MessageComposerPresenterTest {
@Test
fun `present - send message with plain text enabled`() = runTest {
val permalinkBuilder = FakePermalinkBuilder(permalinkForUserLambda = { Result.success("") })
- val presenter = createPresenter(this, isRichTextEditorEnabled = false)
+ val presenter = createPresenter(
+ coroutineScope = this,
+ isRichTextEditorEnabled = false,
+ room = FakeMatrixRoom(
+ sendMessageResult = { _, _, _ -> Result.success(Unit) },
+ typingNoticeResult = { Result.success(Unit) }
+ ),
+ )
moleculeFlow(RecompositionMode.Immediate) {
val state = presenter.present()
val messageMarkdown = state.textEditorState.messageMarkdown(permalinkBuilder)
@@ -358,7 +374,10 @@ class MessageComposerPresenterTest {
val timeline = FakeTimeline().apply {
this.editMessageLambda = editMessageLambda
}
- val fakeMatrixRoom = FakeMatrixRoom(liveTimeline = timeline)
+ val fakeMatrixRoom = FakeMatrixRoom(
+ liveTimeline = timeline,
+ typingNoticeResult = { Result.success(Unit) }
+ )
val presenter = createPresenter(
this,
fakeMatrixRoom,
@@ -399,6 +418,67 @@ class MessageComposerPresenterTest {
}
}
+ @Test
+ fun `present - edit sent message event not found`() = runTest {
+ val timelineEditMessageLambda = lambdaRecorder { _: EventId?, _: TransactionId?, _: String, _: String?, _: List ->
+ Result.failure(TimelineException.EventNotFound)
+ }
+ val timeline = FakeTimeline().apply {
+ this.editMessageLambda = timelineEditMessageLambda
+ }
+ val roomEditMessageLambda = lambdaRecorder { _: EventId?, _: String, _: String?, _: List ->
+ Result.success(Unit)
+ }
+ val fakeMatrixRoom = FakeMatrixRoom(
+ liveTimeline = timeline,
+ typingNoticeResult = { Result.success(Unit) }
+ ).apply {
+ this.editMessageLambda = roomEditMessageLambda
+ }
+ val presenter = createPresenter(
+ this,
+ fakeMatrixRoom,
+ )
+ moleculeFlow(RecompositionMode.Immediate) {
+ val state = presenter.present()
+ remember(state, state.textEditorState.messageHtml()) { state }
+ }.test {
+ val initialState = awaitFirstItem()
+ assertThat(initialState.textEditorState.messageHtml()).isEqualTo("")
+ val mode = anEditMode()
+ initialState.eventSink.invoke(MessageComposerEvents.SetMode(mode))
+ val withMessageState = awaitItem()
+ assertThat(withMessageState.mode).isEqualTo(mode)
+ assertThat(withMessageState.textEditorState.messageHtml()).isEqualTo(A_MESSAGE)
+ withMessageState.textEditorState.setHtml(ANOTHER_MESSAGE)
+ val withEditedMessageState = awaitItem()
+ assertThat(withEditedMessageState.textEditorState.messageHtml()).isEqualTo(ANOTHER_MESSAGE)
+ withEditedMessageState.eventSink.invoke(MessageComposerEvents.SendMessage)
+ skipItems(1)
+ val messageSentState = awaitItem()
+ assertThat(messageSentState.textEditorState.messageHtml()).isEqualTo("")
+
+ advanceUntilIdle()
+
+ assert(timelineEditMessageLambda)
+ .isCalledOnce()
+ .with(value(AN_EVENT_ID), value(null), value(ANOTHER_MESSAGE), value(ANOTHER_MESSAGE), any())
+
+ assert(roomEditMessageLambda)
+ .isCalledOnce()
+ .with(value(AN_EVENT_ID), value(ANOTHER_MESSAGE), value(ANOTHER_MESSAGE), any())
+
+ assertThat(analyticsService.capturedEvents).containsExactly(
+ Composer(
+ inThread = false,
+ isEditing = true,
+ isReply = false,
+ messageType = Composer.MessageType.Text,
+ )
+ )
+ }
+ }
+
@Test
fun `present - edit not sent message`() = runTest {
val editMessageLambda = lambdaRecorder { _: EventId?, _: TransactionId?, _: String, _: String?, _: List ->
@@ -407,7 +487,10 @@ class MessageComposerPresenterTest {
val timeline = FakeTimeline().apply {
this.editMessageLambda = editMessageLambda
}
- val fakeMatrixRoom = FakeMatrixRoom(liveTimeline = timeline)
+ val fakeMatrixRoom = FakeMatrixRoom(
+ liveTimeline = timeline,
+ typingNoticeResult = { Result.success(Unit) },
+ )
val presenter = createPresenter(
this,
fakeMatrixRoom,
@@ -456,7 +539,10 @@ class MessageComposerPresenterTest {
val timeline = FakeTimeline().apply {
this.replyMessageLambda = replyMessageLambda
}
- val fakeMatrixRoom = FakeMatrixRoom(liveTimeline = timeline)
+ val fakeMatrixRoom = FakeMatrixRoom(
+ liveTimeline = timeline,
+ typingNoticeResult = { Result.success(Unit) }
+ )
val presenter = createPresenter(
this,
fakeMatrixRoom,
@@ -524,7 +610,9 @@ class MessageComposerPresenterTest {
@Test
fun `present - Pick image from gallery`() = runTest {
- val room = FakeMatrixRoom()
+ val room = FakeMatrixRoom(
+ typingNoticeResult = { Result.success(Unit) }
+ )
val presenter = createPresenter(this, room = room)
pickerProvider.givenMimeType(MimeTypes.Images)
mediaPreProcessor.givenResult(
@@ -557,7 +645,9 @@ class MessageComposerPresenterTest {
@Test
fun `present - Pick video from gallery`() = runTest {
- val room = FakeMatrixRoom()
+ val room = FakeMatrixRoom(
+ typingNoticeResult = { Result.success(Unit) }
+ )
val presenter = createPresenter(this, room = room)
pickerProvider.givenMimeType(MimeTypes.Videos)
mediaPreProcessor.givenResult(
@@ -607,13 +697,17 @@ class MessageComposerPresenterTest {
@Test
fun `present - Pick file from storage`() = runTest {
- val room = FakeMatrixRoom()
- room.givenProgressCallbackValues(
- listOf(
+ val sendMediaResult = lambdaRecorder { _: ProgressCallback? ->
+ Result.success(FakeMediaUploadHandler())
+ }
+ val room = FakeMatrixRoom(
+ progressCallbackValues = listOf(
Pair(0, 10),
Pair(5, 10),
Pair(10, 10)
- )
+ ),
+ sendMediaResult = sendMediaResult,
+ typingNoticeResult = { Result.success(Unit) }
)
val presenter = createPresenter(this, room = room)
moleculeFlow(RecompositionMode.Immediate) {
@@ -629,13 +723,15 @@ class MessageComposerPresenterTest {
assertThat(awaitItem().attachmentsState).isEqualTo(AttachmentsState.Sending.Uploading(1f))
val sentState = awaitItem()
assertThat(sentState.attachmentsState).isEqualTo(AttachmentsState.None)
- assertThat(room.sendMediaCount).isEqualTo(1)
+ sendMediaResult.assertions().isCalledOnce()
}
}
@Test
fun `present - create poll`() = runTest {
- val room = FakeMatrixRoom()
+ val room = FakeMatrixRoom(
+ typingNoticeResult = { Result.success(Unit) }
+ )
val presenter = createPresenter(this, room = room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -652,7 +748,9 @@ class MessageComposerPresenterTest {
@Test
fun `present - share location`() = runTest {
- val room = FakeMatrixRoom()
+ val room = FakeMatrixRoom(
+ typingNoticeResult = { Result.success(Unit) }
+ )
val presenter = createPresenter(this, room = room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -669,7 +767,9 @@ class MessageComposerPresenterTest {
@Test
fun `present - Take photo`() = runTest {
- val room = FakeMatrixRoom()
+ val room = FakeMatrixRoom(
+ typingNoticeResult = { Result.success(Unit) }
+ )
val permissionPresenter = FakePermissionsPresenter().apply { setPermissionGranted() }
val presenter = createPresenter(
this,
@@ -689,7 +789,9 @@ class MessageComposerPresenterTest {
@Test
fun `present - Take photo with permission request`() = runTest {
- val room = FakeMatrixRoom()
+ val room = FakeMatrixRoom(
+ typingNoticeResult = { Result.success(Unit) }
+ )
val permissionPresenter = FakePermissionsPresenter()
val presenter = createPresenter(
this,
@@ -714,7 +816,9 @@ class MessageComposerPresenterTest {
@Test
fun `present - Record video`() = runTest {
- val room = FakeMatrixRoom()
+ val room = FakeMatrixRoom(
+ typingNoticeResult = { Result.success(Unit) }
+ )
val permissionPresenter = FakePermissionsPresenter().apply { setPermissionGranted() }
val presenter = createPresenter(
this,
@@ -734,7 +838,9 @@ class MessageComposerPresenterTest {
@Test
fun `present - Record video with permission request`() = runTest {
- val room = FakeMatrixRoom()
+ val room = FakeMatrixRoom(
+ typingNoticeResult = { Result.success(Unit) }
+ )
val permissionPresenter = FakePermissionsPresenter()
val presenter = createPresenter(
this,
@@ -759,9 +865,10 @@ class MessageComposerPresenterTest {
@Test
fun `present - Uploading media failure can be recovered from`() = runTest {
- val room = FakeMatrixRoom().apply {
- givenSendMediaResult(Result.failure(Exception()))
- }
+ val room = FakeMatrixRoom(
+ sendMediaResult = { Result.failure(Exception()) },
+ typingNoticeResult = { Result.success(Unit) }
+ )
val presenter = createPresenter(this, room = room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -842,15 +949,17 @@ class MessageComposerPresenterTest {
val invitedUser = aRoomMember(userId = A_USER_ID_3, membership = RoomMembershipState.INVITE)
val bob = aRoomMember(userId = A_USER_ID_2, membership = RoomMembershipState.JOIN)
val david = aRoomMember(userId = A_USER_ID_4, displayName = "Dave", membership = RoomMembershipState.JOIN)
+ var canUserTriggerRoomNotificationResult = true
val room = FakeMatrixRoom(
isDirect = false,
+ canUserTriggerRoomNotificationResult = { Result.success(canUserTriggerRoomNotificationResult) },
+ typingNoticeResult = { Result.success(Unit) }
).apply {
givenRoomMembersState(
MatrixRoomMembersState.Ready(
persistentListOf(currentUser, invitedUser, bob, david),
)
)
- givenCanTriggerRoomNotification(Result.success(true))
}
val flagsService = FakeFeatureFlagService(
mapOf(
@@ -890,13 +999,10 @@ class MessageComposerPresenterTest {
assertThat(awaitItem().memberSuggestions).isEmpty()
// If user has no permission to send `@room` mentions, `RoomMemberSuggestion.Room` is not returned
- room.givenCanTriggerRoomNotification(Result.success(false))
+ canUserTriggerRoomNotificationResult = false
initialState.eventSink(MessageComposerEvents.SuggestionReceived(Suggestion(0, 0, SuggestionType.Mention, "")))
assertThat(awaitItem().memberSuggestions)
.containsExactly(ResolvedMentionSuggestion.Member(bob), ResolvedMentionSuggestion.Member(david))
-
- // If room is a DM, `RoomMemberSuggestion.Room` is not returned
- room.givenCanTriggerRoomNotification(Result.success(true))
}
}
@@ -910,13 +1016,14 @@ class MessageComposerPresenterTest {
isDirect = true,
activeMemberCount = 2,
isEncrypted = true,
+ canUserTriggerRoomNotificationResult = { Result.success(true) },
+ typingNoticeResult = { Result.success(Unit) }
).apply {
givenRoomMembersState(
MatrixRoomMembersState.Ready(
persistentListOf(currentUser, invitedUser, bob, david),
)
)
- givenCanTriggerRoomNotification(Result.success(true))
}
val flagsService = FakeFeatureFlagService(
mapOf(
@@ -973,7 +1080,14 @@ class MessageComposerPresenterTest {
this.replyMessageLambda = replyMessageLambda
this.editMessageLambda = editMessageLambda
}
- val room = FakeMatrixRoom(liveTimeline = timeline)
+ val sendMessageResult = lambdaRecorder { _: String, _: String?, _: List ->
+ Result.success(Unit)
+ }
+ val room = FakeMatrixRoom(
+ liveTimeline = timeline,
+ sendMessageResult = sendMessageResult,
+ typingNoticeResult = { Result.success(Unit) }
+ )
val presenter = createPresenter(room = room, coroutineScope = this)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -993,7 +1107,8 @@ class MessageComposerPresenterTest {
advanceUntilIdle()
- assertThat(room.sendMessageMentions).isEqualTo(listOf(Mention.User(A_USER_ID)))
+ sendMessageResult.assertions().isCalledOnce()
+ .with(value(A_MESSAGE), any(), value(listOf(Mention.User(A_USER_ID))))
// Check intentional mentions on reply sent
initialState.eventSink(MessageComposerEvents.SetMode(aReplyMode()))
@@ -1049,22 +1164,32 @@ class MessageComposerPresenterTest {
@Test
fun `present - handle typing notice event`() = runTest {
- val room = FakeMatrixRoom()
+ val typingNoticeResult = lambdaRecorder> { Result.success(Unit) }
+ val room = FakeMatrixRoom(
+ typingNoticeResult = typingNoticeResult
+ )
val presenter = createPresenter(room = room, coroutineScope = this)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitFirstItem()
- assertThat(room.typingRecord).isEmpty()
+ typingNoticeResult.assertions().isNeverCalled()
initialState.eventSink.invoke(MessageComposerEvents.TypingNotice(true))
initialState.eventSink.invoke(MessageComposerEvents.TypingNotice(false))
- assertThat(room.typingRecord).isEqualTo(listOf(true, false))
+ typingNoticeResult.assertions().isCalledExactly(2)
+ .withSequence(
+ listOf(value(true)),
+ listOf(value(false)),
+ )
}
}
@Test
fun `present - handle typing notice event when sending typing notice is disabled`() = runTest {
- val room = FakeMatrixRoom()
+ val typingNoticeResult = lambdaRecorder> { Result.success(Unit) }
+ val room = FakeMatrixRoom(
+ typingNoticeResult = typingNoticeResult
+ )
val store = InMemorySessionPreferencesStore(
isSendTypingNotificationsEnabled = false
)
@@ -1073,10 +1198,10 @@ class MessageComposerPresenterTest {
presenter.present()
}.test {
val initialState = awaitFirstItem()
- assertThat(room.typingRecord).isEmpty()
+ typingNoticeResult.assertions().isNeverCalled()
initialState.eventSink.invoke(MessageComposerEvents.TypingNotice(true))
initialState.eventSink.invoke(MessageComposerEvents.TypingNotice(false))
- assertThat(room.typingRecord).isEmpty()
+ typingNoticeResult.assertions().isNeverCalled()
}
}
@@ -1215,7 +1340,10 @@ class MessageComposerPresenterTest {
val timeline = FakeTimeline().apply {
this.loadReplyDetailsLambda = loadReplyDetailsLambda
}
- val room = FakeMatrixRoom(liveTimeline = timeline)
+ val room = FakeMatrixRoom(
+ liveTimeline = timeline,
+ typingNoticeResult = { Result.success(Unit) },
+ )
val permalinkBuilder = FakePermalinkBuilder()
val presenter = createPresenter(
room = room,
@@ -1352,7 +1480,9 @@ class MessageComposerPresenterTest {
private fun createPresenter(
coroutineScope: CoroutineScope,
- room: MatrixRoom = FakeMatrixRoom(),
+ room: MatrixRoom = FakeMatrixRoom(
+ typingNoticeResult = { Result.success(Unit) }
+ ),
pickerProvider: PickerProvider = this.pickerProvider,
featureFlagService: FeatureFlagService = this.featureFlagService,
sessionPreferencesStore: SessionPreferencesStore = InMemorySessionPreferencesStore(),
diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineControllerTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineControllerTest.kt
index 58cf11c4a7..887ce88d95 100644
--- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineControllerTest.kt
+++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineControllerTest.kt
@@ -26,6 +26,7 @@ import io.element.android.libraries.matrix.test.A_UNIQUE_ID
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import io.element.android.libraries.matrix.test.timeline.FakeTimeline
import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem
+import io.element.android.tests.testutils.lambda.lambdaError
import io.element.android.tests.testutils.lambda.lambdaRecorder
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOf
@@ -38,9 +39,9 @@ class TimelineControllerTest {
val liveTimeline = FakeTimeline(name = "live")
val detachedTimeline = FakeTimeline(name = "detached")
val matrixRoom = FakeMatrixRoom(
- liveTimeline = liveTimeline
+ liveTimeline = liveTimeline,
+ timelineFocusedOnEventResult = { Result.success(detachedTimeline) }
)
- matrixRoom.givenTimelineFocusedOnEventResult(Result.success(detachedTimeline))
val sut = TimelineController(matrixRoom)
sut.activeTimelineFlow().test {
@@ -68,8 +69,17 @@ class TimelineControllerTest {
val liveTimeline = FakeTimeline(name = "live")
val detachedTimeline1 = FakeTimeline(name = "detached 1")
val detachedTimeline2 = FakeTimeline(name = "detached 2")
+ var callNumber = 0
val matrixRoom = FakeMatrixRoom(
- liveTimeline = liveTimeline
+ liveTimeline = liveTimeline,
+ timelineFocusedOnEventResult = {
+ callNumber++
+ when (callNumber) {
+ 1 -> Result.success(detachedTimeline1)
+ 2 -> Result.success(detachedTimeline2)
+ else -> lambdaError()
+ }
+ }
)
val sut = TimelineController(matrixRoom)
@@ -77,7 +87,6 @@ class TimelineControllerTest {
awaitItem().also { state ->
assertThat(state).isEqualTo(liveTimeline)
}
- matrixRoom.givenTimelineFocusedOnEventResult(Result.success(detachedTimeline1))
sut.focusOnEvent(AN_EVENT_ID)
awaitItem().also { state ->
assertThat(state).isEqualTo(detachedTimeline1)
@@ -85,7 +94,6 @@ class TimelineControllerTest {
assertThat(detachedTimeline1.closeCounter).isEqualTo(0)
assertThat(detachedTimeline2.closeCounter).isEqualTo(0)
// Focus on another event should close the previous detached timeline
- matrixRoom.givenTimelineFocusedOnEventResult(Result.success(detachedTimeline2))
sut.focusOnEvent(AN_EVENT_ID)
awaitItem().also { state ->
assertThat(state).isEqualTo(detachedTimeline2)
@@ -117,11 +125,10 @@ class TimelineControllerTest {
val liveTimeline = FakeTimeline(name = "live")
val detachedTimeline = FakeTimeline(name = "detached")
val matrixRoom = FakeMatrixRoom(
- liveTimeline = liveTimeline
+ liveTimeline = liveTimeline,
+ timelineFocusedOnEventResult = { Result.success(detachedTimeline) }
)
- matrixRoom.givenTimelineFocusedOnEventResult(Result.success(detachedTimeline))
val sut = TimelineController(matrixRoom)
-
sut.activeTimelineFlow().test {
awaitItem().also { state ->
assertThat(state).isEqualTo(liveTimeline)
@@ -168,9 +175,9 @@ class TimelineControllerTest {
sendMessageLambda = lambdaForDetached
}
val matrixRoom = FakeMatrixRoom(
- liveTimeline = liveTimeline
+ liveTimeline = liveTimeline,
+ timelineFocusedOnEventResult = { Result.success(detachedTimeline) }
)
- matrixRoom.givenTimelineFocusedOnEventResult(Result.success(detachedTimeline))
val sut = TimelineController(matrixRoom)
sut.activeTimelineFlow().test {
sut.focusOnEvent(AN_EVENT_ID)
@@ -193,9 +200,9 @@ class TimelineControllerTest {
val liveTimeline = FakeTimeline(name = "live")
val detachedTimeline = FakeTimeline(name = "detached")
val matrixRoom = FakeMatrixRoom(
- liveTimeline = liveTimeline
+ liveTimeline = liveTimeline,
+ timelineFocusedOnEventResult = { Result.success(detachedTimeline) }
)
- matrixRoom.givenTimelineFocusedOnEventResult(Result.success(detachedTimeline))
val sut = TimelineController(matrixRoom)
sut.activeTimelineFlow().test {
diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt
index ad52af11ca..5a171227e7 100644
--- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt
+++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt
@@ -133,7 +133,10 @@ private const val FAKE_UNIQUE_ID_2 = "FAKE_UNIQUE_ID_2"
)
)
)
- val room = FakeMatrixRoom(liveTimeline = timeline)
+ val room = FakeMatrixRoom(
+ liveTimeline = timeline,
+ canUserSendMessageResult = { _, _ -> Result.success(true) },
+ )
val sessionPreferencesStore = InMemorySessionPreferencesStore(isSendPublicReadReceiptsEnabled = false)
val presenter = createTimelinePresenter(
timeline = timeline,
@@ -482,9 +485,9 @@ private const val FAKE_UNIQUE_ID_2 = "FAKE_UNIQUE_ID_2"
)
val room = FakeMatrixRoom(
liveTimeline = liveTimeline,
- ).apply {
- givenTimelineFocusedOnEventResult(Result.success(detachedTimeline))
- }
+ timelineFocusedOnEventResult = { Result.success(detachedTimeline) },
+ canUserSendMessageResult = { _, _ -> Result.success(true) },
+ )
val presenter = createTimelinePresenter(
room = room,
)
@@ -529,6 +532,7 @@ private const val FAKE_UNIQUE_ID_2 = "FAKE_UNIQUE_ID_2"
)
)
),
+ canUserSendMessageResult = { _, _ -> Result.success(true) },
),
timelineItemIndexer = timelineItemIndexer,
)
@@ -551,9 +555,9 @@ private const val FAKE_UNIQUE_ID_2 = "FAKE_UNIQUE_ID_2"
liveTimeline = FakeTimeline(
timelineItems = flowOf(emptyList()),
),
- ).apply {
- givenTimelineFocusedOnEventResult(Result.failure(Throwable("An error")))
- },
+ timelineFocusedOnEventResult = { Result.failure(Throwable("An error")) },
+ canUserSendMessageResult = { _, _ -> Result.success(true) },
+ )
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -594,7 +598,10 @@ private const val FAKE_UNIQUE_ID_2 = "FAKE_UNIQUE_ID_2"
)
)
)
- val room = FakeMatrixRoom(liveTimeline = timeline).apply {
+ val room = FakeMatrixRoom(
+ liveTimeline = timeline,
+ canUserSendMessageResult = { _, _ -> Result.success(true) },
+ ).apply {
givenRoomMembersState(MatrixRoomMembersState.Unknown)
}
@@ -626,7 +633,10 @@ private const val FAKE_UNIQUE_ID_2 = "FAKE_UNIQUE_ID_2"
private fun TestScope.createTimelinePresenter(
timeline: Timeline = FakeTimeline(),
- room: FakeMatrixRoom = FakeMatrixRoom(liveTimeline = timeline),
+ room: FakeMatrixRoom = FakeMatrixRoom(
+ liveTimeline = timeline,
+ canUserSendMessageResult = { _, _ -> Result.success(true) }
+ ),
timelineItemsFactory: TimelineItemsFactory = aTimelineItemsFactory(),
redactedVoiceMessageManager: RedactedVoiceMessageManager = FakeRedactedVoiceMessageManager(),
messagesNavigator: FakeMessagesNavigator = FakeMessagesNavigator(),
diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt
index 59cd3cf383..ffb247ed1a 100644
--- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt
+++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt
@@ -20,9 +20,11 @@ import android.net.Uri
import android.text.SpannableString
import android.text.SpannableStringBuilder
import android.text.Spanned
+import android.text.SpannedString
import android.text.style.URLSpan
import androidx.core.text.buildSpannedString
import androidx.core.text.inSpans
+import androidx.core.text.toSpannable
import com.google.common.truth.Truth.assertThat
import io.element.android.features.location.api.Location
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContent
@@ -74,6 +76,7 @@ import io.element.android.libraries.mediaviewer.api.util.FileExtensionExtractorW
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.test.runTest
+import org.junit.Assert.fail
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
@@ -195,7 +198,7 @@ class TimelineItemContentMessageFactoryTest {
inSpans(URLSpan("https://matrix.org")) {
append("and manually added link")
}
- }
+ }.toSpannable()
val sut = createTimelineItemContentMessageFactory(
htmlConverterTransform = { expected }
)
@@ -610,7 +613,7 @@ class TimelineItemContentMessageFactoryTest {
senderDisambiguatedDisplayName = "Bob",
eventId = AN_EVENT_ID,
)
- assertThat((result as TimelineItemNoticeContent).formattedBody).isEqualTo("formatted")
+ (result as TimelineItemNoticeContent).formattedBody.assertSpannedEquals(SpannedString("formatted"))
}
@Test
@@ -644,7 +647,8 @@ class TimelineItemContentMessageFactoryTest {
senderDisambiguatedDisplayName = "Bob",
eventId = AN_EVENT_ID,
)
- assertThat((result as TimelineItemEmoteContent).formattedBody).isEqualTo(SpannableString("* Bob formatted"))
+
+ (result as TimelineItemEmoteContent).formattedBody.assertSpannedEquals(SpannableString("* Bob formatted"))
}
@Test
@@ -654,7 +658,7 @@ class TimelineItemContentMessageFactoryTest {
inSpans(URLSpan("https://www.example.org")) {
append("me@matrix.org")
}
- }
+ }.toSpannable()
val sut = createTimelineItemContentMessageFactory(
htmlConverterTransform = { expectedSpanned },
permalinkParser = FakePermalinkParser { PermalinkData.FallbackLink(Uri.EMPTY) }
@@ -669,7 +673,59 @@ class TimelineItemContentMessageFactoryTest {
senderDisambiguatedDisplayName = "Bob",
eventId = AN_EVENT_ID,
)
- assertThat((result as TimelineItemTextContent).formattedBody).isEqualTo(expectedSpanned)
+ (result as TimelineItemTextContent).formattedBody.assertSpannedEquals(expectedSpanned)
+ }
+
+ @Test
+ fun `a message with plain URL in a formatted body Spanned format gets linkified too`() = runTest {
+ val expectedSpanned = buildSpannedString {
+ append("Test ")
+ inSpansWithFlags(URLSpan("https://www.example.org"), flags = Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) {
+ append("https://www.example.org")
+ }
+ }
+ val sut = createTimelineItemContentMessageFactory(
+ htmlConverterTransform = { expectedSpanned },
+ permalinkParser = FakePermalinkParser { PermalinkData.FallbackLink(Uri.EMPTY) }
+ )
+ val result = sut.create(
+ content = createMessageContent(
+ type = TextMessageType(
+ body = "Test [me@matrix.org](https://www.example.org)",
+ formatted = FormattedBody(MessageFormat.HTML, "Test https://www.example.org")
+ )
+ ),
+ senderDisambiguatedDisplayName = "Bob",
+ eventId = AN_EVENT_ID,
+ )
+ (result as TimelineItemTextContent).formattedBody.assertSpannedEquals(expectedSpanned)
+ }
+
+ @Test
+ fun `a message with plain URL in a formatted body with plain text format gets linkified too`() = runTest {
+ val resultString = "Test https://www.example.org"
+ val expectedSpanned = buildSpannedString {
+ append("Test ")
+ inSpansWithFlags(URLSpan("https://www.example.org"), flags = Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) {
+ append("https://www.example.org")
+ }
+ }.toSpannable()
+ val sut = createTimelineItemContentMessageFactory(
+ htmlConverterTransform = { resultString },
+ permalinkParser = FakePermalinkParser { PermalinkData.FallbackLink(Uri.EMPTY) }
+ )
+ val result = sut.create(
+ content = createMessageContent(
+ type = TextMessageType(
+ body = "Test [me@matrix.org](https://www.example.org)",
+ formatted = FormattedBody(MessageFormat.HTML, "Test https://www.example.org")
+ )
+ ),
+ senderDisambiguatedDisplayName = "Bob",
+ eventId = AN_EVENT_ID,
+ )
+
+ (result as TimelineItemTextContent).formattedBody.assertSpannedEquals(expectedSpanned)
}
private fun createMessageContent(
@@ -718,3 +774,40 @@ class TimelineItemContentMessageFactoryTest {
fileExtensionExtractor = FileExtensionExtractorWithoutValidation()
)
}
+
+private inline fun SpannableStringBuilder.inSpansWithFlags(span: Any, flags: Int, action: SpannableStringBuilder.() -> Unit) {
+ val start = this.length
+ action()
+ val end = this.length
+ setSpan(span, start, end, flags)
+}
+
+fun CharSequence?.assertSpannedEquals(other: CharSequence?) {
+ if (this == null && other == null) {
+ return
+ } else if (this is Spanned && other is Spanned) {
+ assertThat(this.toString()).isEqualTo(other.toString())
+ assertThat(this.length).isEqualTo(other.length)
+ val thisSpans = this.getSpans(0, this.length, Any::class.java)
+ val otherSpans = other.getSpans(0, other.length, Any::class.java)
+ if (thisSpans.size != otherSpans.size) {
+ fail("Expected ${thisSpans.size} spans, got ${otherSpans.size}")
+ }
+ thisSpans.forEachIndexed { index, span ->
+ val otherSpan = otherSpans[index]
+ // URLSpans don't have a proper `equals` implementation, so we compare the URL instead
+ if (span is URLSpan && otherSpan is URLSpan) {
+ assertThat(span.url).isEqualTo(otherSpan.url)
+ } else {
+ assertThat(span).isEqualTo(otherSpan)
+ }
+ assertThat(this.getSpanStart(span)).isEqualTo(other.getSpanStart(otherSpan))
+ assertThat(this.getSpanEnd(span)).isEqualTo(other.getSpanEnd(otherSpan))
+ assertThat(this.getSpanFlags(span)).isEqualTo(other.getSpanFlags(otherSpan))
+ }
+ } else {
+ val thisString = this?.toString() ?: "null"
+ val otherString = other?.toString() ?: "null"
+ fail("Expected Spanned, got $thisString and $otherString")
+ }
+}
diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPresenterTest.kt
index 1c08a76e3d..0e507bdecf 100644
--- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPresenterTest.kt
+++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPresenterTest.kt
@@ -28,7 +28,9 @@ import com.google.common.truth.Truth.assertThat
import im.vector.app.features.analytics.plan.Composer
import io.element.android.features.messages.impl.voicemessages.VoiceMessageException
import io.element.android.features.messages.test.FakeMessageComposerContext
+import io.element.android.libraries.matrix.api.core.ProgressCallback
import io.element.android.libraries.matrix.test.AN_EVENT_ID
+import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails
import io.element.android.libraries.mediaplayer.test.FakeMediaPlayer
@@ -45,6 +47,7 @@ import io.element.android.libraries.textcomposer.model.VoiceMessageState
import io.element.android.libraries.voicerecorder.test.FakeVoiceRecorder
import io.element.android.services.analytics.test.FakeAnalyticsService
import io.element.android.tests.testutils.WarmUpRule
+import io.element.android.tests.testutils.lambda.lambdaRecorder
import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -63,7 +66,10 @@ class VoiceMessageComposerPresenterTest {
recordingDuration = RECORDING_DURATION
)
private val analyticsService = FakeAnalyticsService()
- private val matrixRoom = FakeMatrixRoom()
+ private val sendMediaResult = lambdaRecorder> { Result.success(FakeMediaUploadHandler()) }
+ private val matrixRoom = FakeMatrixRoom(
+ sendMediaResult = sendMediaResult
+ )
private val mediaPreProcessor = FakeMediaPreProcessor().apply { givenAudioResult() }
private val mediaSender = MediaSender(mediaPreProcessor, matrixRoom)
private val messageComposerContext = FakeMessageComposerContext()
@@ -295,7 +301,7 @@ class VoiceMessageComposerPresenterTest {
val finalState = awaitItem()
assertThat(finalState.voiceMessageState).isEqualTo(VoiceMessageState.Idle)
- assertThat(matrixRoom.sendMediaCount).isEqualTo(1)
+ sendMediaResult.assertions().isCalledOnce()
voiceRecorder.assertCalls(started = 1, stopped = 1, deleted = 1)
testPauseAndDestroy(finalState)
@@ -346,7 +352,7 @@ class VoiceMessageComposerPresenterTest {
val finalState = awaitItem()
assertThat(finalState.voiceMessageState).isEqualTo(VoiceMessageState.Idle)
- assertThat(matrixRoom.sendMediaCount).isEqualTo(1)
+ sendMediaResult.assertions().isCalledOnce()
voiceRecorder.assertCalls(started = 1, stopped = 1, deleted = 1)
testPauseAndDestroy(finalState)
@@ -369,7 +375,7 @@ class VoiceMessageComposerPresenterTest {
val finalState = awaitItem()
assertThat(finalState.voiceMessageState).isEqualTo(VoiceMessageState.Idle)
- assertThat(matrixRoom.sendMediaCount).isEqualTo(1)
+ sendMediaResult.assertions().isCalledOnce()
voiceRecorder.assertCalls(started = 1, stopped = 1, deleted = 1)
testPauseAndDestroy(finalState)
@@ -393,7 +399,7 @@ class VoiceMessageComposerPresenterTest {
val finalState = awaitItem()
assertThat(finalState.voiceMessageState).isEqualTo(aPreviewState(isSending = true))
- assertThat(matrixRoom.sendMediaCount).isEqualTo(0)
+ sendMediaResult.assertions().isNeverCalled()
assertThat(analyticsService.trackedErrors).hasSize(0)
voiceRecorder.assertCalls(started = 1, stopped = 1, deleted = 0)
@@ -418,13 +424,13 @@ class VoiceMessageComposerPresenterTest {
ensureAllEventsConsumed()
assertThat(previewState.voiceMessageState).isEqualTo(aPreviewState())
- assertThat(matrixRoom.sendMediaCount).isEqualTo(0)
+ sendMediaResult.assertions().isNeverCalled()
mediaPreProcessor.givenAudioResult()
previewState.eventSink(VoiceMessageComposerEvents.SendVoiceMessage)
val finalState = awaitItem()
assertThat(finalState.voiceMessageState).isEqualTo(VoiceMessageState.Idle)
- assertThat(matrixRoom.sendMediaCount).isEqualTo(1)
+ sendMediaResult.assertions().isCalledOnce()
voiceRecorder.assertCalls(started = 1, stopped = 1, deleted = 1)
testPauseAndDestroy(finalState)
@@ -461,7 +467,7 @@ class VoiceMessageComposerPresenterTest {
assertThat(showSendFailureDialog).isFalse()
}
- assertThat(matrixRoom.sendMediaCount).isEqualTo(0)
+ sendMediaResult.assertions().isNeverCalled()
testPauseAndDestroy(finalState)
}
}
@@ -477,7 +483,7 @@ class VoiceMessageComposerPresenterTest {
initialState.eventSink(VoiceMessageComposerEvents.SendVoiceMessage)
assertThat(initialState.voiceMessageState).isEqualTo(VoiceMessageState.Idle)
- assertThat(matrixRoom.sendMediaCount).isEqualTo(0)
+ sendMediaResult.assertions().isNeverCalled()
assertThat(analyticsService.trackedErrors).hasSize(1)
voiceRecorder.assertCalls(started = 0)
@@ -496,7 +502,7 @@ class VoiceMessageComposerPresenterTest {
val initialState = awaitItem()
initialState.eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start))
- assertThat(matrixRoom.sendMediaCount).isEqualTo(0)
+ sendMediaResult.assertions().isNeverCalled()
assertThat(analyticsService.trackedErrors).containsExactly(
VoiceMessageException.PermissionMissing(message = "Expected permission to record but none", cause = exception)
)
diff --git a/features/onboarding/impl/src/main/res/values-pl/translations.xml b/features/onboarding/impl/src/main/res/values-pl/translations.xml
new file mode 100644
index 0000000000..5ac4d81c27
--- /dev/null
+++ b/features/onboarding/impl/src/main/res/values-pl/translations.xml
@@ -0,0 +1,9 @@
+
+
+ "Zaloguj się ręcznie"
+ "Zaloguj się za pomocą kodu QR"
+ "Utwórz konto"
+ "Witamy w %1$s. Szybszy i prostszy niż kiedykolwiek."
+ "Witamy w %1$s. Doładowany, dla szybkości i prostoty."
+ "Be in your element"
+
diff --git a/features/onboarding/impl/src/main/res/values-pt-rBR/translations.xml b/features/onboarding/impl/src/main/res/values-pt-rBR/translations.xml
new file mode 100644
index 0000000000..04a26fe212
--- /dev/null
+++ b/features/onboarding/impl/src/main/res/values-pt-rBR/translations.xml
@@ -0,0 +1,8 @@
+
+
+ "Iniciar sessão manualmente"
+ "Iniciar sessão com código QR"
+ "Criar conta"
+ "Bem-vindo ao mais rápido %1$s de todos os tempos. Turbinado para velocidade e simplicidade."
+ "Bem-vindo ao %1$s. Turbinado, para velocidade e simplicidade"
+
diff --git a/features/poll/impl/src/main/res/values-ka/translations.xml b/features/poll/impl/src/main/res/values-ka/translations.xml
index c2417983af..0cdbc950b8 100644
--- a/features/poll/impl/src/main/res/values-ka/translations.xml
+++ b/features/poll/impl/src/main/res/values-ka/translations.xml
@@ -4,8 +4,16 @@
"შედეგების ჩვენება მხოლოდ გამოკითხვის დასრულების შემდეგ"
"ხმების დამალვა"
"ვარიანტი %1$d"
+ "თქვენი ცვლილებები არ არის შენახული. დარწმუნებული ხართ, რომ გსურთ დაბრუნება?"
"კითხვა ან თემა"
"რას ეხება გამოკითხვა?"
"გამოკითხვის შექმნა"
+ "დარწმუნებული ხართ, რომ გსურთ ამ გამოკითხვის წაშლა?"
+ "გამოკითხვის წაშლა"
"გამოკითხვის რედაქტირება"
+ "მიმდინარე გამოკითხვები ვერ მოიძებნა."
+ "ბოლო გამოკითხვების მოძებნა ვერ მოხერხდა."
+ "მიმდინარე"
+ "წარსული"
+ "გამოკითხვები"
diff --git a/features/poll/impl/src/main/res/values-pl/translations.xml b/features/poll/impl/src/main/res/values-pl/translations.xml
new file mode 100644
index 0000000000..c6797b370e
--- /dev/null
+++ b/features/poll/impl/src/main/res/values-pl/translations.xml
@@ -0,0 +1,19 @@
+
+
+ "Dodaj opcję"
+ "Pokaż wyniki dopiero po zakończeniu ankiety"
+ "Ukryj głosy"
+ "Opcja %1$d"
+ "Twoje zmiany nie zostały zapisane. Czy na pewno chcesz wrócić?"
+ "Pytanie lub temat"
+ "Czego dotyczy ankieta?"
+ "Utwórz ankietę"
+ "Czy na pewno chcesz usunąć tę ankietę?"
+ "Usuń ankietę"
+ "Edytuj ankietę"
+ "Nie znaleziono ankiet w trakcie."
+ "Nie znaleziono ankiet."
+ "W trakcie"
+ "Przeszłe"
+ "Ankiety"
+
diff --git a/features/poll/impl/src/main/res/values-pt-rBR/translations.xml b/features/poll/impl/src/main/res/values-pt-rBR/translations.xml
new file mode 100644
index 0000000000..058dab7d42
--- /dev/null
+++ b/features/poll/impl/src/main/res/values-pt-rBR/translations.xml
@@ -0,0 +1,12 @@
+
+
+ "Adicionar opção"
+ "Mostrar resultados somente após o término da enquete"
+ "Ocultar votos"
+ "Opção %1$d"
+ "Pergunta ou tópico"
+ "Sobre o que é a enquete?"
+ "Criar enquete"
+ "Excluir Enquete"
+ "Editar enquete"
+
diff --git a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenterTest.kt b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenterTest.kt
index 89536deaec..334ea7018b 100644
--- a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenterTest.kt
+++ b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenterTest.kt
@@ -35,7 +35,6 @@ import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.timeline.item.event.PollContent
import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
-import io.element.android.libraries.matrix.test.room.SavePollInvocation
import io.element.android.libraries.matrix.test.timeline.FakeTimeline
import io.element.android.libraries.matrix.test.timeline.LiveTimelineProvider
import io.element.android.services.analytics.test.FakeAnalyticsService
@@ -51,9 +50,9 @@ import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.Test
-@OptIn(ExperimentalCoroutinesApi::class) class CreatePollPresenterTest {
- @get:Rule
- val warmUpRule = WarmUpRule()
+@OptIn(ExperimentalCoroutinesApi::class)
+class CreatePollPresenterTest {
+ @get:Rule val warmUpRule = WarmUpRule()
private val pollEventId = AN_EVENT_ID
private var navUpInvocationsCount = 0
@@ -128,7 +127,13 @@ import org.junit.Test
@Test
fun `create poll sends a poll start event`() = runTest {
- val presenter = createCreatePollPresenter(mode = CreatePollMode.NewPoll)
+ val createPollResult = lambdaRecorder, Int, PollKind, Result> { _, _, _, _ -> Result.success(Unit) }
+ val presenter = createCreatePollPresenter(
+ room = FakeMatrixRoom(
+ createPollResult = createPollResult
+ ),
+ mode = CreatePollMode.NewPoll,
+ )
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@@ -139,15 +144,13 @@ import org.junit.Test
skipItems(3)
initial.eventSink(CreatePollEvents.Save)
delay(1) // Wait for the coroutine to finish
- assertThat(fakeMatrixRoom.createPollInvocations.size).isEqualTo(1)
- assertThat(fakeMatrixRoom.createPollInvocations.last()).isEqualTo(
- SavePollInvocation(
- question = "A question?",
- answers = listOf("Answer 1", "Answer 2"),
- maxSelections = 1,
- pollKind = PollKind.Disclosed
+ createPollResult.assertions().isCalledOnce()
+ .with(
+ value("A question?"),
+ value(listOf("Answer 1", "Answer 2")),
+ value(1),
+ value(PollKind.Disclosed),
)
- )
assertThat(fakeAnalyticsService.capturedEvents.size).isEqualTo(2)
assertThat(fakeAnalyticsService.capturedEvents[0]).isEqualTo(
Composer(
@@ -170,8 +173,15 @@ import org.junit.Test
@Test
fun `when poll creation fails, error is tracked`() = runTest {
val error = Exception("cause")
- fakeMatrixRoom.givenCreatePollResult(Result.failure(error))
- val presenter = createCreatePollPresenter(mode = CreatePollMode.NewPoll)
+ val createPollResult = lambdaRecorder, Int, PollKind, Result> { _, _, _, _ ->
+ Result.failure(error)
+ }
+ val presenter = createCreatePollPresenter(
+ room = FakeMatrixRoom(
+ createPollResult = createPollResult
+ ),
+ mode = CreatePollMode.NewPoll,
+ )
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@@ -180,7 +190,7 @@ import org.junit.Test
awaitItem().eventSink(CreatePollEvents.SetAnswer(1, "Answer 2"))
awaitItem().eventSink(CreatePollEvents.Save)
delay(1) // Wait for the coroutine to finish
- assertThat(fakeMatrixRoom.createPollInvocations).hasSize(1)
+ createPollResult.assertions().isCalledOnce()
assertThat(fakeAnalyticsService.capturedEvents).isEmpty()
assertThat(fakeAnalyticsService.trackedErrors).hasSize(1)
assertThat(fakeAnalyticsService.trackedErrors).containsExactly(
@@ -252,14 +262,22 @@ import org.junit.Test
@Test
fun `when edit poll fails, error is tracked`() = runTest {
val error = Exception("cause")
+ val editPollResult = lambdaRecorder { _: EventId, _: String, _: List, _: Int, _: PollKind ->
+ Result.failure(error)
+ }
+ val presenter = createCreatePollPresenter(
+ room = FakeMatrixRoom(
+ editPollResult = editPollResult,
+ liveTimeline = timeline,
+ ),
+ mode = CreatePollMode.EditPoll(pollEventId),
+ )
val editPollLambda = lambdaRecorder { _: EventId, _: String, _: List, _: Int, _: PollKind ->
Result.failure(error)
}
timeline.apply {
this.editPollLambda = editPollLambda
}
- fakeMatrixRoom.givenEditPollResult(Result.failure(error))
- val presenter = createCreatePollPresenter(mode = CreatePollMode.EditPoll(pollEventId))
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
diff --git a/features/preferences/impl/src/main/res/values-el/translations.xml b/features/preferences/impl/src/main/res/values-el/translations.xml
index 32659bca9f..9af59929fc 100644
--- a/features/preferences/impl/src/main/res/values-el/translations.xml
+++ b/features/preferences/impl/src/main/res/values-el/translations.xml
@@ -15,9 +15,11 @@
"Κοινή χρήση παρουσίας"
"Εάν απενεργοποιηθεί, δεν θα μπορείς να στέλνεις ή να λαμβάνεις αποδεικτικά ανάγνωσης ή ειδοποιήσεις πληκτρολόγησης."
"Ενεργοποίησε την επιλογή για προβολή πηγής μηνυμάτων στη ροή."
+ "Δεν έχεις αποκλεισμένους χρήστες"
"Άρση αποκλεισμού"
"Θα μπορείς να δεις ξανά όλα τα μηνύματα του."
"Κατάργηση αποκλεισμού χρήστη"
+ "Άρση αποκλεισμού…"
"Εμφανιζόμενο όνομα"
"Το εμφανιζόμενο όνομά σου"
"Παρουσιάστηκε ένα άγνωστο σφάλμα και οι πληροφορίες δεν μπορούσαν να αλλάξουν."
@@ -45,7 +47,7 @@
"Αναφορές"
"Όλα"
"Αναφορές"
- "Ειδοποιήσε με για"
+ "Ειδοποίησέ με για"
"Ειδοποίηση για @room"
"Για να λαμβάνεις ειδοποιήσεις, άλλαξε το %1$s ."
"ρυθμίσεις συστήματος"
diff --git a/features/preferences/impl/src/main/res/values-it/translations.xml b/features/preferences/impl/src/main/res/values-it/translations.xml
index ea7395492c..5acfeff05b 100644
--- a/features/preferences/impl/src/main/res/values-it/translations.xml
+++ b/features/preferences/impl/src/main/res/values-it/translations.xml
@@ -13,7 +13,7 @@
"Ricevute di visualizzazione"
"Se disattivato, le tue ricevute di visualizzazione non verranno inviate a nessuno. Riceverai comunque ricevute di visualizzazione da altri utenti."
"Condividi presenza online"
- "Se disattivato, non potrai inviare o ricevere ricevute di visualizzazione o notifiche di scrittura."
+ "Se disattivato, non potrai inviare o ricevere ricevute di lettura o notifiche di scrittura."
"Attiva l\'opzione per visualizzare il codice sorgente del messaggio nella conversazione."
"Non hai utenti bloccati"
"Sblocca"
diff --git a/features/preferences/impl/src/main/res/values-ka/translations.xml b/features/preferences/impl/src/main/res/values-ka/translations.xml
index f27eac6d40..89c39c1c44 100644
--- a/features/preferences/impl/src/main/res/values-ka/translations.xml
+++ b/features/preferences/impl/src/main/res/values-ka/translations.xml
@@ -7,6 +7,7 @@
"დააყენეთ საბაზისო URL Element-ის ზარებისათვის."
"არასწორი URL, გთხოვთ, დარწმუნდეთ, რომ შეიტანეთ პროტოკოლი (http/https) და სწორი მისამართი."
"გამორთეთ მდიდარი ტექსტის რედაქტორი, რათა ხელით აკრიფოთ Markdown."
+ "ჩართეთ ოპცია რათა შეტყობინების წყაროს დროის ისტორია ნახოთ."
"განბლოკვა"
"თქვენ კვლავ შეძლებთ მათგან ყველა შეტყობინების ნახვას."
"Მომხმარებლის განბლოკვა"
@@ -32,6 +33,8 @@
"შეტყობინებების ჩართვა ამ მოწყობილობაზე"
"კონფიგურაცია არ გამოსწორებულა, გთხოვთ, კვლავ სცადოთ."
"ჯგუფური ჩატები"
+ "მოსაწვევები"
+ "თქვენი სახლის სერვერი არ უჭერს მხარს ამ პარამეტრს დაშიფრულ ოთახებში, ზოგიერთ ოთახში შეიძლება არ მიიღოთ შეტყობინება."
"ხსენებები"
"ყველა"
"ხსენებები"
diff --git a/features/preferences/impl/src/main/res/values-pl/translations.xml b/features/preferences/impl/src/main/res/values-pl/translations.xml
new file mode 100644
index 0000000000..021d7595f1
--- /dev/null
+++ b/features/preferences/impl/src/main/res/values-pl/translations.xml
@@ -0,0 +1,49 @@
+
+
+ "Upewnij się, że nie pominiesz żadnego połączenia. Zmień swoje ustawienia i zezwól na powiadomienia na blokadzie ekranu."
+ "Popraw jakość swoich rozmów"
+ "Wybierz sposób otrzymywania powiadomień"
+ "Tryb dewelopera"
+ "Włącz, aby uzyskać dostęp do funkcji dla deweloperów."
+ "Własny bazowy URL dla połączeń Element"
+ "Ustaw własny bazowy URL dla połączeń Element"
+ "Nieprawidłowy adres URL, upewnij się, że zawiera protokół (http/https) i poprawny adres."
+ "Wyłącz edytor tekstu bogatego, aby pisać tekst Markdown ręcznie."
+ "Włącz opcję, aby wyświetlić źródło wiadomości na osi czasu."
+ "Odblokuj"
+ "Będziesz mógł ponownie zobaczyć wszystkie wiadomości od tego użytkownika."
+ "Odblokuj użytkownika"
+ "Wyświetlana nazwa"
+ "Twoja wyświetlana nazwa"
+ "Wystąpił nieznany błąd przez co nie można było zmienić informacji."
+ "Nie można zaktualizować profilu"
+ "Edytuj profil"
+ "Aktualizowanie profilu…"
+ "Dodatkowe ustawienia"
+ "Połączenia audio i wideo"
+ "Niezgodność konfiguracji"
+ "Uprościliśmy Ustawienia powiadomień, aby ułatwić nawigowanie między opcjami. Niektóre ustawienia, które wybrałeś mogły zniknąć, lecz są wciąż aktywne.
+
+Niektóre ustawienia mogą ulec zmianie, jeśli kontynuujesz."
+ "Czaty prywatne"
+ "Ustawienia własne wybranego czatu"
+ "Wystąpił błąd podczas aktualizacji ustawienia powiadomień."
+ "Wszystkie wiadomości"
+ "Tylko wzmianki i słowa kluczowe"
+ "Na czatach prywatnych, powiadamiaj mnie przez"
+ "Na czatach grupowych powiadamiaj mnie przez"
+ "Włącz powiadomienia na tym urządzeniu"
+ "Konfiguracja nie została poprawiona, spróbuj ponownie."
+ "Czaty grupowe"
+ "Zaproszenia"
+ "Twój serwer domowy nie wspiera tej opcji w pokojach szyfrowanych, możesz nie otrzymać powiadomień z niektórych pokoi."
+ "Wzmianki"
+ "Wszystkie"
+ "Wzmianki"
+ "Powiadamiaj mnie przez"
+ "Powiadom mnie na @pokój"
+ "Aby otrzymywać powiadomienia, zmień swoje%1$s ."
+ "ustawienia systemowe"
+ "Powiadomienia systemowe wyłączone"
+ "Powiadomienia"
+
diff --git a/features/preferences/impl/src/main/res/values-pt-rBR/translations.xml b/features/preferences/impl/src/main/res/values-pt-rBR/translations.xml
new file mode 100644
index 0000000000..2aa9704981
--- /dev/null
+++ b/features/preferences/impl/src/main/res/values-pt-rBR/translations.xml
@@ -0,0 +1,40 @@
+
+
+ "Escolha como receber notificações"
+ "Modo de desenvolvedor"
+ "Habilite para ter acesso a recursos e funcionalidades para desenvolvedores."
+ "Desative o editor de rich text para digitar Markdown manualmente."
+ "Desbloquear"
+ "Você poderá ver todas as mensagens deles novamente."
+ "Desbloquear usuário"
+ "Nome de exibição"
+ "Seu nome de exibição"
+ "Um erro desconhecido foi encontrado e as informações não puderam ser alteradas."
+ "Não foi possível atualizar o perfil"
+ "Editar perfil"
+ "Atualizando o perfil…"
+ "Configurações adicionais"
+ "Chamadas de áudio e vídeo"
+ "Incompatibilidade de configuração"
+ "Simplificamos as configurações de notificações para facilitar a localização das opções. Algumas configurações personalizadas que você escolheu no passado não são mostradas aqui, mas ainda estão ativas.
+
+Se você continuar, algumas de suas configurações poderão mudar."
+ "Conversas privadas"
+ "Configuração personalizada por chat"
+ "Ocorreu um erro ao atualizar a configuração de notificação."
+ "Todas as mensagens"
+ "Somente menções e palavras-chave"
+ "Em conversas privadas, me notifique para"
+ "Em conversas em grupos, me notifique para"
+ "Ativar notificações neste dispositivo"
+ "A configuração não foi corrigida, tente novamente."
+ "Bate-papos em grupo"
+ "Menções"
+ "Todos"
+ "Menções"
+ "Me notifique para"
+ "Notifique-me em @room"
+ "configurações do sistema"
+ "Notificações do sistema desativadas"
+ "Notificações"
+
diff --git a/features/rageshake/api/src/main/res/values-pl/translations.xml b/features/rageshake/api/src/main/res/values-pl/translations.xml
new file mode 100644
index 0000000000..d9b42eb749
--- /dev/null
+++ b/features/rageshake/api/src/main/res/values-pl/translations.xml
@@ -0,0 +1,7 @@
+
+
+ "%1$s uległ awarii podczas ostatniego użycia. Czy chcesz przesłać nam raport o awarii?"
+ "Wygląda na to, że potrząsasz telefonem z frustracji. Czy chcesz otworzyć ekran zgłaszania błędów?"
+ "Gniewne wstrząsanie"
+ "Próg wykrywania"
+
diff --git a/features/rageshake/api/src/main/res/values-pt-rBR/translations.xml b/features/rageshake/api/src/main/res/values-pt-rBR/translations.xml
new file mode 100644
index 0000000000..3985149a27
--- /dev/null
+++ b/features/rageshake/api/src/main/res/values-pt-rBR/translations.xml
@@ -0,0 +1,7 @@
+
+
+ "%1$s fechou inesperadamente na última vez que foi usado. Gostaria de compartilhar um relatório de falhas conosco?"
+ "Você parece estar sacudindo o telefone em sinal de frustração. Você gostaria de abrir a tela de relatório de erros?"
+ "Rageshake"
+ "Limiar de deteção"
+
diff --git a/features/rageshake/impl/src/main/res/values-pl/translations.xml b/features/rageshake/impl/src/main/res/values-pl/translations.xml
new file mode 100644
index 0000000000..04bf123498
--- /dev/null
+++ b/features/rageshake/impl/src/main/res/values-pl/translations.xml
@@ -0,0 +1,16 @@
+
+
+ "Dołącz zrzut ekranu"
+ "Możecie skontaktować się ze mną, jeśli macie jakiekolwiek dodatkowe pytania."
+ "Napisz do mnie"
+ "Edytuj zrzut ekranu"
+ "Opisz problem. Co zrobiłeś? Czego oczekiwałeś? Co się stało zamiast tego. Podaj jak najwięcej szczegółów."
+ "Opisz problem…"
+ "Jeśli to możliwe, napisz zgłoszenje w języku angielskim."
+ "Wyślij logi awarii"
+ "Zezwól na logi"
+ "Wyślij zrzut ekranu"
+ "Logi zostaną dołączone do Twojej wiadomości, aby upewnić się, że wszystko działa poprawnie. Aby wysłać wiadomość bez logów, wyłącz to ustawienie."
+ "%1$s uległ awarii podczas ostatniego użycia. Czy chcesz przesłać nam raport o awarii?"
+ "Wyświetl logi"
+
diff --git a/features/rageshake/impl/src/main/res/values-pt-rBR/translations.xml b/features/rageshake/impl/src/main/res/values-pt-rBR/translations.xml
new file mode 100644
index 0000000000..d046391f6a
--- /dev/null
+++ b/features/rageshake/impl/src/main/res/values-pt-rBR/translations.xml
@@ -0,0 +1,15 @@
+
+
+ "Anexar captura de tela"
+ "Você pode entrar em contato comigo se tiver alguma pergunta adicional."
+ "Entre em contato comigo"
+ "Editar captura de tela"
+ "Descreva o problema. O que você fez? O que você esperava que acontecesse? O que realmente aconteceu? Por favor, forneça o máximo de detalhes possível."
+ "Descreva o problema…"
+ "Se possível, escreva a descrição em inglês."
+ "Enviar registros de falhas"
+ "Permitir registros"
+ "Enviar captura de tela"
+ "Os registros serão incluídos com sua mensagem para garantir que tudo esteja funcionando corretamente. Para enviar sua mensagem sem registros, desative essa configuração."
+ "%1$s fechou inesperadamente na última vez que foi usado. Gostaria de compartilhar um relatório de falhas conosco?"
+
diff --git a/features/roomdetails/impl/src/main/res/values-el/translations.xml b/features/roomdetails/impl/src/main/res/values-el/translations.xml
index 9262a346c5..2c1886ce40 100644
--- a/features/roomdetails/impl/src/main/res/values-el/translations.xml
+++ b/features/roomdetails/impl/src/main/res/values-el/translations.xml
@@ -4,6 +4,7 @@
"Ο οικιακός διακομιστής σου δεν υποστηρίζει αυτήν την επιλογή σε κρυπτογραφημένα δωμάτια, ενδέχεται να μην λάβεις ειδοποίηση σε ορισμένα δωμάτια."
"Δημοσκοπήσεις"
"Μόνο διαχειριστές"
+ "Αποκλεισμός ατόμων"
"Αφαίρεση μηνυμάτων"
"Όλοι"
"Πρόσκληση ατόμων"
@@ -58,17 +59,23 @@
"Πληροφορίες δωματίου"
"Θέμα"
"Ενημέρωση δωματίου…"
+ "Αποκλεισμός"
"Δεν θα μπορεί να συμμετέχει ξανά σε αυτό το δωμάτιο εάν προσκληθεί."
+ "Θες σίγουρα να αποκλείσεις αυτό το μέλος;"
+ "Δεν υπάρχουν αποκλεισμένοι χρήστες σε αυτό το δωμάτιο."
+ "Αποκλεισμός του χρήστη %1$s"
- "%1$d άτομο"
- "%1$d άτομα"
+ "Αφαίρεση και αποκλεισμός μέλους"
"Αφαίρεση από το δωμάτιο"
"Αφαίρεση και αποκλεισμός μέλους"
"Μόνο αφαίρεση μέλους"
"Αφαίρεση μέλους και απαγόρευση συμμετοχής στο μέλλον;"
"Αναίρεση αποκλεισμού"
"Θα μπορεί να συμμετάσχει ξανά στο δωμάτιο εάν προσκληθεί."
+ "Άρση αποκλεισμού χρήστη"
"Προβολή προφίλ"
"Αποκλεισμένοι"
"Μέλη"
@@ -77,6 +84,7 @@
"Διαχειριστής"
"Συντονιστής"
"Μέλη δωματίου"
+ "Άρση αποκλεισμού %1$s"
"Να επιτρέπεται η προσαρμοσμένη ρύθμιση"
"Η ενεργοποίηση αυτής της ρύθμισης θα παρακάμψει την προεπιλεγμένη ρύθμιση"
"Ειδοποιήσε με σε αυτήν τη συνομιλία για"
diff --git a/features/roomdetails/impl/src/main/res/values-ka/translations.xml b/features/roomdetails/impl/src/main/res/values-ka/translations.xml
index 75f7121b45..65ce26797e 100644
--- a/features/roomdetails/impl/src/main/res/values-ka/translations.xml
+++ b/features/roomdetails/impl/src/main/res/values-ka/translations.xml
@@ -1,6 +1,8 @@
"შეტყობინებების პარამეტრის განახლებისას მოხდა შეცდომა."
+ "თქვენი სახლის სერვერი არ უჭერს მხარს ამ პარამეტრს დაშიფრულ ოთახებში, ზოგიერთ ოთახში შეიძლება არ მიიღოთ შეტყობინება."
+ "გამოკითხვები"
"ყველა"
"თემის დამატება"
"უკვე წევრია"
@@ -39,6 +41,7 @@
"შეტყობინებების პარამეტრების ჩატვირთვისას მოხდა შეცდომა."
"ნაგულისხმევი რეჟიმის აღდგენა ვერ მოხერხდა, გთხოვთ, სცადოთ ხელახლა."
"რეჟიმის დაყენება ვერ მოხერხდა, გთხოვთ, სცადოთ ხელახლა."
+ "თქვენი სახლის სერვერი არ უჭერს მხარს ამ პარამეტრს დაშიფრულ ოთახებში, თქვენ არ მიიღებთ შეტყობინებას ამ ოთახში."
"ყველა შეტყობინება"
"მხოლოდ ხსენებები და საკვანძო სიტყვები"
"ამ ოთახში, შემატყობინეთ:"
diff --git a/features/roomdetails/impl/src/main/res/values-pl/translations.xml b/features/roomdetails/impl/src/main/res/values-pl/translations.xml
new file mode 100644
index 0000000000..569e167d92
--- /dev/null
+++ b/features/roomdetails/impl/src/main/res/values-pl/translations.xml
@@ -0,0 +1,50 @@
+
+
+ "Wystąpił błąd podczas aktualizacji ustawienia powiadomień."
+ "Twój serwer domowy nie wspiera tej opcji w pokojach szyfrowanych, możesz nie otrzymać powiadomień z niektórych pokoi."
+ "Ankiety"
+ "Wszyscy"
+ "Dodaj temat"
+ "Jest już członkiem"
+ "Już zaproszony"
+ "Edytuj pokój"
+ "Wystąpił nieznany błąd i nie można było zmienić informacji."
+ "Nie można zaktualizować pokoju"
+ "Wiadomości są zabezpieczone kłódkami. Tylko Ty i odbiorcy macie unikalne klucze do ich odblokowania."
+ "Szyfrowanie wiadomości włączone"
+ "Wystąpił błąd podczas ładowania ustawień powiadomień."
+ "Wyciszenie tego pokoju nie powiodło się, spróbuj ponownie."
+ "Nie udało się wyłączyć wyciszenia tego pokoju. Spróbuj ponownie."
+ "Zaproś znajomych"
+ "Opuść rozmowę"
+ "Opuść pokój"
+ "Niestandardowy"
+ "Domyślny"
+ "Powiadomienia"
+ "Nazwa pokoju"
+ "Bezpieczeństwo"
+ "Udostępnij pokój"
+ "Temat"
+ "Aktualizuję pokój…"
+
+ - "%1$d osoba"
+ - "%1$d osoby"
+ - "%1$d osób"
+
+ "Oczekiwanie"
+ "Członkowie pokoju"
+ "Zezwalaj na ustawienia niestandardowe"
+ "Włączenie tej opcji nadpisze ustawienie domyślne"
+ "Powiadamiaj mnie o tym czacie przez"
+ "Możesz to zmienić w swoim %1$s."
+ "ustawienia globalne"
+ "Ustawienie domyślne"
+ "Usuń ustawienia własne"
+ "Wystąpił błąd podczas ładowania ustawień powiadomień."
+ "Nie udało się przywrócić trybu domyślnego, spróbuj ponownie."
+ "Nie udało się ustawić trybu, spróbuj ponownie."
+ "Twój serwer domowy nie wspiera tej opcji w pokojach szyfrowanych, możesz nie otrzymać powiadomień z tego pokoju."
+ "Wszystkie wiadomości"
+ "Tylko wzmianki i słowa kluczowe"
+ "W tym pokoju, powiadamiaj mnie przez"
+
diff --git a/features/roomdetails/impl/src/main/res/values-pt-rBR/translations.xml b/features/roomdetails/impl/src/main/res/values-pt-rBR/translations.xml
new file mode 100644
index 0000000000..4989168201
--- /dev/null
+++ b/features/roomdetails/impl/src/main/res/values-pt-rBR/translations.xml
@@ -0,0 +1,46 @@
+
+
+ "Ocorreu um erro ao atualizar a configuração de notificação."
+ "Todos"
+ "Adicionar tópico"
+ "Já é membro"
+ "Já foi convidado"
+ "Editar sala"
+ "Ocorreu um erro desconhecido e as informações não puderam ser alteradas."
+ "Não foi possível atualizar a sala"
+ "As mensagens são protegidas com bloqueios. Somente você e os destinatários têm as chaves exclusivas para desbloqueá-los."
+ "Criptografia de mensagens ativada"
+ "Ocorreu um erro ao carregar as configurações de notificação."
+ "Falha ao silenciar esta sala, tente novamente."
+ "Falha ao ativar o som desta sala. Tente novamente."
+ "Convidar pessoas"
+ "Sair da conversa"
+ "Sair da sala"
+ "Personalizado"
+ "Padrão"
+ "Notificações"
+ "Nome da sala"
+ "Segurança"
+ "Compartilhar sala"
+ "Tópico"
+ "Atualizando a sala…"
+
+ - "%1$d pessoa"
+ - "%1$d pessoas"
+
+ "Pendente"
+ "Membros da sala"
+ "Permitir configuração personalizada"
+ "Ativar isso substituirá sua configuração padrão"
+ "Me notifique nesta conversa para"
+ "Você pode alterá-lo no seu %1$s."
+ "configurações globais"
+ "Configuração padrão"
+ "Remover configuração personalizada"
+ "Ocorreu um erro ao carregar as configurações de notificação."
+ "Falha ao restaurar o modo padrão, tente novamente."
+ "Falha ao definir o modo, tente novamente."
+ "Todas as mensagens"
+ "Somente menções e palavras-chave"
+ "Nesta sala, notifique-me para"
+
diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/MatrixRoomFixture.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/MatrixRoomFixture.kt
index cffd2ae1f1..9fd89f482d 100644
--- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/MatrixRoomFixture.kt
+++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/MatrixRoomFixture.kt
@@ -17,11 +17,15 @@
package io.element.android.features.roomdetails
import io.element.android.libraries.matrix.api.core.RoomId
+import io.element.android.libraries.matrix.api.core.UserId
+import io.element.android.libraries.matrix.api.room.RoomMember
+import io.element.android.libraries.matrix.api.room.StateEventType
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_ROOM_NAME
import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import io.element.android.libraries.matrix.test.room.aRoomInfo
+import io.element.android.tests.testutils.lambda.lambdaError
fun aMatrixRoom(
roomId: RoomId = A_ROOM_ID,
@@ -34,6 +38,16 @@ fun aMatrixRoom(
isDirect: Boolean = false,
notificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService(),
emitRoomInfo: Boolean = false,
+ canInviteResult: (UserId) -> Result = { lambdaError() },
+ canSendStateResult: (UserId, StateEventType) -> Result = { _, _ -> lambdaError() },
+ userDisplayNameResult: () -> Result = { lambdaError() },
+ userAvatarUrlResult: () -> Result = { lambdaError() },
+ setNameResult: (String) -> Result = { lambdaError() },
+ setTopicResult: (String) -> Result = { lambdaError() },
+ updateAvatarResult: (String, ByteArray) -> Result = { _, _ -> lambdaError() },
+ removeAvatarResult: () -> Result = { lambdaError() },
+ canUserJoinCallResult: (UserId) -> Result = { lambdaError() },
+ getUpdatedMemberResult: (UserId) -> Result = { lambdaError() },
) = FakeMatrixRoom(
roomId = roomId,
displayName = displayName,
@@ -42,7 +56,17 @@ fun aMatrixRoom(
isEncrypted = isEncrypted,
isPublic = isPublic,
isDirect = isDirect,
- notificationSettingsService = notificationSettingsService
+ notificationSettingsService = notificationSettingsService,
+ canInviteResult = canInviteResult,
+ canSendStateResult = canSendStateResult,
+ userDisplayNameResult = userDisplayNameResult,
+ userAvatarUrlResult = userAvatarUrlResult,
+ setNameResult = setNameResult,
+ setTopicResult = setTopicResult,
+ updateAvatarResult = updateAvatarResult,
+ removeAvatarResult = removeAvatarResult,
+ canUserJoinCallResult = canUserJoinCallResult,
+ getUpdatedMemberResult = getUpdatedMemberResult,
).apply {
if (emitRoomInfo) {
givenRoomInfo(
diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTest.kt
index 92ac27454a..ff89aba9be 100644
--- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTest.kt
+++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTest.kt
@@ -53,6 +53,9 @@ import io.element.android.services.analytics.test.FakeAnalyticsService
import io.element.android.tests.testutils.FakeLifecycleOwner
import io.element.android.tests.testutils.WarmUpRule
import io.element.android.tests.testutils.consumeItemsUntilPredicate
+import io.element.android.tests.testutils.lambda.lambdaError
+import io.element.android.tests.testutils.lambda.lambdaRecorder
+import io.element.android.tests.testutils.lambda.value
import io.element.android.tests.testutils.testCoroutineDispatchers
import io.element.android.tests.testutils.withFakeLifecycleOwner
import kotlinx.collections.immutable.persistentListOf
@@ -110,7 +113,11 @@ class RoomDetailsPresenterTest {
@Test
fun `present - initial state is created from room if roomInfo is null`() = runTest {
- val room = aMatrixRoom()
+ val room = aMatrixRoom(
+ canInviteResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ canSendStateResult = { _, _ -> Result.success(true) },
+ )
val presenter = createRoomDetailsPresenter(room)
presenter.test {
val initialState = awaitItem()
@@ -128,7 +135,11 @@ class RoomDetailsPresenterTest {
@Test
fun `present - initial state is updated with roomInfo if it exists`() = runTest {
val roomInfo = aRoomInfo(name = "A room name", topic = "A topic", avatarUrl = "https://matrix.org/avatar.jpg")
- val room = aMatrixRoom().apply {
+ val room = aMatrixRoom(
+ canInviteResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ canSendStateResult = { _, _ -> Result.success(true) },
+ ).apply {
givenRoomInfo(roomInfo)
}
val presenter = createRoomDetailsPresenter(room)
@@ -145,7 +156,12 @@ class RoomDetailsPresenterTest {
@Test
fun `present - initial state with no room name`() = runTest {
- val room = aMatrixRoom(displayName = "")
+ val room = aMatrixRoom(
+ displayName = "",
+ canInviteResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ canSendStateResult = { _, _ -> Result.success(true) },
+ )
val presenter = createRoomDetailsPresenter(room)
presenter.test {
val initialState = awaitItem()
@@ -162,6 +178,16 @@ class RoomDetailsPresenterTest {
val room = aMatrixRoom(
isEncrypted = true,
isDirect = true,
+ canInviteResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ canSendStateResult = { _, _ -> Result.success(true) },
+ getUpdatedMemberResult = { userId ->
+ when (userId) {
+ A_SESSION_ID -> Result.success(myRoomMember)
+ A_USER_ID_2 -> Result.success(otherRoomMember)
+ else -> lambdaError()
+ }
+ },
).apply {
val roomMembers = persistentListOf(myRoomMember, otherRoomMember)
givenRoomMembersState(MatrixRoomMembersState.Ready(roomMembers))
@@ -181,9 +207,11 @@ class RoomDetailsPresenterTest {
@Test
fun `present - initial state when user can invite others to room`() = runTest {
- val room = aMatrixRoom().apply {
- givenCanInviteResult(Result.success(true))
- }
+ val room = aMatrixRoom(
+ canInviteResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ canSendStateResult = { _, _ -> Result.success(true) },
+ )
val presenter = createRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers())
presenter.test {
// Initially false
@@ -197,9 +225,11 @@ class RoomDetailsPresenterTest {
@Test
fun `present - initial state when user can not invite others to room`() = runTest {
- val room = aMatrixRoom().apply {
- givenCanInviteResult(Result.success(false))
- }
+ val room = aMatrixRoom(
+ canInviteResult = { Result.success(false) },
+ canUserJoinCallResult = { Result.success(true) },
+ canSendStateResult = { _, _ -> Result.success(true) },
+ )
val presenter = createRoomDetailsPresenter(room)
presenter.test {
assertThat(awaitItem().canInvite).isFalse()
@@ -210,9 +240,11 @@ class RoomDetailsPresenterTest {
@Test
fun `present - initial state when canInvite errors`() = runTest {
- val room = aMatrixRoom().apply {
- givenCanInviteResult(Result.failure(Throwable("Whoops")))
- }
+ val room = aMatrixRoom(
+ canInviteResult = { Result.failure(Throwable("Whoops")) },
+ canUserJoinCallResult = { Result.success(true) },
+ canSendStateResult = { _, _ -> Result.success(true) },
+ )
val presenter = createRoomDetailsPresenter(room)
presenter.test {
assertThat(awaitItem().canInvite).isFalse()
@@ -223,12 +255,18 @@ class RoomDetailsPresenterTest {
@Test
fun `present - initial state when user can edit one attribute`() = runTest {
- val room = aMatrixRoom().apply {
- givenCanSendStateResult(StateEventType.ROOM_TOPIC, Result.success(true))
- givenCanSendStateResult(StateEventType.ROOM_NAME, Result.success(false))
- givenCanSendStateResult(StateEventType.ROOM_AVATAR, Result.failure(Throwable("Whelp")))
- givenCanInviteResult(Result.success(false))
- }
+ val room = aMatrixRoom(
+ canSendStateResult = { _, stateEventType ->
+ when (stateEventType) {
+ StateEventType.ROOM_TOPIC -> Result.success(true)
+ StateEventType.ROOM_NAME -> Result.success(false)
+ StateEventType.ROOM_AVATAR -> Result.failure(Throwable("Whelp"))
+ else -> lambdaError()
+ }
+ },
+ canInviteResult = { Result.success(false) },
+ canUserJoinCallResult = { Result.success(true) },
+ )
val presenter = createRoomDetailsPresenter(room)
presenter.test {
// Initially false
@@ -247,14 +285,26 @@ class RoomDetailsPresenterTest {
val room = aMatrixRoom(
isEncrypted = true,
isDirect = true,
+ canSendStateResult = { _, stateEventType ->
+ when (stateEventType) {
+ StateEventType.ROOM_TOPIC -> Result.success(true)
+ StateEventType.ROOM_NAME -> Result.success(true)
+ StateEventType.ROOM_AVATAR -> Result.success(true)
+ else -> lambdaError()
+ }
+ },
+ canInviteResult = { Result.success(false) },
+ canUserJoinCallResult = { Result.success(true) },
+ getUpdatedMemberResult = { userId ->
+ when (userId) {
+ A_SESSION_ID -> Result.success(myRoomMember)
+ A_USER_ID_2 -> Result.success(otherRoomMember)
+ else -> lambdaError()
+ }
+ },
).apply {
val roomMembers = persistentListOf(myRoomMember, otherRoomMember)
givenRoomMembersState(MatrixRoomMembersState.Ready(roomMembers))
-
- givenCanSendStateResult(StateEventType.ROOM_TOPIC, Result.success(true))
- givenCanSendStateResult(StateEventType.ROOM_NAME, Result.success(true))
- givenCanSendStateResult(StateEventType.ROOM_AVATAR, Result.success(true))
- givenCanInviteResult(Result.success(false))
}
val presenter = createRoomDetailsPresenter(room)
presenter.test {
@@ -278,12 +328,28 @@ class RoomDetailsPresenterTest {
isEncrypted = true,
isDirect = true,
topic = null,
+ canSendStateResult = { _, stateEventType ->
+ when (stateEventType) {
+ StateEventType.ROOM_AVATAR,
+ StateEventType.ROOM_TOPIC,
+ StateEventType.ROOM_NAME -> Result.success(true)
+ else -> lambdaError()
+ }
+ },
+ canInviteResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ getUpdatedMemberResult = { userId ->
+ when (userId) {
+ A_SESSION_ID -> Result.success(myRoomMember)
+ A_USER_ID_2 -> Result.success(otherRoomMember)
+ else -> lambdaError()
+ }
+ },
).apply {
val roomMembers = persistentListOf(myRoomMember, otherRoomMember)
givenRoomMembersState(MatrixRoomMembersState.Ready(roomMembers))
-
- givenCanSendStateResult(StateEventType.ROOM_TOPIC, Result.success(true))
}
+
val presenter = createRoomDetailsPresenter(room)
presenter.test {
skipItems(1)
@@ -297,12 +363,20 @@ class RoomDetailsPresenterTest {
@Test
fun `present - initial state when user can edit all attributes`() = runTest {
- val room = aMatrixRoom().apply {
- givenCanSendStateResult(StateEventType.ROOM_TOPIC, Result.success(true))
- givenCanSendStateResult(StateEventType.ROOM_NAME, Result.success(true))
- givenCanSendStateResult(StateEventType.ROOM_AVATAR, Result.success(true))
- givenCanInviteResult(Result.success(false))
- }
+ val room = aMatrixRoom(
+ canSendStateResult = { _, stateEventType ->
+ when (stateEventType) {
+ StateEventType.ROOM_TOPIC -> Result.success(true)
+ StateEventType.ROOM_NAME -> Result.success(true)
+ StateEventType.ROOM_AVATAR -> Result.success(true)
+ else -> lambdaError()
+ }
+ },
+ canInviteResult = {
+ Result.success(false)
+ },
+ canUserJoinCallResult = { Result.success(true) },
+ )
val presenter = createRoomDetailsPresenter(room)
presenter.test {
// Initially false
@@ -316,12 +390,20 @@ class RoomDetailsPresenterTest {
@Test
fun `present - initial state when user can edit no attributes`() = runTest {
- val room = aMatrixRoom().apply {
- givenCanSendStateResult(StateEventType.ROOM_TOPIC, Result.success(false))
- givenCanSendStateResult(StateEventType.ROOM_NAME, Result.success(false))
- givenCanSendStateResult(StateEventType.ROOM_AVATAR, Result.success(false))
- givenCanInviteResult(Result.success(false))
- }
+ val room = aMatrixRoom(
+ canSendStateResult = { _, stateEventType ->
+ when (stateEventType) {
+ StateEventType.ROOM_TOPIC -> Result.success(false)
+ StateEventType.ROOM_NAME -> Result.success(false)
+ StateEventType.ROOM_AVATAR -> Result.success(false)
+ else -> lambdaError()
+ }
+ },
+ canInviteResult = {
+ Result.success(false)
+ },
+ canUserJoinCallResult = { Result.success(true) },
+ )
val presenter = createRoomDetailsPresenter(room)
presenter.test {
// Initially false, and no further events
@@ -333,11 +415,21 @@ class RoomDetailsPresenterTest {
@Test
fun `present - topic state is hidden when no topic and user has no permission`() = runTest {
- val room = aMatrixRoom(topic = null).apply {
- givenCanSendStateResult(StateEventType.ROOM_TOPIC, Result.success(false))
- givenCanInviteResult(Result.success(false))
- }
-
+ val room = aMatrixRoom(
+ topic = null,
+ canSendStateResult = { _, stateEventType ->
+ when (stateEventType) {
+ StateEventType.ROOM_AVATAR,
+ StateEventType.ROOM_NAME -> Result.success(true)
+ StateEventType.ROOM_TOPIC -> Result.success(false)
+ else -> lambdaError()
+ }
+ },
+ canInviteResult = {
+ Result.success(false)
+ },
+ canUserJoinCallResult = { Result.success(true) },
+ )
val presenter = createRoomDetailsPresenter(room)
presenter.test {
// The initial state is "hidden" and no further state changes happen
@@ -349,12 +441,23 @@ class RoomDetailsPresenterTest {
@Test
fun `present - topic state is 'can add topic' when no topic and user has permission`() = runTest {
- val room = aMatrixRoom(topic = null).apply {
- givenCanSendStateResult(StateEventType.ROOM_TOPIC, Result.success(true))
- givenCanInviteResult(Result.success(false))
+ val room = aMatrixRoom(
+ topic = null,
+ canSendStateResult = { _, stateEventType ->
+ when (stateEventType) {
+ StateEventType.ROOM_AVATAR,
+ StateEventType.ROOM_TOPIC,
+ StateEventType.ROOM_NAME -> Result.success(true)
+ else -> lambdaError()
+ }
+ },
+ canInviteResult = {
+ Result.success(false)
+ },
+ canUserJoinCallResult = { Result.success(true) },
+ ).apply {
givenRoomInfo(aRoomInfo(topic = null))
}
-
val presenter = createRoomDetailsPresenter(room)
presenter.test {
// Ignore the initial state
@@ -370,7 +473,11 @@ class RoomDetailsPresenterTest {
@Test
fun `present - leave room event is passed on to leave room presenter`() = runTest {
val leaveRoomPresenter = FakeLeaveRoomPresenter()
- val room = aMatrixRoom()
+ val room = aMatrixRoom(
+ canInviteResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ canSendStateResult = { _, _ -> Result.success(true) },
+ )
val presenter = createRoomDetailsPresenter(
room = room,
leaveRoomPresenter = leaveRoomPresenter,
@@ -379,7 +486,11 @@ class RoomDetailsPresenterTest {
presenter.test {
awaitItem().eventSink(RoomDetailsEvent.LeaveRoom)
- assertThat(leaveRoomPresenter.events).contains(LeaveRoomEvent.ShowConfirmation(room.roomId))
+ assertThat(leaveRoomPresenter.events).contains(
+ LeaveRoomEvent.ShowConfirmation(
+ room.roomId
+ )
+ )
cancelAndIgnoreRemainingEvents()
}
@@ -389,33 +500,54 @@ class RoomDetailsPresenterTest {
fun `present - notification mode changes`() = runTest {
val leaveRoomPresenter = FakeLeaveRoomPresenter()
val notificationSettingsService = FakeNotificationSettingsService()
- val room = aMatrixRoom(notificationSettingsService = notificationSettingsService)
+ val room = aMatrixRoom(
+ notificationSettingsService = notificationSettingsService,
+ canInviteResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ canSendStateResult = { _, _ -> Result.success(true) },
+ )
val presenter = createRoomDetailsPresenter(
room = room,
leaveRoomPresenter = leaveRoomPresenter,
notificationSettingsService = notificationSettingsService,
)
presenter.test {
- notificationSettingsService.setRoomNotificationMode(room.roomId, RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY)
+ notificationSettingsService.setRoomNotificationMode(
+ room.roomId,
+ RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY
+ )
val updatedState = consumeItemsUntilPredicate {
it.roomNotificationSettings?.mode == RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY
}.last()
- assertThat(updatedState.roomNotificationSettings?.mode).isEqualTo(RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY)
+ assertThat(updatedState.roomNotificationSettings?.mode).isEqualTo(
+ RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY
+ )
cancelAndIgnoreRemainingEvents()
}
}
@Test
fun `present - mute room notifications`() = runTest {
- val notificationSettingsService = FakeNotificationSettingsService(initialRoomMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY)
- val room = aMatrixRoom(notificationSettingsService = notificationSettingsService)
- val presenter = createRoomDetailsPresenter(room = room, notificationSettingsService = notificationSettingsService)
+ val notificationSettingsService =
+ FakeNotificationSettingsService(initialRoomMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY)
+ val room = aMatrixRoom(
+ notificationSettingsService = notificationSettingsService,
+ canInviteResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ canSendStateResult = { _, _ -> Result.success(true) },
+ )
+ val presenter = createRoomDetailsPresenter(
+ room = room,
+ notificationSettingsService = notificationSettingsService
+ )
presenter.test {
awaitItem().eventSink(RoomDetailsEvent.MuteNotification)
val updatedState = consumeItemsUntilPredicate(timeout = 250.milliseconds) {
it.roomNotificationSettings?.mode == RoomNotificationMode.MUTE
}.last()
- assertThat(updatedState.roomNotificationSettings?.mode).isEqualTo(RoomNotificationMode.MUTE)
+ assertThat(updatedState.roomNotificationSettings?.mode).isEqualTo(
+ RoomNotificationMode.MUTE
+ )
cancelAndIgnoreRemainingEvents()
}
}
@@ -426,29 +558,50 @@ class RoomDetailsPresenterTest {
initialRoomMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY,
initialEncryptedGroupDefaultMode = RoomNotificationMode.ALL_MESSAGES
)
- val room = aMatrixRoom(notificationSettingsService = notificationSettingsService)
- val presenter = createRoomDetailsPresenter(room = room, notificationSettingsService = notificationSettingsService)
+ val room = aMatrixRoom(
+ notificationSettingsService = notificationSettingsService,
+ canInviteResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ canSendStateResult = { _, _ -> Result.success(true) },
+ )
+ val presenter = createRoomDetailsPresenter(
+ room = room,
+ notificationSettingsService = notificationSettingsService
+ )
presenter.test {
awaitItem().eventSink(RoomDetailsEvent.UnmuteNotification)
val updatedState = consumeItemsUntilPredicate {
it.roomNotificationSettings?.mode == RoomNotificationMode.ALL_MESSAGES
}.last()
- assertThat(updatedState.roomNotificationSettings?.mode).isEqualTo(RoomNotificationMode.ALL_MESSAGES)
+ assertThat(updatedState.roomNotificationSettings?.mode).isEqualTo(
+ RoomNotificationMode.ALL_MESSAGES
+ )
cancelAndIgnoreRemainingEvents()
}
}
@Test
fun `present - when set is favorite event is emitted, then the action is called`() = runTest {
- val room = FakeMatrixRoom()
+ val setIsFavoriteResult = lambdaRecorder> { _ -> Result.success(Unit) }
+ val room = FakeMatrixRoom(
+ setIsFavoriteResult = setIsFavoriteResult,
+ canInviteResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ canSendStateResult = { _, _ -> Result.success(true) },
+ )
val analyticsService = FakeAnalyticsService()
- val presenter = createRoomDetailsPresenter(room = room, analyticsService = analyticsService)
+ val presenter =
+ createRoomDetailsPresenter(room = room, analyticsService = analyticsService)
presenter.test {
val initialState = awaitItem()
initialState.eventSink(RoomDetailsEvent.SetFavorite(true))
- assertThat(room.setIsFavoriteCalls).isEqualTo(listOf(true))
+ setIsFavoriteResult.assertions().isCalledOnce().with(value(true))
initialState.eventSink(RoomDetailsEvent.SetFavorite(false))
- assertThat(room.setIsFavoriteCalls).isEqualTo(listOf(true, false))
+ setIsFavoriteResult.assertions().isCalledExactly(2)
+ .withSequence(
+ listOf(value(true)),
+ listOf(value(false)),
+ )
assertThat(analyticsService.capturedEvents).containsExactly(
Interaction(name = Interaction.Name.MobileRoomFavouriteToggle),
Interaction(name = Interaction.Name.MobileRoomFavouriteToggle)
@@ -459,7 +612,11 @@ class RoomDetailsPresenterTest {
@Test
fun `present - changes in room info updates the is favorite flag`() = runTest {
- val room = aMatrixRoom()
+ val room = aMatrixRoom(
+ canInviteResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ canSendStateResult = { _, _ -> Result.success(true) },
+ )
val presenter = createRoomDetailsPresenter(room = room)
presenter.test {
room.givenRoomInfo(aRoomInfo(isFavorite = true))
diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/edit/RoomDetailsEditPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/edit/RoomDetailsEditPresenterTest.kt
index f287fe4ab4..5d8a43ca4b 100644
--- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/edit/RoomDetailsEditPresenterTest.kt
+++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/edit/RoomDetailsEditPresenterTest.kt
@@ -39,6 +39,9 @@ import io.element.android.libraries.permissions.api.PermissionsPresenter
import io.element.android.libraries.permissions.test.FakePermissionsPresenter
import io.element.android.libraries.permissions.test.FakePermissionsPresenterFactory
import io.element.android.tests.testutils.WarmUpRule
+import io.element.android.tests.testutils.lambda.lambdaError
+import io.element.android.tests.testutils.lambda.lambdaRecorder
+import io.element.android.tests.testutils.lambda.value
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
@@ -98,6 +101,7 @@ class RoomDetailsEditPresenterTest {
displayName = A_ROOM_NAME,
rawName = A_ROOM_RAW_NAME,
emitRoomInfo = true,
+ canSendStateResult = { _, _ -> Result.success(true) }
)
val presenter = createRoomDetailsEditPresenter(room)
moleculeFlow(RecompositionMode.Immediate) {
@@ -120,11 +124,17 @@ class RoomDetailsEditPresenterTest {
@Test
fun `present - sets canChangeName if user has permission`() = runTest {
- val room = aMatrixRoom(avatarUrl = AN_AVATAR_URL).apply {
- givenCanSendStateResult(StateEventType.ROOM_NAME, Result.success(true))
- givenCanSendStateResult(StateEventType.ROOM_AVATAR, Result.success(false))
- givenCanSendStateResult(StateEventType.ROOM_TOPIC, Result.failure(Throwable("Oops")))
- }
+ val room = aMatrixRoom(
+ avatarUrl = AN_AVATAR_URL,
+ canSendStateResult = { _, stateEventType ->
+ when (stateEventType) {
+ StateEventType.ROOM_NAME -> Result.success(true)
+ StateEventType.ROOM_AVATAR -> Result.success(false)
+ StateEventType.ROOM_TOPIC -> Result.failure(Throwable("Oops"))
+ else -> lambdaError()
+ }
+ },
+ )
val presenter = createRoomDetailsEditPresenter(room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -144,11 +154,17 @@ class RoomDetailsEditPresenterTest {
@Test
fun `present - sets canChangeAvatar if user has permission`() = runTest {
- val room = aMatrixRoom(avatarUrl = AN_AVATAR_URL).apply {
- givenCanSendStateResult(StateEventType.ROOM_NAME, Result.success(false))
- givenCanSendStateResult(StateEventType.ROOM_AVATAR, Result.success(true))
- givenCanSendStateResult(StateEventType.ROOM_TOPIC, Result.failure(Throwable("Oops")))
- }
+ val room = aMatrixRoom(
+ avatarUrl = AN_AVATAR_URL,
+ canSendStateResult = { _, stateEventType ->
+ when (stateEventType) {
+ StateEventType.ROOM_NAME -> Result.success(false)
+ StateEventType.ROOM_AVATAR -> Result.success(true)
+ StateEventType.ROOM_TOPIC -> Result.failure(Throwable("Oops"))
+ else -> lambdaError()
+ }
+ }
+ )
val presenter = createRoomDetailsEditPresenter(room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -168,11 +184,17 @@ class RoomDetailsEditPresenterTest {
@Test
fun `present - sets canChangeTopic if user has permission`() = runTest {
- val room = aMatrixRoom(avatarUrl = AN_AVATAR_URL).apply {
- givenCanSendStateResult(StateEventType.ROOM_NAME, Result.success(false))
- givenCanSendStateResult(StateEventType.ROOM_AVATAR, Result.failure(Throwable("Oops")))
- givenCanSendStateResult(StateEventType.ROOM_TOPIC, Result.success(true))
- }
+ val room = aMatrixRoom(
+ avatarUrl = AN_AVATAR_URL,
+ canSendStateResult = { _, stateEventType ->
+ when (stateEventType) {
+ StateEventType.ROOM_NAME -> Result.success(false)
+ StateEventType.ROOM_AVATAR -> Result.failure(Throwable("Oops"))
+ StateEventType.ROOM_TOPIC -> Result.success(true)
+ else -> lambdaError()
+ }
+ }
+ )
val presenter = createRoomDetailsEditPresenter(room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -197,6 +219,7 @@ class RoomDetailsEditPresenterTest {
displayName = "Name",
avatarUrl = AN_AVATAR_URL,
emitRoomInfo = true,
+ canSendStateResult = { _, _ -> Result.success(true) }
)
val presenter = createRoomDetailsEditPresenter(room)
moleculeFlow(RecompositionMode.Immediate) {
@@ -240,6 +263,7 @@ class RoomDetailsEditPresenterTest {
displayName = "Name",
avatarUrl = AN_AVATAR_URL,
emitRoomInfo = true,
+ canSendStateResult = { _, _ -> Result.success(true) }
)
fakePickerProvider.givenResult(anotherAvatarUri)
val presenter = createRoomDetailsEditPresenter(room)
@@ -262,6 +286,7 @@ class RoomDetailsEditPresenterTest {
displayName = "Name",
avatarUrl = AN_AVATAR_URL,
emitRoomInfo = true,
+ canSendStateResult = { _, _ -> Result.success(true) }
)
fakePickerProvider.givenResult(anotherAvatarUri)
val fakePermissionsPresenter = FakePermissionsPresenter()
@@ -298,6 +323,7 @@ class RoomDetailsEditPresenterTest {
displayName = "Name",
avatarUrl = AN_AVATAR_URL,
emitRoomInfo = true,
+ canSendStateResult = { _, _ -> Result.success(true) }
)
fakePickerProvider.givenResult(roomAvatarUri)
val presenter = createRoomDetailsEditPresenter(room)
@@ -346,6 +372,7 @@ class RoomDetailsEditPresenterTest {
displayName = "fallback",
avatarUrl = null,
emitRoomInfo = true,
+ canSendStateResult = { _, _ -> Result.success(true) }
)
fakePickerProvider.givenResult(roomAvatarUri)
val presenter = createRoomDetailsEditPresenter(room)
@@ -389,11 +416,18 @@ class RoomDetailsEditPresenterTest {
@Test
fun `present - save changes room details if different`() = runTest {
+ val setNameResult = lambdaRecorder { _: String -> Result.success(Unit) }
+ val setTopicResult = lambdaRecorder { _: String -> Result.success(Unit) }
+ val removeAvatarResult = lambdaRecorder> { Result.success(Unit) }
val room = aMatrixRoom(
topic = "My topic",
displayName = "Name",
avatarUrl = AN_AVATAR_URL,
emitRoomInfo = true,
+ setNameResult = setNameResult,
+ setTopicResult = setTopicResult,
+ removeAvatarResult = removeAvatarResult,
+ canSendStateResult = { _, _ -> Result.success(true) }
)
val presenter = createRoomDetailsEditPresenter(room)
moleculeFlow(RecompositionMode.Immediate) {
@@ -405,16 +439,20 @@ class RoomDetailsEditPresenterTest {
initialState.eventSink(RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.Remove))
initialState.eventSink(RoomDetailsEditEvents.Save)
skipItems(5)
- assertThat(room.newName).isEqualTo("New name")
- assertThat(room.newTopic).isEqualTo("New topic")
- assertThat(room.newAvatarData).isNull()
- assertThat(room.removedAvatar).isTrue()
+ setNameResult.assertions().isCalledOnce().with(value("New name"))
+ setTopicResult.assertions().isCalledOnce().with(value("New topic"))
+ removeAvatarResult.assertions().isCalledOnce()
}
}
@Test
fun `present - save doesn't change room details if they're the same trimmed`() = runTest {
- val room = aMatrixRoom(topic = "My topic", displayName = "Name", avatarUrl = AN_AVATAR_URL)
+ val room = aMatrixRoom(
+ topic = "My topic",
+ displayName = "Name",
+ avatarUrl = AN_AVATAR_URL,
+ canSendStateResult = { _, _ -> Result.success(true) }
+ )
val presenter = createRoomDetailsEditPresenter(room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -423,17 +461,18 @@ class RoomDetailsEditPresenterTest {
initialState.eventSink(RoomDetailsEditEvents.UpdateRoomName(" Name "))
initialState.eventSink(RoomDetailsEditEvents.UpdateRoomTopic(" My topic "))
initialState.eventSink(RoomDetailsEditEvents.Save)
- assertThat(room.newName).isNull()
- assertThat(room.newTopic).isNull()
- assertThat(room.newAvatarData).isNull()
- assertThat(room.removedAvatar).isFalse()
cancelAndIgnoreRemainingEvents()
}
}
@Test
fun `present - save doesn't change topic if it was unset and is now blank`() = runTest {
- val room = aMatrixRoom(topic = null, displayName = "Name", avatarUrl = AN_AVATAR_URL)
+ val room = aMatrixRoom(
+ topic = null,
+ displayName = "Name",
+ avatarUrl = AN_AVATAR_URL,
+ canSendStateResult = { _, _ -> Result.success(true) }
+ )
val presenter = createRoomDetailsEditPresenter(room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -441,17 +480,18 @@ class RoomDetailsEditPresenterTest {
val initialState = awaitItem()
initialState.eventSink(RoomDetailsEditEvents.UpdateRoomTopic(""))
initialState.eventSink(RoomDetailsEditEvents.Save)
- assertThat(room.newName).isNull()
- assertThat(room.newTopic).isNull()
- assertThat(room.newAvatarData).isNull()
- assertThat(room.removedAvatar).isFalse()
cancelAndIgnoreRemainingEvents()
}
}
@Test
fun `present - save doesn't change name if it's now empty`() = runTest {
- val room = aMatrixRoom(topic = "My topic", displayName = "Name", avatarUrl = AN_AVATAR_URL)
+ val room = aMatrixRoom(
+ topic = "My topic",
+ displayName = "Name",
+ avatarUrl = AN_AVATAR_URL,
+ canSendStateResult = { _, _ -> Result.success(true) }
+ )
val presenter = createRoomDetailsEditPresenter(room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -459,17 +499,20 @@ class RoomDetailsEditPresenterTest {
val initialState = awaitItem()
initialState.eventSink(RoomDetailsEditEvents.UpdateRoomName(""))
initialState.eventSink(RoomDetailsEditEvents.Save)
- assertThat(room.newName).isNull()
- assertThat(room.newTopic).isNull()
- assertThat(room.newAvatarData).isNull()
- assertThat(room.removedAvatar).isFalse()
cancelAndIgnoreRemainingEvents()
}
}
@Test
fun `present - save processes and sets avatar when processor returns successfully`() = runTest {
- val room = aMatrixRoom(topic = "My topic", displayName = "Name", avatarUrl = AN_AVATAR_URL)
+ val updateAvatarResult = lambdaRecorder { _: String, _: ByteArray -> Result.success(Unit) }
+ val room = aMatrixRoom(
+ topic = "My topic",
+ displayName = "Name",
+ avatarUrl = AN_AVATAR_URL,
+ updateAvatarResult = updateAvatarResult,
+ canSendStateResult = { _, _ -> Result.success(true) }
+ )
givenPickerReturnsFile()
val presenter = createRoomDetailsEditPresenter(room)
moleculeFlow(RecompositionMode.Immediate) {
@@ -478,17 +521,19 @@ class RoomDetailsEditPresenterTest {
val initialState = awaitItem()
initialState.eventSink(RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.ChoosePhoto))
initialState.eventSink(RoomDetailsEditEvents.Save)
- skipItems(3)
- assertThat(room.newName).isNull()
- assertThat(room.newTopic).isNull()
- assertThat(room.newAvatarData).isSameInstanceAs(fakeFileContents)
- assertThat(room.removedAvatar).isFalse()
+ skipItems(4)
+ updateAvatarResult.assertions().isCalledOnce().with(value("image/jpeg"), value(fakeFileContents))
}
}
@Test
fun `present - save does not set avatar data if processor fails`() = runTest {
- val room = aMatrixRoom(topic = "My topic", displayName = "Name", avatarUrl = AN_AVATAR_URL)
+ val room = aMatrixRoom(
+ topic = "My topic",
+ displayName = "Name",
+ avatarUrl = AN_AVATAR_URL,
+ canSendStateResult = { _, _ -> Result.success(true) }
+ )
fakePickerProvider.givenResult(anotherAvatarUri)
fakeMediaPreProcessor.givenResult(Result.failure(Throwable("Oh no")))
val presenter = createRoomDetailsEditPresenter(room)
@@ -498,11 +543,7 @@ class RoomDetailsEditPresenterTest {
val initialState = awaitItem()
initialState.eventSink(RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.ChoosePhoto))
initialState.eventSink(RoomDetailsEditEvents.Save)
- skipItems(2)
- assertThat(room.newName).isNull()
- assertThat(room.newTopic).isNull()
- assertThat(room.newAvatarData).isNull()
- assertThat(room.removedAvatar).isFalse()
+ skipItems(3)
assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Failure::class.java)
}
}
@@ -514,9 +555,9 @@ class RoomDetailsEditPresenterTest {
displayName = "Name",
avatarUrl = AN_AVATAR_URL,
emitRoomInfo = true,
- ).apply {
- givenSetNameResult(Result.failure(Throwable("!")))
- }
+ setNameResult = { Result.failure(Throwable("!")) },
+ canSendStateResult = { _, _ -> Result.success(true) }
+ )
saveAndAssertFailure(room, RoomDetailsEditEvents.UpdateRoomName("New name"))
}
@@ -527,9 +568,9 @@ class RoomDetailsEditPresenterTest {
displayName = "Name",
avatarUrl = AN_AVATAR_URL,
emitRoomInfo = true,
- ).apply {
- givenSetTopicResult(Result.failure(Throwable("!")))
- }
+ setTopicResult = { Result.failure(Throwable("!")) },
+ canSendStateResult = { _, _ -> Result.success(true) }
+ )
saveAndAssertFailure(room, RoomDetailsEditEvents.UpdateRoomTopic("New topic"))
}
@@ -540,9 +581,9 @@ class RoomDetailsEditPresenterTest {
displayName = "Name",
avatarUrl = AN_AVATAR_URL,
emitRoomInfo = true,
- ).apply {
- givenRemoveAvatarResult(Result.failure(Throwable("!")))
- }
+ removeAvatarResult = { Result.failure(Throwable("!")) },
+ canSendStateResult = { _, _ -> Result.success(true) }
+ )
saveAndAssertFailure(room, RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.Remove))
}
@@ -554,18 +595,22 @@ class RoomDetailsEditPresenterTest {
displayName = "Name",
avatarUrl = AN_AVATAR_URL,
emitRoomInfo = true,
- ).apply {
- givenUpdateAvatarResult(Result.failure(Throwable("!")))
- }
+ updateAvatarResult = { _, _ -> Result.failure(Throwable("!")) },
+ canSendStateResult = { _, _ -> Result.success(true) }
+ )
saveAndAssertFailure(room, RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.ChoosePhoto))
}
@Test
fun `present - CancelSaveChanges resets save action state`() = runTest {
givenPickerReturnsFile()
- val room = aMatrixRoom(topic = "My topic", displayName = "Name", avatarUrl = AN_AVATAR_URL).apply {
- givenSetTopicResult(Result.failure(Throwable("!")))
- }
+ val room = aMatrixRoom(
+ topic = "My topic",
+ displayName = "Name",
+ avatarUrl = AN_AVATAR_URL,
+ setTopicResult = { Result.failure(Throwable("!")) },
+ canSendStateResult = { _, _ -> Result.success(true) }
+ )
val presenter = createRoomDetailsEditPresenter(room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -573,7 +618,7 @@ class RoomDetailsEditPresenterTest {
val initialState = awaitItem()
initialState.eventSink(RoomDetailsEditEvents.UpdateRoomTopic("foo"))
initialState.eventSink(RoomDetailsEditEvents.Save)
- skipItems(2)
+ skipItems(3)
assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Failure::class.java)
initialState.eventSink(RoomDetailsEditEvents.CancelSaveChanges)
assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Uninitialized::class.java)
diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/RoomMemberListPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/RoomMemberListPresenterTest.kt
index a352d8bf27..8ca8b7b810 100644
--- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/RoomMemberListPresenterTest.kt
+++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/RoomMemberListPresenterTest.kt
@@ -52,7 +52,10 @@ class RoomMemberListPresenterTest {
@Test
fun `member loading is done automatically on start, but is async`() = runTest {
- val room = FakeMatrixRoom().apply {
+ val room = FakeMatrixRoom(
+ updateMembersResult = { Result.success(Unit) },
+ canInviteResult = { Result.success(true) }
+ ).apply {
// Needed to avoid discarding the loaded members as a partial and invalid result
givenRoomInfo(aRoomInfo(joinedMembersCount = 2))
}
@@ -78,7 +81,12 @@ class RoomMemberListPresenterTest {
@Test
fun `open search`() = runTest {
- val presenter = createPresenter()
+ val presenter = createPresenter(
+ matrixRoom = FakeMatrixRoom(
+ updateMembersResult = { Result.success(Unit) },
+ canInviteResult = { Result.success(true) }
+ )
+ )
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@@ -93,7 +101,12 @@ class RoomMemberListPresenterTest {
@Test
fun `search for something which is not found`() = runTest {
- val presenter = createPresenter()
+ val presenter = createPresenter(
+ matrixRoom = FakeMatrixRoom(
+ updateMembersResult = { Result.success(Unit) },
+ canInviteResult = { Result.success(true) }
+ )
+ )
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@@ -112,7 +125,12 @@ class RoomMemberListPresenterTest {
@Test
fun `search for something which is found`() = runTest {
- val presenter = createPresenter()
+ val presenter = createPresenter(
+ matrixRoom = FakeMatrixRoom(
+ updateMembersResult = { Result.success(Unit) },
+ canInviteResult = { Result.success(true) }
+ )
+ )
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@@ -134,9 +152,10 @@ class RoomMemberListPresenterTest {
@Test
fun `present - asynchronously sets canInvite when user has correct power level`() = runTest {
val presenter = createPresenter(
- matrixRoom = FakeMatrixRoom().apply {
- givenCanInviteResult(Result.success(true))
- }
+ matrixRoom = FakeMatrixRoom(
+ canInviteResult = { Result.success(true) },
+ updateMembersResult = { Result.success(Unit) }
+ )
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -150,9 +169,10 @@ class RoomMemberListPresenterTest {
@Test
fun `present - asynchronously sets canInvite when user does not have correct power level`() = runTest {
val presenter = createPresenter(
- matrixRoom = FakeMatrixRoom().apply {
- givenCanInviteResult(Result.success(false))
- }
+ matrixRoom = FakeMatrixRoom(
+ canInviteResult = { Result.success(false) },
+ updateMembersResult = { Result.success(Unit) }
+ )
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -166,9 +186,10 @@ class RoomMemberListPresenterTest {
@Test
fun `present - asynchronously sets canInvite when power level check fails`() = runTest {
val presenter = createPresenter(
- matrixRoom = FakeMatrixRoom().apply {
- givenCanInviteResult(Result.failure(Throwable("Eek")))
- }
+ matrixRoom = FakeMatrixRoom(
+ canInviteResult = { Result.failure(Throwable("Eek")) },
+ updateMembersResult = { Result.success(Unit) }
+ )
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -183,7 +204,14 @@ class RoomMemberListPresenterTest {
fun `present - RoomMemberSelected by default opens the room member details through the navigator`() = runTest {
val navigator = FakeRoomMemberListNavigator()
val moderationPresenter = FakeRoomMembersModerationPresenter(canDisplayModerationActions = false)
- val presenter = createPresenter(moderationPresenter = moderationPresenter, navigator = navigator)
+ val presenter = createPresenter(
+ moderationPresenter = moderationPresenter,
+ navigator = navigator,
+ matrixRoom = FakeMatrixRoom(
+ updateMembersResult = { Result.success(Unit) },
+ canInviteResult = { Result.success(true) }
+ )
+ )
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@@ -205,7 +233,14 @@ class RoomMemberListPresenterTest {
val moderationPresenter = FakeRoomMembersModerationPresenter(canDisplayModerationActions = true).apply {
givenState(capturingState)
}
- val presenter = createPresenter(moderationPresenter = moderationPresenter, navigator = navigator)
+ val presenter = createPresenter(
+ moderationPresenter = moderationPresenter,
+ navigator = navigator,
+ matrixRoom = FakeMatrixRoom(
+ updateMembersResult = { Result.success(Unit) },
+ canInviteResult = { Result.success(true) }
+ )
+ )
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@@ -236,10 +271,12 @@ private fun TestScope.createDataSource(
@ExperimentalCoroutinesApi
private fun TestScope.createPresenter(
coroutineDispatchers: CoroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true),
- matrixRoom: MatrixRoom = FakeMatrixRoom(),
+ matrixRoom: MatrixRoom = FakeMatrixRoom(
+ updateMembersResult = { Result.success(Unit) }
+ ),
roomMemberListDataSource: RoomMemberListDataSource = createDataSource(coroutineDispatchers = coroutineDispatchers),
moderationPresenter: FakeRoomMembersModerationPresenter = FakeRoomMembersModerationPresenter(),
- navigator: RoomMemberListNavigator = object : RoomMemberListNavigator { }
+ navigator: RoomMemberListNavigator = object : RoomMemberListNavigator {}
) = RoomMemberListPresenter(
room = matrixRoom,
roomMemberListDataSource = roomMemberListDataSource,
diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/details/RoomMemberDetailsPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/details/RoomMemberDetailsPresenterTest.kt
index c9df8d381c..1012de62bd 100644
--- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/details/RoomMemberDetailsPresenterTest.kt
+++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/details/RoomMemberDetailsPresenterTest.kt
@@ -34,6 +34,7 @@ import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
+import io.element.android.libraries.matrix.test.AN_EXCEPTION
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_THROWABLE
import io.element.android.libraries.matrix.test.FakeMatrixClient
@@ -53,9 +54,11 @@ class RoomMemberDetailsPresenterTest {
@Test
fun `present - returns the room member's data, then updates it if needed`() = runTest {
val roomMember = aRoomMember(displayName = "Alice")
- val room = aMatrixRoom().apply {
- givenUserDisplayNameResult(Result.success("A custom name"))
- givenUserAvatarUrlResult(Result.success("A custom avatar"))
+ val room = aMatrixRoom(
+ userDisplayNameResult = { Result.success("A custom name") },
+ userAvatarUrlResult = { Result.success("A custom avatar") },
+ getUpdatedMemberResult = { Result.success(roomMember) },
+ ).apply {
givenRoomMembersState(MatrixRoomMembersState.Ready(persistentListOf(roomMember)))
}
val presenter = createRoomMemberDetailsPresenter(
@@ -82,11 +85,14 @@ class RoomMemberDetailsPresenterTest {
@Test
fun `present - will recover when retrieving room member details fails`() = runTest {
val roomMember = aRoomMember(displayName = "Alice")
- val room = aMatrixRoom().apply {
- givenUserDisplayNameResult(Result.failure(Throwable()))
- givenUserAvatarUrlResult(Result.failure(Throwable()))
+ val room = aMatrixRoom(
+ userDisplayNameResult = { Result.failure(Throwable()) },
+ userAvatarUrlResult = { Result.failure(Throwable()) },
+ getUpdatedMemberResult = { Result.failure(AN_EXCEPTION) },
+ ).apply {
givenRoomMembersState(MatrixRoomMembersState.Ready(persistentListOf(roomMember)))
}
+
val presenter = createRoomMemberDetailsPresenter(
room = room,
roomMemberId = roomMember.userId
@@ -105,9 +111,11 @@ class RoomMemberDetailsPresenterTest {
@Test
fun `present - will fallback to original data if the updated data is null`() = runTest {
val roomMember = aRoomMember(displayName = "Alice")
- val room = aMatrixRoom().apply {
- givenUserDisplayNameResult(Result.success(null))
- givenUserAvatarUrlResult(Result.success(null))
+ val room = aMatrixRoom(
+ userDisplayNameResult = { Result.success(null) },
+ userAvatarUrlResult = { Result.success(null) },
+ getUpdatedMemberResult = { Result.success(roomMember) }
+ ).apply {
givenRoomMembersState(MatrixRoomMembersState.Ready(persistentListOf(roomMember)))
}
val presenter = createRoomMemberDetailsPresenter(
@@ -128,10 +136,11 @@ class RoomMemberDetailsPresenterTest {
@Test
fun `present - will fallback to user profile if user is not a member of the room`() = runTest {
val bobProfile = aMatrixUser("@bob:server.org", "Bob", avatarUrl = "anAvatarUrl")
- val room = aMatrixRoom().apply {
- givenUserDisplayNameResult(Result.failure(Exception("Not a member!")))
- givenUserAvatarUrlResult(Result.failure(Exception("Not a member!")))
- }
+ val room = aMatrixRoom(
+ userDisplayNameResult = { Result.failure(Exception("Not a member!")) },
+ userAvatarUrlResult = { Result.failure(Exception("Not a member!")) },
+ getUpdatedMemberResult = { Result.failure(AN_EXCEPTION) },
+ )
val client = FakeMatrixClient().apply {
givenGetProfileResult(bobProfile.userId, Result.success(bobProfile))
}
@@ -154,7 +163,13 @@ class RoomMemberDetailsPresenterTest {
@Test
fun `present - BlockUser needing confirmation displays confirmation dialog`() = runTest {
- val presenter = createRoomMemberDetailsPresenter()
+ val presenter = createRoomMemberDetailsPresenter(
+ room = aMatrixRoom(
+ getUpdatedMemberResult = { Result.failure(AN_EXCEPTION) },
+ userDisplayNameResult = { Result.success("Alice") },
+ userAvatarUrlResult = { Result.success("anAvatarUrl") },
+ )
+ )
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@@ -176,6 +191,11 @@ class RoomMemberDetailsPresenterTest {
val client = FakeMatrixClient()
val roomMember = aRoomMember()
val presenter = createRoomMemberDetailsPresenter(
+ room = aMatrixRoom(
+ getUpdatedMemberResult = { Result.failure(AN_EXCEPTION) },
+ userDisplayNameResult = { Result.success("Alice") },
+ userAvatarUrlResult = { Result.success("anAvatarUrl") },
+ ),
client = client,
roomMemberId = roomMember.userId
)
@@ -199,13 +219,21 @@ class RoomMemberDetailsPresenterTest {
fun `present - BlockUser with error`() = runTest {
val matrixClient = FakeMatrixClient()
matrixClient.givenIgnoreUserResult(Result.failure(A_THROWABLE))
- val presenter = createRoomMemberDetailsPresenter(client = matrixClient)
+ val presenter = createRoomMemberDetailsPresenter(
+ client = matrixClient,
+ room = aMatrixRoom(
+ getUpdatedMemberResult = { Result.success(aRoomMember(displayName = "Alice")) },
+ userDisplayNameResult = { Result.success("Alice") },
+ userAvatarUrlResult = { Result.success("anAvatarUrl") },
+ ),
+ )
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitFirstItem()
initialState.eventSink(UserProfileEvents.BlockUser(needsConfirmation = false))
assertThat(awaitItem().isBlocked.isLoading()).isTrue()
+ skipItems(2)
val errorState = awaitItem()
assertThat(errorState.isBlocked.errorOrNull()).isEqualTo(A_THROWABLE)
// Clear error
@@ -218,13 +246,21 @@ class RoomMemberDetailsPresenterTest {
fun `present - UnblockUser with error`() = runTest {
val matrixClient = FakeMatrixClient()
matrixClient.givenUnignoreUserResult(Result.failure(A_THROWABLE))
- val presenter = createRoomMemberDetailsPresenter(client = matrixClient)
+ val presenter = createRoomMemberDetailsPresenter(
+ room = aMatrixRoom(
+ getUpdatedMemberResult = { Result.success(aRoomMember(displayName = "Alice")) },
+ userDisplayNameResult = { Result.success("Alice") },
+ userAvatarUrlResult = { Result.success("anAvatarUrl") },
+ ),
+ client = matrixClient,
+ )
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitFirstItem()
initialState.eventSink(UserProfileEvents.UnblockUser(needsConfirmation = false))
assertThat(awaitItem().isBlocked.isLoading()).isTrue()
+ skipItems(2)
val errorState = awaitItem()
assertThat(errorState.isBlocked.errorOrNull()).isEqualTo(A_THROWABLE)
// Clear error
@@ -235,7 +271,13 @@ class RoomMemberDetailsPresenterTest {
@Test
fun `present - UnblockUser needing confirmation displays confirmation dialog`() = runTest {
- val presenter = createRoomMemberDetailsPresenter()
+ val presenter = createRoomMemberDetailsPresenter(
+ room = aMatrixRoom(
+ getUpdatedMemberResult = { Result.failure(AN_EXCEPTION) },
+ userDisplayNameResult = { Result.success("Alice") },
+ userAvatarUrlResult = { Result.success("anAvatarUrl") },
+ ),
+ )
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@@ -255,7 +297,14 @@ class RoomMemberDetailsPresenterTest {
@Test
fun `present - start DM action complete scenario`() = runTest {
val startDMAction = FakeStartDMAction()
- val presenter = createRoomMemberDetailsPresenter(startDMAction = startDMAction)
+ val presenter = createRoomMemberDetailsPresenter(
+ room = aMatrixRoom(
+ getUpdatedMemberResult = { Result.success(aRoomMember(displayName = "Alice")) },
+ userDisplayNameResult = { Result.success("Alice") },
+ userAvatarUrlResult = { Result.success("anAvatarUrl") },
+ ),
+ startDMAction = startDMAction,
+ )
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@@ -268,6 +317,7 @@ class RoomMemberDetailsPresenterTest {
startDMAction.givenExecuteResult(startDMFailureResult)
initialState.eventSink(UserProfileEvents.StartDM)
assertThat(awaitItem().startDmActionState).isInstanceOf(AsyncAction.Loading::class.java)
+ skipItems(2)
awaitItem().also { state ->
assertThat(state.startDmActionState).isEqualTo(startDMFailureResult)
state.eventSink(UserProfileEvents.ClearStartDMState)
@@ -292,8 +342,8 @@ class RoomMemberDetailsPresenterTest {
}
private fun createRoomMemberDetailsPresenter(
+ room: MatrixRoom,
client: MatrixClient = FakeMatrixClient(),
- room: MatrixRoom = aMatrixRoom(),
roomMemberId: UserId = UserId("@alice:server.org"),
startDMAction: StartDMAction = FakeStartDMAction()
): RoomMemberDetailsPresenter {
diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/moderation/DefaultRoomMembersModerationPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/moderation/DefaultRoomMembersModerationPresenterTest.kt
index ae789f8958..5f704a23d4 100644
--- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/moderation/DefaultRoomMembersModerationPresenterTest.kt
+++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/moderation/DefaultRoomMembersModerationPresenterTest.kt
@@ -54,29 +54,34 @@ class DefaultRoomMembersModerationPresenterTest {
@Test
fun `canDisplayModerationActions - when user can kick other users, FF is enabled and room is not a DM returns true`() = runTest {
- val room = FakeMatrixRoom(isDirect = false, activeMemberCount = 10).apply {
- givenCanKickResult(Result.success(true))
- }
+ val room = FakeMatrixRoom(
+ isDirect = false,
+ activeMemberCount = 10,
+ canKickResult = { Result.success(true) },
+ canBanResult = { Result.success(true) },
+ )
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room)
assertThat(presenter.canDisplayModerationActions()).isTrue()
}
@Test
fun `canDisplayModerationActions - when user can ban other users, FF is enabled and room is not a DM returns true`() = runTest {
- val room = FakeMatrixRoom(isDirect = false, activeMemberCount = 10).apply {
- givenCanBanResult(Result.success(true))
- }
+ val room = FakeMatrixRoom(
+ isDirect = false,
+ activeMemberCount = 10,
+ canBanResult = { Result.success(true) },
+ )
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room)
assertThat(presenter.canDisplayModerationActions()).isTrue()
}
@Test
fun `present - SelectRoomMember when the current user has permissions displays member actions`() = runTest {
- val room = FakeMatrixRoom().apply {
- givenCanKickResult(Result.success(true))
- givenCanBanResult(Result.success(true))
- givenUserRoleResult(Result.success(RoomMember.Role.ADMIN))
- }
+ val room = FakeMatrixRoom(
+ canKickResult = { Result.success(true) },
+ canBanResult = { Result.success(true) },
+ userRoleResult = { Result.success(RoomMember.Role.ADMIN) },
+ )
val selectedMember = aVictor()
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room)
moleculeFlow(RecompositionMode.Immediate) {
@@ -98,11 +103,12 @@ class DefaultRoomMembersModerationPresenterTest {
@Test
fun `present - SelectRoomMember displays only view profile if selected member has same power level as the current user`() = runTest {
- val room = FakeMatrixRoom(sessionId = A_USER_ID).apply {
- givenCanKickResult(Result.success(true))
- givenCanBanResult(Result.success(true))
- givenUserRoleResult(Result.success(RoomMember.Role.ADMIN))
- }
+ val room = FakeMatrixRoom(
+ sessionId = A_USER_ID,
+ canKickResult = { Result.success(true) },
+ canBanResult = { Result.success(true) },
+ userRoleResult = { Result.success(RoomMember.Role.ADMIN) },
+ )
val selectedMember = aRoomMember(A_USER_ID_2, powerLevel = 100L)
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room)
moleculeFlow(RecompositionMode.Immediate) {
@@ -123,11 +129,11 @@ class DefaultRoomMembersModerationPresenterTest {
@Test
fun `present - SelectRoomMember displays an unban confirmation dialog when the member is banned`() = runTest {
val selectedMember = aRoomMember(A_USER_ID_2, membership = RoomMembershipState.BAN)
- val room = FakeMatrixRoom().apply {
- givenCanKickResult(Result.success(true))
- givenCanBanResult(Result.success(true))
- givenUserRoleResult(Result.success(RoomMember.Role.ADMIN))
- }
+ val room = FakeMatrixRoom(
+ canKickResult = { Result.success(true) },
+ canBanResult = { Result.success(true) },
+ userRoleResult = { Result.success(RoomMember.Role.ADMIN) },
+ )
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -144,11 +150,12 @@ class DefaultRoomMembersModerationPresenterTest {
@Test
fun `present - Kick removes the user`() = runTest {
val analyticsService = FakeAnalyticsService()
- val room = FakeMatrixRoom().apply {
- givenCanKickResult(Result.success(true))
- givenCanBanResult(Result.success(true))
- givenUserRoleResult(Result.success(RoomMember.Role.ADMIN))
- }
+ val room = FakeMatrixRoom(
+ canKickResult = { Result.success(true) },
+ canBanResult = { Result.success(true) },
+ userRoleResult = { Result.success(RoomMember.Role.ADMIN) },
+ kickUserResult = { _, _ -> Result.success(Unit) },
+ )
val selectedMember = aVictor()
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room, analyticsService = analyticsService)
moleculeFlow(RecompositionMode.Immediate) {
@@ -171,11 +178,12 @@ class DefaultRoomMembersModerationPresenterTest {
@Test
fun `present - BanUser requires confirmation and then bans the user`() = runTest {
val analyticsService = FakeAnalyticsService()
- val room = FakeMatrixRoom().apply {
- givenCanKickResult(Result.success(true))
- givenCanBanResult(Result.success(true))
- givenUserRoleResult(Result.success(RoomMember.Role.ADMIN))
- }
+ val room = FakeMatrixRoom(
+ canKickResult = { Result.success(true) },
+ canBanResult = { Result.success(true) },
+ userRoleResult = { Result.success(RoomMember.Role.ADMIN) },
+ banUserResult = { _, _ -> Result.success(Unit) },
+ )
val selectedMember = aVictor()
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room, analyticsService = analyticsService)
moleculeFlow(RecompositionMode.Immediate) {
@@ -204,11 +212,13 @@ class DefaultRoomMembersModerationPresenterTest {
fun `present - UnbanUser requires confirmation and then unbans the user`() = runTest {
val analyticsService = FakeAnalyticsService()
val selectedMember = aRoomMember(A_USER_ID_2, membership = RoomMembershipState.BAN)
- val room = FakeMatrixRoom().apply {
- givenCanKickResult(Result.success(true))
- givenCanBanResult(Result.success(true))
+ val room = FakeMatrixRoom(
+ canKickResult = { Result.success(true) },
+ canBanResult = { Result.success(true) },
+ userRoleResult = { Result.success(RoomMember.Role.ADMIN) },
+ unBanUserResult = { _, _ -> Result.success(Unit) },
+ ).apply {
givenRoomMembersState(MatrixRoomMembersState.Ready(persistentListOf(selectedMember)))
- givenUserRoleResult(Result.success(RoomMember.Role.ADMIN))
}
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room, analyticsService = analyticsService)
moleculeFlow(RecompositionMode.Immediate) {
@@ -231,10 +241,11 @@ class DefaultRoomMembersModerationPresenterTest {
@Test
fun `present - Reset removes the selected user and actions`() = runTest {
- val room = FakeMatrixRoom().apply {
- givenCanKickResult(Result.success(true))
- givenCanBanResult(Result.success(true))
- }
+ val room = FakeMatrixRoom(
+ canKickResult = { Result.success(true) },
+ canBanResult = { Result.success(true) },
+ userRoleResult = { Result.success(RoomMember.Role.USER) },
+ )
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -251,13 +262,14 @@ class DefaultRoomMembersModerationPresenterTest {
@Test
fun `present - Reset resets any async actions`() = runTest {
- val room = FakeMatrixRoom().apply {
- givenCanKickResult(Result.success(true))
- givenCanBanResult(Result.success(true))
- givenKickUserResult(Result.failure(Throwable("Eek")))
- givenBanUserResult(Result.failure(Throwable("Eek")))
- givenUnbanUserResult(Result.failure(Throwable("Eek")))
- }
+ val room = FakeMatrixRoom(
+ canKickResult = { Result.success(true) },
+ canBanResult = { Result.success(true) },
+ kickUserResult = { _, _ -> Result.failure(Throwable("Eek")) },
+ banUserResult = { _, _ -> Result.failure(Throwable("Eek")) },
+ unBanUserResult = { _, _ -> Result.failure(Throwable("Eek")) },
+ userRoleResult = { Result.success(RoomMember.Role.USER) },
+ )
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/RolesAndPermissionPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/RolesAndPermissionPresenterTest.kt
index 35353f0d1a..c2eb07002c 100644
--- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/RolesAndPermissionPresenterTest.kt
+++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/RolesAndPermissionPresenterTest.kt
@@ -27,6 +27,7 @@ import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
+import io.element.android.libraries.matrix.test.room.defaultRoomPowerLevels
import io.element.android.services.analytics.test.FakeAnalyticsService
import io.element.android.tests.testutils.testCoroutineDispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -67,7 +68,12 @@ class RolesAndPermissionPresenterTest {
@OptIn(ExperimentalCoroutinesApi::class)
@Test
fun `present - DemoteSelfTo changes own role to the specified one`() = runTest(StandardTestDispatcher()) {
- val presenter = createRolesAndPermissionsPresenter(dispatchers = testCoroutineDispatchers())
+ val presenter = createRolesAndPermissionsPresenter(
+ dispatchers = testCoroutineDispatchers(),
+ room = FakeMatrixRoom(
+ updateUserRoleResult = { Result.success(Unit) }
+ ),
+ )
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@@ -85,9 +91,9 @@ class RolesAndPermissionPresenterTest {
@OptIn(ExperimentalCoroutinesApi::class)
@Test
fun `present - DemoteSelfTo can handle failures and clean them`() = runTest(StandardTestDispatcher()) {
- val room = FakeMatrixRoom().apply {
- givenUpdateUserRoleResult(Result.failure(Exception("Failed to update role")))
- }
+ val room = FakeMatrixRoom(
+ updateUserRoleResult = { Result.failure(Exception("Failed to update role")) }
+ )
val presenter = createRolesAndPermissionsPresenter(room = room, dispatchers = testCoroutineDispatchers())
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -123,7 +129,12 @@ class RolesAndPermissionPresenterTest {
@Test
fun `present - ResetPermissions needs confirmation, then resets permissions`() = runTest {
val analyticsService = FakeAnalyticsService()
- val presenter = createRolesAndPermissionsPresenter(analyticsService = analyticsService)
+ val presenter = createRolesAndPermissionsPresenter(
+ analyticsService = analyticsService,
+ room = FakeMatrixRoom(
+ resetPowerLevelsResult = { Result.success(defaultRoomPowerLevels()) }
+ )
+ )
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/changeroles/ChangeRolesPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/changeroles/ChangeRolesPresenterTest.kt
index d4155d9924..71223c8ff3 100644
--- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/changeroles/ChangeRolesPresenterTest.kt
+++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/changeroles/ChangeRolesPresenterTest.kt
@@ -275,7 +275,10 @@ class ChangeRolesPresenterTest {
@Test
fun `present - Save will display a confirmation when adding admins`() = runTest {
- val room = FakeMatrixRoom().apply {
+ val room = FakeMatrixRoom(
+ updateUserRoleResult = { Result.success(Unit) },
+ updateMembersResult = { Result.success(Unit) },
+ ).apply {
givenRoomMembersState(MatrixRoomMembersState.Ready(aRoomMemberList()))
givenRoomInfo(aRoomInfo(userPowerLevels = persistentMapOf(A_USER_ID to 100)))
}
@@ -325,7 +328,10 @@ class ChangeRolesPresenterTest {
@Test
fun `present - Save will just save the data for moderators`() = runTest {
val analyticsService = FakeAnalyticsService()
- val room = FakeMatrixRoom().apply {
+ val room = FakeMatrixRoom(
+ updateUserRoleResult = { Result.success(Unit) },
+ updateMembersResult = { Result.success(Unit) },
+ ).apply {
givenRoomMembersState(MatrixRoomMembersState.Ready(aRoomMemberList()))
givenRoomInfo(aRoomInfo(userPowerLevels = persistentMapOf(A_USER_ID to 50)))
}
@@ -351,10 +357,11 @@ class ChangeRolesPresenterTest {
@Test
fun `present - Save can handle failures and ClearError clears them`() = runTest {
- val room = FakeMatrixRoom().apply {
+ val room = FakeMatrixRoom(
+ updateUserRoleResult = { Result.failure(IllegalStateException("Failed")) }
+ ).apply {
givenRoomMembersState(MatrixRoomMembersState.Ready(aRoomMemberList()))
givenRoomInfo(aRoomInfo(userPowerLevels = persistentMapOf(A_USER_ID to 50)))
- givenUpdateUserRoleResult(Result.failure(IllegalStateException("Failed")))
}
val presenter = createChangeRolesPresenter(role = RoomMember.Role.MODERATOR, room = room)
moleculeFlow(RecompositionMode.Immediate) {
diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/permissions/ChangeRoomPermissionsPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/permissions/ChangeRoomPermissionsPresenterTest.kt
index c37d458d67..5d2ae18783 100644
--- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/permissions/ChangeRoomPermissionsPresenterTest.kt
+++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/permissions/ChangeRoomPermissionsPresenterTest.kt
@@ -164,7 +164,13 @@ class ChangeRoomPermissionsPresenterTest {
@Test
fun `present - Save updates the current permissions and resets hasChanges`() = runTest {
val analyticsService = FakeAnalyticsService()
- val presenter = createChangeRoomPermissionsPresenter(analyticsService = analyticsService)
+ val presenter = createChangeRoomPermissionsPresenter(
+ analyticsService = analyticsService,
+ room = FakeMatrixRoom(
+ updatePowerLevelsResult = { Result.success(Unit) },
+ powerLevelsResult = { Result.success(defaultPermissions()) }
+ ),
+ )
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@@ -208,9 +214,9 @@ class ChangeRoomPermissionsPresenterTest {
@Test
fun `present - Save will fail if there are not current permissions`() = runTest {
- val room = FakeMatrixRoom().apply {
- givenPowerLevelsResult(Result.failure(IllegalStateException("Failed to load power levels")))
- }
+ val room = FakeMatrixRoom(
+ powerLevelsResult = { Result.failure(IllegalStateException("Failed to load power levels")) }
+ )
val presenter = createChangeRoomPermissionsPresenter(room = room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -225,9 +231,10 @@ class ChangeRoomPermissionsPresenterTest {
@Test
fun `present - Save can handle failures and they can be cleared`() = runTest {
- val room = FakeMatrixRoom().apply {
- givenUpdatePowerLevelsResult(Result.failure(IllegalStateException("Failed to update power levels")))
- }
+ val room = FakeMatrixRoom(
+ powerLevelsResult = { Result.success(defaultPermissions()) },
+ updatePowerLevelsResult = { Result.failure(IllegalStateException("Failed to update power levels")) },
+ )
val presenter = createChangeRoomPermissionsPresenter(room = room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -292,7 +299,9 @@ class ChangeRoomPermissionsPresenterTest {
private fun createChangeRoomPermissionsPresenter(
section: ChangeRoomPermissionsSection = ChangeRoomPermissionsSection.RoomDetails,
- room: FakeMatrixRoom = FakeMatrixRoom(),
+ room: FakeMatrixRoom = FakeMatrixRoom(
+ powerLevelsResult = { Result.success(defaultPermissions()) }
+ ),
analyticsService: FakeAnalyticsService = FakeAnalyticsService(),
) = ChangeRoomPermissionsPresenter(
section = section,
diff --git a/features/roomlist/impl/src/main/res/values-el/translations.xml b/features/roomlist/impl/src/main/res/values-el/translations.xml
index b3a4a8f04d..71aca711ad 100644
--- a/features/roomlist/impl/src/main/res/values-el/translations.xml
+++ b/features/roomlist/impl/src/main/res/values-el/translations.xml
@@ -25,12 +25,13 @@
"Μπορείς να καταργήσεις την επιλογή φίλτρων για να δεις τις άλλες συνομιλίες σου"
"Δεν έχεις συνομιλίες για αυτήν την επιλογή"
"Άτομα"
+ "Δεν έχεις ακόμα ΠΜ"
"Δωμάτια"
"Δεν είσαι ακόμα σε κανένα δωμάτιο"
"Μη αναγνωσμένα"
"Συγχαρητήρια!
Δεν έχεις μη αναγνωσμένα μηνύματα!"
- "Συζητήσεις"
+ "Συνομιλίες"
"Επισήμανση ως αναγνωσμένου"
"Επισήμανση ως μη αναγνωσμένου"
"Περιήγηση σε όλα τα δωμάτια"
diff --git a/features/roomlist/impl/src/main/res/values-ka/translations.xml b/features/roomlist/impl/src/main/res/values-ka/translations.xml
index de6358f443..dac714cd0a 100644
--- a/features/roomlist/impl/src/main/res/values-ka/translations.xml
+++ b/features/roomlist/impl/src/main/res/values-ka/translations.xml
@@ -1,5 +1,7 @@
+ "თქვენი ჩეთების სარეზერვო ასლი ამჟამად არ არის სინქრონიზებული. თქვენ უნდა შეიყვანოთ თქვენი აღდგენის გასაღები, რათა შეინარჩუნოთ წვდომა ჩეთების სარეზერვო ასლზე."
+ "შეიყვანეთ აღდგენის გასაღები"
"დარწმუნებული ხართ, რომ გსურთ, უარი თქვათ მოწვევაზე %1$s-ში?"
"მოწვევაზე უარის თქმა"
"დარწმუნებული ხართ, რომ გსურთ, უარი თქვათ ჩატზე %1$s-თან?"
diff --git a/features/roomlist/impl/src/main/res/values-pl/translations.xml b/features/roomlist/impl/src/main/res/values-pl/translations.xml
new file mode 100644
index 0000000000..0ae62297a1
--- /dev/null
+++ b/features/roomlist/impl/src/main/res/values-pl/translations.xml
@@ -0,0 +1,22 @@
+
+
+ "Twoja kopia zapasowa czatu jest obecnie niezsynchronizowana. Aby zachować dostęp do kopii zapasowej czatu, musisz potwierdzić klucz odzyskiwania."
+ "Potwierdź klucz odzyskiwania"
+ "Upewnij się, że nie pominiesz żadnego połączenia. Zmień swoje ustawienia i zezwól na powiadomienia na blokadzie ekranu."
+ "Popraw jakość swoich rozmów"
+ "Czy na pewno chcesz odrzucić zaproszenie do dołączenia do %1$s?"
+ "Odrzuć zaproszenie"
+ "Czy na pewno chcesz odrzucić rozmowę prywatną z %1$s?"
+ "Odrzuć czat"
+ "Brak zaproszeń"
+ "%1$s (%2$s) zaprosił Cię"
+ "Jest to jednorazowy proces, dziękujemy za czekanie."
+ "Konfigurowanie Twojego konta."
+ "Utwórz nową rozmowę lub pokój"
+ "Wyślij komuś wiadomość, aby rozpocząć."
+ "Brak czatów."
+ "Osoby"
+ "Wszystkie czaty"
+ "Wygląda na to, że używasz nowego urządzenia. Zweryfikuj się innym urządzeniem, aby uzyskać dostęp do zaszyfrowanych wiadomości."
+ "Potwierdź, że to Ty"
+
diff --git a/features/roomlist/impl/src/main/res/values-pt-rBR/translations.xml b/features/roomlist/impl/src/main/res/values-pt-rBR/translations.xml
new file mode 100644
index 0000000000..6765199a70
--- /dev/null
+++ b/features/roomlist/impl/src/main/res/values-pt-rBR/translations.xml
@@ -0,0 +1,19 @@
+
+
+ "Insira sua chave de recuperação"
+ "Tem certeza de que deseja recusar o convite para ingressar em %1$s?"
+ "Recusar convite"
+ "Tem certeza de que deseja recusar esse chat privado com %1$s?"
+ "Recusar chat"
+ "Sem convites"
+ "%1$s(%2$s) convidou você"
+ "Este é um processo único, obrigado por esperar."
+ "Configurando sua conta."
+ "Criar uma nova conversa ou sala"
+ "Comece enviando uma mensagem para alguém."
+ "Ainda não há conversas."
+ "Pessoas"
+ "Conversas"
+ "Parece que você está usando um novo dispositivo. Verifique com outro dispositivo para acessar suas mensagens criptografadas."
+ "Verifique se é você"
+
diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTest.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTest.kt
index b48a603a85..7a9a8b2744 100644
--- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTest.kt
+++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTest.kt
@@ -441,7 +441,10 @@ class RoomListPresenterTest {
@Test
fun `present - when set is favorite event is emitted, then the action is called`() = runTest {
val scope = CoroutineScope(coroutineContext + SupervisorJob())
- val room = FakeMatrixRoom()
+ val setIsFavoriteResult = lambdaRecorder { _: Boolean -> Result.success(Unit) }
+ val room = FakeMatrixRoom(
+ setIsFavoriteResult = setIsFavoriteResult
+ )
val analyticsService = FakeAnalyticsService()
val client = FakeMatrixClient().apply {
givenGetRoomResult(A_ROOM_ID, room)
@@ -452,9 +455,13 @@ class RoomListPresenterTest {
}.test {
val initialState = awaitItem()
initialState.eventSink(RoomListEvents.SetRoomIsFavorite(A_ROOM_ID, true))
- assertThat(room.setIsFavoriteCalls).isEqualTo(listOf(true))
+ setIsFavoriteResult.assertions().isCalledOnce().with(value(true))
initialState.eventSink(RoomListEvents.SetRoomIsFavorite(A_ROOM_ID, false))
- assertThat(room.setIsFavoriteCalls).isEqualTo(listOf(true, false))
+ setIsFavoriteResult.assertions().isCalledExactly(2)
+ .withSequence(
+ listOf(value(true)),
+ listOf(value(false)),
+ )
assertThat(analyticsService.capturedEvents).containsExactly(
Interaction(name = Interaction.Name.MobileRoomListRoomContextMenuFavouriteToggle),
Interaction(name = Interaction.Name.MobileRoomListRoomContextMenuFavouriteToggle)
diff --git a/features/securebackup/impl/src/main/res/values-ka/translations.xml b/features/securebackup/impl/src/main/res/values-ka/translations.xml
index e31153859c..6c1ed02eb5 100644
--- a/features/securebackup/impl/src/main/res/values-ka/translations.xml
+++ b/features/securebackup/impl/src/main/res/values-ka/translations.xml
@@ -22,10 +22,14 @@
"აღდგენის გასაღები შეიცვალა"
"გსურთ აღდგენის გასაღების შეცვლა?"
"დარწმუნდით, რომ ვერავინ ხედავს ამ ეკრანს!"
+ "გთხოვთ, სცადოთ ხელახლა, რათა თქვენი ჩეთის სარეზერვო ასლაზე წვდომა დაადასტუროთ"
+ "აღდგენის არასწორი გასაღები"
"თუ თქვენ გაქვთ უსაფრთხოების გასაღები ან უსაფრთხოების ფრაზა, ეს ასევე იმუშავებს."
"შეყვანა"
"აღდგენის გასაღები დადასტურებულია"
"შეიყვანეთ თქვენი აღდგენის გასაღები"
+ "დაკოპირებულია აღდგენის გასაღები"
+ "გენერირება…"
"აღდგენის გასაღების შენახვა"
"ჩაწერეთ თქვენი აღდგენის გასაღები სადმე უსაფრთხო ადგილას ან შეინახეთ პაროლის მენეჯერში."
"აღდგენის გასაღების დასაკოპირებლად, დააწკაპუნეთ"
diff --git a/features/securebackup/impl/src/main/res/values-pl/translations.xml b/features/securebackup/impl/src/main/res/values-pl/translations.xml
new file mode 100644
index 0000000000..ca1589c5ac
--- /dev/null
+++ b/features/securebackup/impl/src/main/res/values-pl/translations.xml
@@ -0,0 +1,44 @@
+
+
+ "Wyłącz backup"
+ "Włącz backup"
+ "Backup zapewnia, że nie stracisz swojej historii wiadomości. %1$s"
+ "Backup"
+ "Zmień klucz przywracania"
+ "Wprowadź klucz przywracania"
+ "Backup czatu nie jest zsynchronizowany."
+ "Skonfiguruj przywracanie"
+ "Uzyskaj dostęp do swoich wiadomości szyfrowanych, jeśli utracisz wszystkie swoje urządzenia lub zostaniesz wylogowany z %1$s."
+ "Wyłącz"
+ "Utracisz dostęp do wiadomości szyfrowanych, jeśli zostaniesz wylogowany ze wszystkich urządzeń."
+ "Czy na pewno chcesz wyłączyć backup?"
+ "Wyłączenie backupu spowoduje usunięcie kopii klucza szyfrowania i wyłączenie innych funkcji bezpieczeństwa. W takim przypadku będziesz:"
+ "Posiadał historii wiadomości szyfrowanych na nowych urządzeniach"
+ "Utracisz dostęp do wiadomości szyfrowanych, jeśli zostaniesz wszędzie wylogowany z %1$s"
+ "Czy na pewno chcesz wyłączyć backup?"
+ "Uzyskaj nowy klucz przywracania, jeśli straciłeś dostęp do obecnego. Po zmianie klucza przywracania stary nie będzie już działał."
+ "Generuj nowy klucz przywracania"
+ "Upewnij się, że klucz przywracania będzie trzymany w bezpiecznym miejscu"
+ "Zmieniono klucz przywracania"
+ "Zmienić klucz przywracania?"
+ "Upewnij się, że nikt nie widzi tego ekranu!"
+ "Spróbuj ponownie, aby potwierdzić dostęp do backupu czatu."
+ "Nieprawidłowy klucz przywracania"
+ "To też zadziała, jeśli posiadasz klucz lub frazę bezpieczeństwa."
+ "Wprowadź…"
+ "Potwierdzono klucz przywracania"
+ "Wprowadź klucz przywracania"
+ "Skopiowano klucz przywracania"
+ "Generuję…"
+ "Zapisz klucz przywracania"
+ "Zapisz klucz przywracania w bezpiecznym miejscu lub zapisz go w menedżerze haseł."
+ "Stuknij, by skopiować klucz przywracania"
+ "Zapisz klucz przywracania"
+ "Po tym kroku nie będziesz mieć dostępu do nowego klucza przywracania."
+ "Czy zapisałeś swój klucz przywracania?"
+ "Backup czatu jest chroniony przez klucz przywracania. Jeśli potrzebujesz utworzyć nowy klucz, możesz to zrobić wybierając `Zmień klucz przywracania`."
+ "Wygeneruj klucz przywracania"
+ "Upewnij się, że klucz przywracania możesz przechowywać w bezpiecznym miejscu"
+ "Skonfigurowano przywracanie pomyślnie"
+ "Skonfiguruj przywracanie"
+
diff --git a/features/securebackup/impl/src/main/res/values-pt-rBR/translations.xml b/features/securebackup/impl/src/main/res/values-pt-rBR/translations.xml
new file mode 100644
index 0000000000..786de3f4da
--- /dev/null
+++ b/features/securebackup/impl/src/main/res/values-pt-rBR/translations.xml
@@ -0,0 +1,40 @@
+
+
+ "Desativar o backup"
+ "Ativar o backup"
+ "O backup garante que você não perca seu histórico de mensagens. %1$s."
+ "Backup"
+ "Mudar chave de recuperação"
+ "Insira a chave de recuperação"
+ "Seu backup das conversas está atualmente fora de sincronia."
+ "Configurar a recuperação"
+ "Tenha acesso às suas mensagens criptografadas se você perder todos os seus dispositivos ou for desconectado do %1$s em qualquer lugar."
+ "Desligar"
+ "Você perderá suas mensagens criptografadas se estiver desconectado de todos os dispositivos."
+ "Tem certeza de que deseja desativar o backup?"
+ "Desativar o backup removerá o backup da chave de criptografia atual e desativará outros recursos de segurança. Neste caso, você irá:"
+ "Não ter histórico de mensagens criptografadas em novos dispositivos"
+ "Perder o acesso às suas mensagens criptografadas se você estiver desconectado %1$s em todos os lugares"
+ "Tem certeza de que deseja desativar o backup?"
+ "Obtenha uma nova chave de recuperação caso tenha perdido a existente. Depois de alterar sua chave de recuperação, a antiga não funcionará mais."
+ "Gere uma nova chave de recuperação"
+ "Certifique-se de que você pode armazenar sua chave de recuperação em algum lugar seguro"
+ "Chave de recuperação alterada"
+ "Alterar chave de recuperação?"
+ "Certifique-se de que ninguém possa ver essa tela!"
+ "Se você tiver uma chave de segurança ou frase de segurança, isso também funcionará."
+ "Inserir…"
+ "Chave de recuperação confirmada"
+ "Insira sua chave de recuperação"
+ "Salvar chave de recuperação"
+ "Anote sua chave de recuperação em algum lugar seguro ou salve-a em um gerenciador de senhas."
+ "Toque para copiar a chave de recuperação"
+ "Salve sua chave de recuperação"
+ "Você não poderá acessar sua nova chave de recuperação após essa etapa."
+ "Você salvou sua chave de recuperação?"
+ "Seu backup das conversas é protegido por uma chave de recuperação. Se precisar de uma nova chave de recuperação após a configuração, você pode recriá-la selecionando “Alterar chave de recuperação”."
+ "Gere sua chave de recuperação"
+ "Certifique-se de que você pode armazenar sua chave de recuperação em algum lugar seguro"
+ "Configuração de recuperação bem-sucedida"
+ "Configurar a recuperação"
+
diff --git a/features/share/impl/src/test/kotlin/io/element/android/features/share/impl/SharePresenterTest.kt b/features/share/impl/src/test/kotlin/io/element/android/features/share/impl/SharePresenterTest.kt
index 42c053bafb..8c91ea530c 100644
--- a/features/share/impl/src/test/kotlin/io/element/android/features/share/impl/SharePresenterTest.kt
+++ b/features/share/impl/src/test/kotlin/io/element/android/features/share/impl/SharePresenterTest.kt
@@ -28,6 +28,7 @@ import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.test.A_MESSAGE
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.FakeMatrixClient
+import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import io.element.android.libraries.mediaupload.api.MediaPreProcessor
import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor
@@ -92,7 +93,9 @@ class SharePresenterTest {
@Test
fun `present - send text ok`() = runTest {
- val matrixRoom = FakeMatrixRoom()
+ val matrixRoom = FakeMatrixRoom(
+ sendMessageResult = { _, _, _ -> Result.success(Unit) },
+ )
val matrixClient = FakeMatrixClient().apply {
givenGetRoomResult(A_ROOM_ID, matrixRoom)
}
@@ -117,7 +120,9 @@ class SharePresenterTest {
@Test
fun `present - send media ok`() = runTest {
- val matrixRoom = FakeMatrixRoom()
+ val matrixRoom = FakeMatrixRoom(
+ sendMediaResult = { Result.success(FakeMediaUploadHandler()) },
+ )
val matrixClient = FakeMatrixClient().apply {
givenGetRoomResult(A_ROOM_ID, matrixRoom)
}
diff --git a/features/signedout/impl/src/main/res/values-pl/translations.xml b/features/signedout/impl/src/main/res/values-pl/translations.xml
new file mode 100644
index 0000000000..9b83e7d692
--- /dev/null
+++ b/features/signedout/impl/src/main/res/values-pl/translations.xml
@@ -0,0 +1,8 @@
+
+
+ "Zmieniono hasło w innej sesji"
+ "Sesja została usunięta z innej sesji"
+ "Administrator serwera unieważnił Twój dostęp"
+ "Mogłeś zostać wylogowany z powodów wymienionych poniżej. Zaloguj się ponownie, aby dalej korzystać z %s."
+ "Zostałeś wylogowany"
+
diff --git a/features/signedout/impl/src/main/res/values-pt-rBR/translations.xml b/features/signedout/impl/src/main/res/values-pt-rBR/translations.xml
new file mode 100644
index 0000000000..3c31806492
--- /dev/null
+++ b/features/signedout/impl/src/main/res/values-pt-rBR/translations.xml
@@ -0,0 +1,8 @@
+
+
+ "Você alterou sua senha em outra sessão"
+ "Você excluiu essa sessão através de outra sessão"
+ "O administrador do seu servidor invalidou seu acesso"
+ "Você pode ter sido desconectado por um dos motivos listados abaixo. Faça login novamente para continuar usando %s."
+ "Você está desconectado"
+
diff --git a/features/userprofile/shared/src/main/res/values-pl/translations.xml b/features/userprofile/shared/src/main/res/values-pl/translations.xml
new file mode 100644
index 0000000000..8ee9d296ac
--- /dev/null
+++ b/features/userprofile/shared/src/main/res/values-pl/translations.xml
@@ -0,0 +1,10 @@
+
+
+ "Zablokuj"
+ "Zablokowani użytkownicy nie będą mogli wysyłać Ci wiadomości, a wszystkie ich wiadomości zostaną ukryte. Możesz odblokować ich w dowolnym momencie."
+ "Zablokuj użytkownika"
+ "Odblokuj"
+ "Będziesz mógł ponownie zobaczyć wszystkie wiadomości od tego użytkownika."
+ "Odblokuj użytkownika"
+ "Wystąpił błąd podczas próby rozpoczęcia czatu"
+
diff --git a/features/userprofile/shared/src/main/res/values-pt-rBR/translations.xml b/features/userprofile/shared/src/main/res/values-pt-rBR/translations.xml
new file mode 100644
index 0000000000..d5a26a77ca
--- /dev/null
+++ b/features/userprofile/shared/src/main/res/values-pt-rBR/translations.xml
@@ -0,0 +1,10 @@
+
+
+ "Bloquear"
+ "Usuários bloqueados não poderão enviar mensagens para você e todas as mensagens deles serão ocultadas. Você pode desbloqueá-los a qualquer momento."
+ "Bloquear usuário"
+ "Desbloquear"
+ "Você poderá ver todas as mensagens deles novamente."
+ "Desbloquear usuário"
+ "Ocorreu um erro ao tentar iniciar um chat"
+
diff --git a/features/verifysession/impl/src/main/res/values-ka/translations.xml b/features/verifysession/impl/src/main/res/values-ka/translations.xml
index fb1f3c6db4..afd1b03a56 100644
--- a/features/verifysession/impl/src/main/res/values-ka/translations.xml
+++ b/features/verifysession/impl/src/main/res/values-ka/translations.xml
@@ -3,12 +3,15 @@
"რაღაცა არასწორადაა. ან მოთხოვნის ვადაა ამოწურული, ან მოთხოვნა უარყოფილი იყო."
"დაადასტურეთ, რომ ქვემოთ მოყვანილი ემოჯიები შეესაბამება თქვენს სხვა სესიაზე ნაჩვენებს."
"შეადარეთ ემოჯიები"
+ "დაადასტურეთ, რომ ქვემოთ მოცემული ნომრები ემთხვევა თქვენს სხვა სესიაზე ნაჩვენები ნომრებს."
+ "შეადარეთ რიცხვები"
"თქვენი ახალი სესია დადასტურებულია. მას აქვს წვდომა დაშიფრულ შეტყობინებებზე და სხვა მომხმარებლები მას სანდოდ ხედავენ."
"დაამტკიცეთ, რომ ეს თქვენ ხართ, რათა მიიღოთ წვდომა თქვენი დაშიფრული შეტყობინებების ისტორიასთან."
"არსებული სესიის გახსნა"
"დადასტურების ხელახლა ცდა"
"მზად ვარ"
"ველოდებით დამთხვევას"
+ "შეადარეთ ემოციების უნიკალური ნაკრები."
"შეადარეთ უნიკალური ემოჯი, დარწმუნდით, რომ ისინი ერთი დ იმავე თანმიმდევრობით გამოჩნდნენ."
"ისინი არ ემთხვევიან ერთმანეთს"
"ისინი ემთხვევიან ერთმანეთს"
diff --git a/features/verifysession/impl/src/main/res/values-pl/translations.xml b/features/verifysession/impl/src/main/res/values-pl/translations.xml
new file mode 100644
index 0000000000..6f39abf905
--- /dev/null
+++ b/features/verifysession/impl/src/main/res/values-pl/translations.xml
@@ -0,0 +1,20 @@
+
+
+ "Coś tu nie gra. Albo upłynął limit czasu, albo żądanie zostało odrzucone."
+ "Upewnij się, że poniższe emotikony pasują do tych wyświetlanych na innej sesji."
+ "Porównaj emotki"
+ "Upewnij się, że liczby poniżej pasują do tych wyświetlanych na innej sesji."
+ "Porównaj liczby"
+ "Twoja nowa sesja jest teraz zweryfikowana. Ma ona dostęp do Twoich zaszyfrowanych wiadomości, a inni użytkownicy będą widzieć ją jako zaufaną."
+ "Udowodnij, że to ty, aby uzyskać dostęp do historii zaszyfrowanych wiadomości."
+ "Otwórz istniejącą sesję"
+ "Ponów weryfikację"
+ "Jestem gotowy(a)"
+ "Oczekiwanie na dopasowanie"
+ "Porównaj unikalny zestaw emoji."
+ "Porównaj unikalne emoji, upewniając się, że pojawiły się w tej samej kolejności."
+ "Nie pasują do siebie"
+ "Pasują do siebie"
+ "Zaakceptuj prośbę o rozpoczęcie procesu weryfikacji w innej sesji, aby kontynuować."
+ "Oczekiwanie na zaakceptowanie żądania"
+
diff --git a/features/verifysession/impl/src/main/res/values-pt-rBR/translations.xml b/features/verifysession/impl/src/main/res/values-pt-rBR/translations.xml
new file mode 100644
index 0000000000..9d4658be82
--- /dev/null
+++ b/features/verifysession/impl/src/main/res/values-pt-rBR/translations.xml
@@ -0,0 +1,17 @@
+
+
+ "Algo não parece certo. Ou a solicitação atingiu o tempo limite ou a solicitação foi negada."
+ "Confirme se os emojis abaixo correspondem aos mostrados em sua outra sessão."
+ "Compare os emojis"
+ "Sua nova sessão está agora verificada. Ela tem acesso às suas mensagens criptografadas e outros usuários a verão como confiável."
+ "Prove que é você para acessar seu histórico de mensagens criptografadas."
+ "Abrir uma sessão existente"
+ "Repetir verificação"
+ "Estou pronto"
+ "Esperando para combinar"
+ "Compare os emojis únicos, garantindo que apareçam na mesma ordem."
+ "Eles não combinam"
+ "Eles combinam"
+ "Aceite a solicitação para iniciar o processo de verificação em sua outra sessão para continuar."
+ "Aguardando para aceitar a solicitação"
+
diff --git a/gradle.properties b/gradle.properties
index 9808f9eda1..b1f6468108 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -49,7 +49,7 @@ signing.element.nightly.keyPassword=Secret
# Customise the Lint version to use a more recent version than the one bundled with AGP
# https://googlesamples.github.io/android-custom-lint-rules/usage/newer-lint.md.html
-android.experimental.lint.version=8.5.0-alpha07
+android.experimental.lint.version=8.7.0-alpha01
# Enable test fixture for all modules by default
android.experimental.enableTestFixtures=true
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index e39b0297b6..a1fede3e54 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -45,7 +45,7 @@ showkase = "1.0.3"
appyx = "1.4.0"
sqldelight = "2.0.2"
wysiwyg = "2.37.7"
-telephoto = "0.12.0"
+telephoto = "0.12.1"
# DI
dagger = "2.51.1"
@@ -163,7 +163,7 @@ jsoup = "org.jsoup:jsoup:1.18.1"
appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" }
molecule-runtime = "app.cash.molecule:molecule-runtime:2.0.0"
timber = "com.jakewharton.timber:timber:5.0.1"
-matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.2.33"
+matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.2.34"
matrix_richtexteditor = { module = "io.element.android:wysiwyg", version.ref = "wysiwyg" }
matrix_richtexteditor_compose = { module = "io.element.android:wysiwyg-compose", version.ref = "wysiwyg" }
sqldelight-driver-android = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" }
diff --git a/libraries/androidutils/src/main/res/values-pl/translations.xml b/libraries/androidutils/src/main/res/values-pl/translations.xml
new file mode 100644
index 0000000000..6302f19472
--- /dev/null
+++ b/libraries/androidutils/src/main/res/values-pl/translations.xml
@@ -0,0 +1,4 @@
+
+
+ "Nie znaleziono kompatybilnej aplikacji do obsługi tej akcji."
+
diff --git a/libraries/androidutils/src/main/res/values-pt-rBR/translations.xml b/libraries/androidutils/src/main/res/values-pt-rBR/translations.xml
new file mode 100644
index 0000000000..cde5b743f6
--- /dev/null
+++ b/libraries/androidutils/src/main/res/values-pt-rBR/translations.xml
@@ -0,0 +1,4 @@
+
+
+ "Nenhum aplicativo compatível foi encontrado para lidar com essa ação."
+
diff --git a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatter.kt b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatter.kt
index 428e6d1da1..13655c48f2 100644
--- a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatter.kt
+++ b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatter.kt
@@ -85,7 +85,7 @@ class DefaultRoomLastMessageFormatter @Inject constructor(
}
}
is StickerContent -> {
- content.body
+ prefixIfNeeded(sp.getString(CommonStrings.common_sticker) + " (" + content.body + ")", senderDisambiguatedDisplayName, isDmRoom)
}
is UnableToDecryptContent -> {
val message = sp.getString(CommonStrings.common_waiting_for_decryption_key)
diff --git a/libraries/eventformatter/impl/src/main/res/values-pl/translations.xml b/libraries/eventformatter/impl/src/main/res/values-pl/translations.xml
new file mode 100644
index 0000000000..0c3bddc3c1
--- /dev/null
+++ b/libraries/eventformatter/impl/src/main/res/values-pl/translations.xml
@@ -0,0 +1,59 @@
+
+
+ "(zdjęcie profilowe też zostało zmienione)"
+ "%1$s zmienił swoje zdjęcie profilowe"
+ "Zmieniłeś swoje zdjęcie profilowe"
+ "%1$s zmienił swoją wyświetlaną nazwę z %2$s na %3$s"
+ "Zmieniłeś swoją wyświetlaną nazwę z %1$s na %2$s"
+ "%1$s usunął swoją wyświetlaną nazwę (byo to %2$s)"
+ "Usunąłeś swoją wyświetlaną nazwę (było to %1$s)"
+ "%1$s ustawił swoją wyświetlaną nazwę na %2$s"
+ "Ustawiłeś swoją wyświetlaną nazwę na %1$s"
+ "%1$s zmienił zdjęcie profilowe pokoju"
+ "Zmieniłeś zdjęcie profilowe pokoju"
+ "%1$s usunął zdjęcie profilowe pokoju"
+ "Usunąłeś zdjęcie profilowe pokoju"
+ "%1$s zbanował %2$s"
+ "Zbanowałeś %1$s"
+ "%1$s stworzył pokój"
+ "Stworzyłeś pokój"
+ "%1$s zaprosił %2$s"
+ "%1$s zaakceptował zaproszenie"
+ "Zaakceptowałeś zaproszenie"
+ "Zaprosiłeś %1$s"
+ "%1$s zaprosił Cię"
+ "%1$s dołączył do pokoju"
+ "Dołączyłeś(aś) do pokoju"
+ "%1$s prosi o możliwość dołączenia"
+ "%1$s zezwolił %2$s na dołączenie"
+ "Zezwoliłeś %1$s na dołączenie"
+ "Poprosiłeś o możliwość dołączenia"
+ "%1$s odrzucił prośbę %2$s o dołączenie"
+ "Odrzuciłeś prośbę %1$s o dołączenie"
+ "%1$s odrzucił Twoją prośbę o dołączenie"
+ "%1$s nie jest już zainteresowany dołączeniem"
+ "Anulowałeś prośbę o dołączenie"
+ "%1$s opuścił pokój"
+ "Opuściłeś pokój"
+ "%1$s zmienił nazwę pokoju na: %2$s"
+ "Zmieniłeś nazwę pokoju na: %1$s"
+ "%1$s usunął nazwę pokoju"
+ "Usunąłeś nazwę pokoju"
+ "%1$s nie wprowadził żadnych zmian"
+ "Nie wprowadzono żadnych zmian"
+ "%1$s odrzucił zaproszenie"
+ "Odrzuciłeś(aś) zaproszenie"
+ "%1$s usunął %2$s"
+ "Usunąłeś %1$s"
+ "%1$s wysłał zaproszenie do %2$s, aby dołączył do pokoju"
+ "Wysłano zaproszenie do %1$s, aby dołączył do pokoju"
+ "%1$s cofnął zaproszenie dla %2$s do tego pokoju"
+ "Odwołano zaproszenie %1$s, aby dołączył do pokoju"
+ "%1$s zmienił temat na: %2$s"
+ "Zmieniłeś temat na: %1$s"
+ "%1$s usunął temat pokoju"
+ "Usunąłeś temat pokoju"
+ "%1$s odbanował %2$s"
+ "Odbanowałeś %1$s"
+ "%1$s dokonał nieznanej zmiany w swoim członkostwie"
+
diff --git a/libraries/eventformatter/impl/src/main/res/values-pt-rBR/translations.xml b/libraries/eventformatter/impl/src/main/res/values-pt-rBR/translations.xml
new file mode 100644
index 0000000000..8cb375f1b7
--- /dev/null
+++ b/libraries/eventformatter/impl/src/main/res/values-pt-rBR/translations.xml
@@ -0,0 +1,57 @@
+
+
+ "(o avatar também foi alterado)"
+ "%1$s mudou seu avatar"
+ "Você mudou seu avatar"
+ "%1$s mudou seu nome de exibição de %2$s para %3$s"
+ "Você alterou seu nome de exibição de %1$s para %2$s"
+ "%1$s removeu seu nome de exibição (era %2$s)"
+ "Você removeu seu nome de exibição (era %1$s)"
+ "%1$s definiu seu nome de exibição como %2$s"
+ "Você definiu seu nome de exibição como %1$s"
+ "%1$s mudou o avatar da sala"
+ "Você mudou o avatar da sala"
+ "%1$s removeu o avatar da sala"
+ "Você removeu o avatar da sala"
+ "%1$s baniu %2$s"
+ "Você baniu %1$s"
+ "%1$s criou a sala"
+ "Você criou a sala"
+ "%1$s convidou %2$s"
+ "%1$s aceitou o convite"
+ "Você aceitou o convite"
+ "Você convidou %1$s"
+ "%1$s convidou você"
+ "%1$s entrou na sala"
+ "Você entrou na sala"
+ "%1$s solicitou entrada"
+ "%1$s permitiu que o %2$s entrar"
+ "Você permitiu que o %1$s entrasse"
+ "Você solicitou entrada"
+ "%1$s rejeitou a solicitação de %2$s para entrar"
+ "Você rejeitou a solicitação de %1$s para entrar"
+ "%1$s rejeitou sua solicitação para entrar"
+ "%1$s não está mais interessado em entrar"
+ "Você cancelou seu pedido para entrar"
+ "%1$s saiu da sala"
+ "Você saiu da sala"
+ "%1$s mudou o nome da sala para: %2$s"
+ "Você mudou o nome da sala para: %1$s"
+ "%1$s removeu o nome da sala"
+ "Você removeu o nome da sala"
+ "%1$s rejeitou o convite"
+ "Você rejeitou o convite"
+ "%1$s removido %2$s"
+ "Você removeu %1$s"
+ "%1$s enviou um convite para %2$s para entrar na sala"
+ "Você enviou um convite para %1$s para entrar na sala"
+ "%1$s revogou o convite para %2$s para entrar na sala"
+ "Você revogou o convite para %1$s para entrar na sala"
+ "%1$s mudou o tópico para: %2$s"
+ "Você mudou o tópico para: %1$s"
+ "%1$s removeu o tópico da sala"
+ "Você removeu o tópico da sala"
+ "%1$s desbaniu %2$s"
+ "Você desbaniu %1$s"
+ "%1$s fez uma alteração desconhecida em sua associação"
+
diff --git a/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatterTest.kt b/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatterTest.kt
index 5255fcea4a..5465882479 100644
--- a/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatterTest.kt
+++ b/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatterTest.kt
@@ -105,11 +105,12 @@ class DefaultRoomLastMessageFormatterTest {
@Test
@Config(qualifiers = "en")
fun `Sticker content`() {
- val body = "body"
+ val body = "a sticker body"
val info = ImageInfo(null, null, null, null, null, null, null)
val message = createRoomEvent(false, null, StickerContent(body, info, aMediaSource(url = "url")))
val result = formatter.format(message, false)
- assertThat(result).isEqualTo(body)
+ val expectedBody = someoneElseId.toString() + ": Sticker (a sticker body)"
+ assertThat(result.toString()).isEqualTo(expectedBody)
}
@Test
diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt
index 926df6ae21..378ada5b16 100644
--- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt
+++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt
@@ -126,6 +126,8 @@ interface MatrixRoom : Closeable {
suspend fun sendMessage(body: String, htmlBody: String?, mentions: List): Result
+ suspend fun editMessage(eventId: EventId, body: String, htmlBody: String?, mentions: List): Result
+
suspend fun sendImage(
file: File,
thumbnailFile: File?,
diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/TimelineException.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/TimelineException.kt
index e3970619cd..ba44de0ba3 100644
--- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/TimelineException.kt
+++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/TimelineException.kt
@@ -18,4 +18,5 @@ package io.element.android.libraries.matrix.api.timeline
sealed class TimelineException : Exception() {
data object CannotPaginate : TimelineException()
+ data object EventNotFound : TimelineException()
}
diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt
index 0a09ea005e..b594bba5e4 100644
--- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt
+++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt
@@ -56,6 +56,7 @@ import io.element.android.libraries.matrix.impl.room.member.RoomMemberMapper
import io.element.android.libraries.matrix.impl.room.powerlevels.RoomPowerLevelsMapper
import io.element.android.libraries.matrix.impl.timeline.RustTimeline
import io.element.android.libraries.matrix.impl.timeline.toRustReceiptType
+import io.element.android.libraries.matrix.impl.util.MessageEventContent
import io.element.android.libraries.matrix.impl.util.mxCallbackFlow
import io.element.android.libraries.matrix.impl.widget.RustWidgetDriver
import io.element.android.libraries.matrix.impl.widget.generateWidgetWebViewUrl
@@ -324,6 +325,14 @@ class RustMatrixRoom(
}
}
+ override suspend fun editMessage(eventId: EventId, body: String, htmlBody: String?, mentions: List): Result = withContext(roomDispatcher) {
+ runCatching {
+ MessageEventContent.from(body, htmlBody, mentions).use { newContent ->
+ innerRoom.edit(eventId.value, newContent)
+ }
+ }
+ }
+
override suspend fun sendMessage(body: String, htmlBody: String?, mentions: List): Result {
return liveTimeline.sendMessage(body, htmlBody, mentions)
}
diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt
index 00c79bf687..b57efc5603 100644
--- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt
+++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt
@@ -42,7 +42,6 @@ import io.element.android.libraries.matrix.impl.media.toMSC3246range
import io.element.android.libraries.matrix.impl.poll.toInner
import io.element.android.libraries.matrix.impl.room.RoomContentForwarder
import io.element.android.libraries.matrix.impl.room.location.toInner
-import io.element.android.libraries.matrix.impl.room.map
import io.element.android.libraries.matrix.impl.timeline.item.event.EventTimelineItemMapper
import io.element.android.libraries.matrix.impl.timeline.item.event.TimelineEventContentMapper
import io.element.android.libraries.matrix.impl.timeline.item.virtual.VirtualTimelineItemMapper
@@ -51,6 +50,7 @@ import io.element.android.libraries.matrix.impl.timeline.postprocessor.LoadingIn
import io.element.android.libraries.matrix.impl.timeline.postprocessor.RoomBeginningPostProcessor
import io.element.android.libraries.matrix.impl.timeline.postprocessor.TimelineEncryptedHistoryPostProcessor
import io.element.android.libraries.matrix.impl.timeline.reply.InReplyToMapper
+import io.element.android.libraries.matrix.impl.util.MessageEventContent
import io.element.android.services.toolbox.api.systemclock.SystemClock
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineDispatcher
@@ -70,12 +70,10 @@ import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
+import org.matrix.rustcomponents.sdk.EventTimelineItem
import org.matrix.rustcomponents.sdk.FormattedBody
import org.matrix.rustcomponents.sdk.MessageFormat
-import org.matrix.rustcomponents.sdk.RoomMessageEventContentWithoutRelation
import org.matrix.rustcomponents.sdk.SendAttachmentJoinHandle
-import org.matrix.rustcomponents.sdk.messageEventContentFromHtml
-import org.matrix.rustcomponents.sdk.messageEventContentFromMarkdown
import org.matrix.rustcomponents.sdk.use
import timber.log.Timber
import uniffi.matrix_sdk_ui.LiveBackPaginationStatus
@@ -266,7 +264,7 @@ class RustTimeline(
}
override suspend fun sendMessage(body: String, htmlBody: String?, mentions: List): Result = withContext(dispatcher) {
- messageEventContentFromParts(body, htmlBody).withMentions(mentions.map()).use { content ->
+ MessageEventContent.from(body, htmlBody, mentions).use { content ->
runCatching {
inner.send(content)
}
@@ -275,20 +273,8 @@ class RustTimeline(
override suspend fun redactEvent(eventId: EventId?, transactionId: TransactionId?, reason: String?): Result = withContext(dispatcher) {
runCatching {
- when {
- eventId != null -> {
- inner.getEventTimelineItemByEventId(eventId.value).use {
- inner.redactEvent(item = it, reason = reason)
- }
- }
- transactionId != null -> {
- inner.getEventTimelineItemByTransactionId(transactionId.value).use {
- inner.redactEvent(item = it, reason = reason)
- }
- }
- else -> {
- error("Either eventId or transactionId must be non-null")
- }
+ getEventTimelineItem(eventId, transactionId).use { item ->
+ inner.redactEvent(item = item, reason = reason)
}
}
}
@@ -302,26 +288,11 @@ class RustTimeline(
): Result =
withContext(dispatcher) {
runCatching {
- when {
- originalEventId != null -> {
- inner.getEventTimelineItemByEventId(originalEventId.value).use {
- inner.edit(
- newContent = messageEventContentFromParts(body, htmlBody).withMentions(mentions.map()),
- item = it,
- )
- }
- }
- transactionId != null -> {
- inner.getEventTimelineItemByTransactionId(transactionId.value).use {
- inner.edit(
- newContent = messageEventContentFromParts(body, htmlBody).withMentions(mentions.map()),
- item = it,
- )
- }
- }
- else -> {
- error("Either originalEventId or transactionId must be non null")
- }
+ getEventTimelineItem(originalEventId, transactionId).use { item ->
+ inner.edit(
+ newContent = MessageEventContent.from(body, htmlBody, mentions),
+ item = item,
+ )
}
}
}
@@ -334,7 +305,7 @@ class RustTimeline(
fromNotification: Boolean,
): Result = withContext(dispatcher) {
runCatching {
- val msg = messageEventContentFromParts(body, htmlBody).withMentions(mentions.map())
+ val msg = MessageEventContent.from(body, htmlBody, mentions)
inner.sendReply(msg, eventId.value)
}
}
@@ -361,6 +332,20 @@ class RustTimeline(
}
}
+ @Throws
+ private suspend fun getEventTimelineItem(eventId: EventId?, transactionId: TransactionId?): EventTimelineItem {
+ return try {
+ when {
+ eventId != null -> inner.getEventTimelineItemByEventId(eventId.value)
+ transactionId != null -> inner.getEventTimelineItemByTransactionId(transactionId.value)
+ else -> error("Either eventId or transactionId must be non-null")
+ }
+ } catch (e: Exception) {
+ Timber.e(e, "Failed to get event timeline item")
+ throw TimelineException.EventNotFound
+ }
+ }
+
override suspend fun sendVideo(
file: File,
thumbnailFile: File?,
@@ -517,13 +502,6 @@ class RustTimeline(
)
}
- private fun messageEventContentFromParts(body: String, htmlBody: String?): RoomMessageEventContentWithoutRelation =
- if (htmlBody != null) {
- messageEventContentFromHtml(body, htmlBody)
- } else {
- messageEventContentFromMarkdown(body)
- }
-
private fun sendAttachment(files: List, handle: () -> SendAttachmentJoinHandle): Result {
return runCatching {
MediaUploadHandlerImpl(files, handle())
diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/MessageEventContent.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/MessageEventContent.kt
new file mode 100644
index 0000000000..e1728bb528
--- /dev/null
+++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/MessageEventContent.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2024 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.libraries.matrix.impl.util
+
+import io.element.android.libraries.matrix.api.room.Mention
+import io.element.android.libraries.matrix.impl.room.map
+import org.matrix.rustcomponents.sdk.RoomMessageEventContentWithoutRelation
+import org.matrix.rustcomponents.sdk.messageEventContentFromHtml
+import org.matrix.rustcomponents.sdk.messageEventContentFromMarkdown
+
+/**
+ * Creates a [RoomMessageEventContentWithoutRelation] from a body, an html body and a list of mentions.
+ */
+object MessageEventContent {
+ fun from(body: String, htmlBody: String?, mentions: List): RoomMessageEventContentWithoutRelation {
+ return if (htmlBody != null) {
+ messageEventContentFromHtml(body, htmlBody)
+ } else {
+ messageEventContentFromMarkdown(body)
+ }.withMentions(mentions.map())
+ }
+}
diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClientProvider.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClientProvider.kt
index 53ebf00f41..de03a43df5 100644
--- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClientProvider.kt
+++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClientProvider.kt
@@ -21,7 +21,7 @@ import io.element.android.libraries.matrix.api.MatrixClientProvider
import io.element.android.libraries.matrix.api.core.SessionId
class FakeMatrixClientProvider(
- private val getClient: (SessionId) -> Result = { Result.success(FakeMatrixClient()) }
+ var getClient: (SessionId) -> Result = { Result.success(FakeMatrixClient()) }
) : MatrixClientProvider {
override suspend fun getOrRestore(sessionId: SessionId): Result = getClient(sessionId)
diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt
index d7a4b105b4..cfd0267516 100644
--- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt
+++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt
@@ -56,7 +56,7 @@ import io.element.android.libraries.matrix.test.A_SESSION_ID
import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler
import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService
import io.element.android.libraries.matrix.test.timeline.FakeTimeline
-import io.element.android.libraries.matrix.test.widget.FakeMatrixWidgetDriver
+import io.element.android.tests.testutils.lambda.lambdaError
import io.element.android.tests.testutils.simulateLongTask
import kotlinx.collections.immutable.ImmutableMap
import kotlinx.collections.immutable.persistentMapOf
@@ -84,126 +84,88 @@ class FakeMatrixRoom(
override val activeMemberCount: Long = 234L,
val notificationSettingsService: NotificationSettingsService = FakeNotificationSettingsService(),
override val liveTimeline: Timeline = FakeTimeline(),
- private var roomPermalinkResult: () -> Result = { Result.success("room link") },
- private var eventPermalinkResult: (EventId) -> Result = { Result.success("event link") },
- var sendCallNotificationIfNeededResult: () -> Result = { Result.success(Unit) },
- canRedactOwn: Boolean = false,
- canRedactOther: Boolean = false,
+ private var roomPermalinkResult: () -> Result = { lambdaError() },
+ private var eventPermalinkResult: (EventId) -> Result = { lambdaError() },
+ private val sendCallNotificationIfNeededResult: () -> Result = { lambdaError() },
+ private val userDisplayNameResult: () -> Result = { lambdaError() },
+ private val userAvatarUrlResult: () -> Result = { lambdaError() },
+ private val userRoleResult: () -> Result = { lambdaError() },
+ private val getUpdatedMemberResult: (UserId) -> Result = { lambdaError() },
+ private val joinRoomResult: () -> Result = { lambdaError() },
+ private val inviteUserResult: (UserId) -> Result = { lambdaError() },
+ private val canInviteResult: (UserId) -> Result = { lambdaError() },
+ private val canKickResult: (UserId) -> Result = { lambdaError() },
+ private val canBanResult: (UserId) -> Result = { lambdaError() },
+ private val canRedactOwnResult: (UserId) -> Result = { lambdaError() },
+ private val canRedactOtherResult: (UserId) -> Result = { lambdaError() },
+ private val canSendStateResult: (UserId, StateEventType) -> Result = { _, _ -> lambdaError() },
+ private val canUserSendMessageResult: (UserId, MessageEventType) -> Result = { _, _ -> lambdaError() },
+ private val sendMediaResult: (ProgressCallback?) -> Result = { lambdaError() },
+ private val setNameResult: (String) -> Result = { lambdaError() },
+ private val setTopicResult: (String) -> Result = { lambdaError() },
+ private val updateAvatarResult: (String, ByteArray) -> Result = { _, _ -> lambdaError() },
+ private val removeAvatarResult: () -> Result = { lambdaError() },
+ private val sendMessageResult: (String, String?, List) -> Result = { _, _, _ -> lambdaError() },
+ private val updateUserRoleResult: () -> Result = { lambdaError() },
+ private val toggleReactionResult: (String, EventId) -> Result = { _, _ -> lambdaError() },
+ private val retrySendMessageResult: (TransactionId) -> Result = { lambdaError() },
+ private val cancelSendResult: (TransactionId) -> Result = { lambdaError() },
+ private val forwardEventResult: (EventId, List) -> Result = { _, _ -> lambdaError() },
+ private val reportContentResult: (EventId, String, UserId?) -> Result = { _, _, _ -> lambdaError() },
+ private val kickUserResult: (UserId, String?) -> Result = { _, _ -> lambdaError() },
+ private val banUserResult: (UserId, String?) -> Result = { _, _ -> lambdaError() },
+ private val unBanUserResult: (UserId, String?) -> Result = { _, _ -> lambdaError() },
+ private val sendLocationResult: (String, String, String?, Int?, AssetType?) -> Result = { _, _, _, _, _ -> lambdaError() },
+ private val createPollResult: (String, List, Int, PollKind) -> Result = { _, _, _, _ -> lambdaError() },
+ private val editPollResult: (EventId, String, List, Int, PollKind) -> Result = { _, _, _, _, _ -> lambdaError() },
+ private val sendPollResponseResult: (EventId, List) -> Result = { _, _ -> lambdaError() },
+ private val endPollResult: (EventId, String) -> Result = { _, _ -> lambdaError() },
+ private val progressCallbackValues: List> = emptyList(),
+ private val generateWidgetWebViewUrlResult: (MatrixWidgetSettings, String, String?, String?) -> Result = { _, _, _, _ -> lambdaError() },
+ private val getWidgetDriverResult: (MatrixWidgetSettings) -> Result = { lambdaError() },
+ private val canUserTriggerRoomNotificationResult: (UserId) -> Result = { lambdaError() },
+ private val canUserJoinCallResult: (UserId) -> Result = { lambdaError() },
+ private val setIsFavoriteResult: (Boolean) -> Result = { lambdaError() },
+ private val powerLevelsResult: () -> Result = { lambdaError() },
+ private val updatePowerLevelsResult: () -> Result = { lambdaError() },
+ private val resetPowerLevelsResult: () -> Result = { lambdaError() },
+ private val typingNoticeResult: (Boolean) -> Result = { lambdaError() },
+ private val leaveRoomLambda: () -> Result = { lambdaError() },
+ private val updateMembersResult: () -> Unit = { lambdaError() },
+ private val getMembersResult: (Int) -> Result> = { lambdaError() },
+ private val timelineFocusedOnEventResult: (EventId) -> Result = { lambdaError() },
+ private val setSendQueueEnabledLambda: (Boolean) -> Unit = { _: Boolean -> },
+ private val saveComposerDraftLambda: (ComposerDraft) -> Result = { _: ComposerDraft -> Result.success(Unit) },
+ private val loadComposerDraftLambda: () -> Result = { Result.success(null) },
+ private val clearComposerDraftLambda: () -> Result = { Result.success(Unit) },
) : MatrixRoom {
- private var ignoreResult: Result = Result.success(Unit)
- private var unignoreResult: Result = Result.success(Unit)
- private var userDisplayNameResult = Result.success(null)
- private var userAvatarUrlResult = Result.success(null)
- private var userRoleResult = Result.success(RoomMember.Role.USER)
- private var getRoomMemberResult = Result.failure(IllegalStateException("Member not found"))
- private var joinRoomResult = Result.success(Unit)
- private var inviteUserResult = Result.success(Unit)
- private var canInviteResult = Result.success(true)
- private var canKickResult = Result.success(false)
- private var canBanResult = Result.success(false)
- private var canRedactOwnResult = Result.success(canRedactOwn)
- private var canRedactOtherResult = Result.success(canRedactOther)
- private val canSendStateResults = mutableMapOf>()
- private val canSendEventResults = mutableMapOf>()
- private var sendMediaResult = Result.success(FakeMediaUploadHandler())
- private var setNameResult = Result.success(Unit)
- private var setTopicResult = Result.success(Unit)
- private var updateAvatarResult = Result.success(Unit)
- private var removeAvatarResult = Result.success(Unit)
- private var updateUserRoleResult = Result.success(Unit)
- private var toggleReactionResult = Result.success(Unit)
- private var retrySendMessageResult = Result.success(Unit)
- private var cancelSendResult = Result.success(true)
- private var forwardEventResult = Result.success(Unit)
- private var reportContentResult = Result.success(Unit)
- private var kickUserResult = Result.success(Unit)
- private var banUserResult = Result.success(Unit)
- private var unBanUserResult = Result.success(Unit)
- private var sendLocationResult = Result.success(Unit)
- private var createPollResult = Result.success(Unit)
- private var editPollResult = Result.success(Unit)
- private var sendPollResponseResult = Result.success(Unit)
- private var endPollResult = Result.success(Unit)
- private var progressCallbackValues = emptyList>()
- private var generateWidgetWebViewUrlResult = Result.success("https://call.element.io")
- private var getWidgetDriverResult: Result = Result.success(FakeMatrixWidgetDriver())
- private var canUserTriggerRoomNotificationResult: Result = Result.success(true)
- private var canUserJoinCallResult: Result = Result.success(true)
- private var setIsFavoriteResult = Result.success(Unit)
- private var powerLevelsResult = Result.success(defaultRoomPowerLevels())
- private var updatePowerLevelsResult = Result.success(Unit)
- private var resetPowerLevelsResult = Result.success(defaultRoomPowerLevels())
- var sendMessageMentions = emptyList()
- private val _typingRecord = mutableListOf()
- val typingRecord: List
- get() = _typingRecord
-
- var sendMediaCount = 0
- private set
-
- private val _myReactions = mutableSetOf()
- val myReactions: Set = _myReactions
-
- var retrySendMessageCount: Int = 0
- private set
-
- var cancelSendCount: Int = 0
- private set
-
- var reportedContentCount: Int = 0
- private set
-
- private val _sentLocations = mutableListOf()
- val sentLocations: List = _sentLocations
-
- private val _createPollInvocations = mutableListOf()
- val createPollInvocations: List = _createPollInvocations
-
- private val _editPollInvocations = mutableListOf()
- val editPollInvocations: List = _editPollInvocations
-
- private val _sendPollResponseInvocations = mutableListOf()
- val sendPollResponseInvocations: List = _sendPollResponseInvocations
-
- private val _endPollInvocations = mutableListOf()
- val endPollInvocations: List = _endPollInvocations
-
- var invitedUserId: UserId? = null
- private set
-
- var newTopic: String? = null
- private set
-
- var newName: String? = null
- private set
-
- var newAvatarData: ByteArray? = null
- private set
-
- var removedAvatar: Boolean = false
- private set
-
- var leaveRoomLambda: (() -> Result) = { Result.success(Unit) }
-
private val _roomInfoFlow: MutableSharedFlow = MutableSharedFlow(replay = 1)
override val roomInfoFlow: Flow = _roomInfoFlow
+ fun givenRoomInfo(roomInfo: MatrixRoomInfo) {
+ _roomInfoFlow.tryEmit(roomInfo)
+ }
+
private val _roomTypingMembersFlow: MutableSharedFlow> = MutableSharedFlow(replay = 1)
override val roomTypingMembersFlow: Flow> = _roomTypingMembersFlow
+ fun givenRoomTypingMembers(typingMembers: List) {
+ _roomTypingMembersFlow.tryEmit(typingMembers)
+ }
+
override val membersStateFlow: MutableStateFlow = MutableStateFlow(MatrixRoomMembersState.Unknown)
override val roomNotificationSettingsStateFlow: MutableStateFlow =
MutableStateFlow(MatrixRoomNotificationSettingsState.Unknown)
- override suspend fun updateMembers() = Unit
+ override suspend fun updateMembers() = updateMembersResult()
override suspend fun getUpdatedMember(userId: UserId): Result {
- return getRoomMemberResult
+ return getUpdatedMemberResult(userId)
}
override suspend fun getMembers(limit: Int): Result> {
- return Result.success(emptyList())
+ return getMembersResult(limit)
}
override suspend fun updateRoomNotificationSettings(): Result = simulateLongTask {
@@ -214,76 +176,61 @@ class FakeMatrixRoom(
override val syncUpdateFlow: StateFlow = MutableStateFlow(0L)
- private var timelineFocusedOnEventResult: Result = Result.success(FakeTimeline())
-
- fun givenTimelineFocusedOnEventResult(result: Result) {
- timelineFocusedOnEventResult = result
- }
-
override suspend fun timelineFocusedOnEvent(eventId: EventId): Result = simulateLongTask {
- timelineFocusedOnEventResult
+ timelineFocusedOnEventResult(eventId)
}
override suspend fun subscribeToSync() = Unit
override suspend fun powerLevels(): Result {
- return powerLevelsResult
+ return powerLevelsResult()
}
override suspend fun updatePowerLevels(matrixRoomPowerLevels: MatrixRoomPowerLevels): Result = simulateLongTask {
- updatePowerLevelsResult
+ updatePowerLevelsResult()
}
override suspend fun resetPowerLevels(): Result = simulateLongTask {
- resetPowerLevelsResult
+ resetPowerLevelsResult()
}
override fun destroy() = Unit
override suspend fun userDisplayName(userId: UserId): Result = simulateLongTask {
- userDisplayNameResult
+ userDisplayNameResult()
}
override suspend fun userAvatarUrl(userId: UserId): Result = simulateLongTask {
- userAvatarUrlResult
+ userAvatarUrlResult()
}
override suspend fun userRole(userId: UserId): Result {
- return userRoleResult
+ return userRoleResult()
}
override suspend fun updateUsersRoles(changes: List): Result {
- return updateUserRoleResult
+ return updateUserRoleResult()
+ }
+
+ var editMessageLambda: (EventId, String, String?, List) -> Result = { _, _, _, _ -> lambdaError() }
+ override suspend fun editMessage(eventId: EventId, body: String, htmlBody: String?, mentions: List): Result {
+ return editMessageLambda(eventId, body, htmlBody, mentions)
}
override suspend fun sendMessage(body: String, htmlBody: String?, mentions: List) = simulateLongTask {
- sendMessageMentions = mentions
- Result.success(Unit)
+ sendMessageResult(body, htmlBody, mentions)
}
override suspend fun toggleReaction(emoji: String, eventId: EventId): Result {
- if (toggleReactionResult.isFailure) {
- // Don't do the toggle if we failed
- return toggleReactionResult
- }
-
- if (_myReactions.contains(emoji)) {
- _myReactions.remove(emoji)
- } else {
- _myReactions.add(emoji)
- }
-
- return toggleReactionResult
+ return toggleReactionResult(emoji, eventId)
}
override suspend fun retrySendMessage(transactionId: TransactionId): Result {
- retrySendMessageCount++
- return retrySendMessageResult
+ return retrySendMessageResult(transactionId)
}
override suspend fun cancelSend(transactionId: TransactionId): Result {
- cancelSendCount++
- return cancelSendResult
+ return cancelSendResult(transactionId)
}
override suspend fun getPermalink(): Result {
@@ -299,48 +246,47 @@ class FakeMatrixRoom(
}
override suspend fun join(): Result {
- return joinRoomResult
+ return joinRoomResult()
}
override suspend fun inviteUserById(id: UserId): Result = simulateLongTask {
- invitedUserId = id
- inviteUserResult
+ inviteUserResult(id)
}
override suspend fun canUserBan(userId: UserId): Result {
- return canBanResult
+ return canBanResult(userId)
}
override suspend fun canUserKick(userId: UserId): Result {
- return canKickResult
+ return canKickResult(userId)
}
override suspend fun canUserInvite(userId: UserId): Result {
- return canInviteResult
+ return canInviteResult(userId)
}
override suspend fun canUserRedactOwn(userId: UserId): Result {
- return canRedactOwnResult
+ return canRedactOwnResult(userId)
}
override suspend fun canUserRedactOther(userId: UserId): Result {
- return canRedactOtherResult
+ return canRedactOtherResult(userId)
}
override suspend fun canUserSendState(userId: UserId, type: StateEventType): Result {
- return canSendStateResults[type] ?: Result.failure(IllegalStateException("No fake answer"))
+ return canSendStateResult(userId, type)
}
override suspend fun canUserSendMessage(userId: UserId, type: MessageEventType): Result {
- return canSendEventResults[type] ?: Result.failure(IllegalStateException("No fake answer"))
+ return canUserSendMessageResult(userId, type)
}
override suspend fun canUserTriggerRoomNotification(userId: UserId): Result {
- return canUserTriggerRoomNotificationResult
+ return canUserTriggerRoomNotificationResult(userId)
}
override suspend fun canUserJoinCall(userId: UserId): Result {
- return canUserJoinCallResult
+ return canUserJoinCallResult(userId)
}
override suspend fun sendImage(
@@ -376,37 +322,31 @@ class FakeMatrixRoom(
): Result = fakeSendMedia(progressCallback)
override suspend fun forwardEvent(eventId: EventId, roomIds: List): Result = simulateLongTask {
- forwardEventResult
+ forwardEventResult(eventId, roomIds)
}
private suspend fun fakeSendMedia(progressCallback: ProgressCallback?): Result = simulateLongTask {
- sendMediaResult.onSuccess {
- progressCallbackValues.forEach { (current, total) ->
- progressCallback?.onProgress(current, total)
- delay(1)
- }
- sendMediaCount++
+ progressCallbackValues.forEach { (current, total) ->
+ progressCallback?.onProgress(current, total)
+ delay(1)
}
+ sendMediaResult(progressCallback)
}
override suspend fun updateAvatar(mimeType: String, data: ByteArray): Result = simulateLongTask {
- newAvatarData = data
- updateAvatarResult
+ updateAvatarResult(mimeType, data)
}
override suspend fun removeAvatar(): Result = simulateLongTask {
- removedAvatar = true
- removeAvatarResult
+ removeAvatarResult()
}
override suspend fun setName(name: String): Result = simulateLongTask {
- newName = name
- setNameResult
+ setNameResult(name)
}
override suspend fun setTopic(topic: String): Result = simulateLongTask {
- newTopic = topic
- setTopicResult
+ setTopicResult(topic)
}
override suspend fun reportContent(
@@ -414,28 +354,23 @@ class FakeMatrixRoom(
reason: String,
blockUserId: UserId?
): Result = simulateLongTask {
- reportedContentCount++
- return reportContentResult
+ return reportContentResult(eventId, reason, blockUserId)
}
override suspend fun kickUser(userId: UserId, reason: String?): Result {
- return kickUserResult
+ return kickUserResult(userId, reason)
}
override suspend fun banUser(userId: UserId, reason: String?): Result {
- return banUserResult
+ return banUserResult(userId, reason)
}
override suspend fun unbanUser(userId: UserId, reason: String?): Result {
- return unBanUserResult
+ return unBanUserResult(userId, reason)
}
- val setIsFavoriteCalls = mutableListOf()
-
override suspend fun setIsFavorite(isFavorite: Boolean): Result {
- return setIsFavoriteResult.also {
- setIsFavoriteCalls.add(isFavorite)
- }
+ return setIsFavoriteResult(isFavorite)
}
val markAsReadCalls = mutableListOf()
@@ -460,8 +395,13 @@ class FakeMatrixRoom(
zoomLevel: Int?,
assetType: AssetType?,
): Result = simulateLongTask {
- _sentLocations.add(SendLocationInvocation(body, geoUri, description, zoomLevel, assetType))
- return sendLocationResult
+ return sendLocationResult(
+ body,
+ geoUri,
+ description,
+ zoomLevel,
+ assetType,
+ )
}
override suspend fun createPoll(
@@ -470,8 +410,12 @@ class FakeMatrixRoom(
maxSelections: Int,
pollKind: PollKind
): Result = simulateLongTask {
- _createPollInvocations.add(SavePollInvocation(question, answers, maxSelections, pollKind))
- return createPollResult
+ return createPollResult(
+ question,
+ answers,
+ maxSelections,
+ pollKind,
+ )
}
override suspend fun editPoll(
@@ -481,24 +425,27 @@ class FakeMatrixRoom(
maxSelections: Int,
pollKind: PollKind
): Result = simulateLongTask {
- _editPollInvocations.add(SavePollInvocation(question, answers, maxSelections, pollKind))
- return editPollResult
+ return editPollResult(
+ pollStartId,
+ question,
+ answers,
+ maxSelections,
+ pollKind,
+ )
}
override suspend fun sendPollResponse(
pollStartId: EventId,
answers: List
): Result = simulateLongTask {
- _sendPollResponseInvocations.add(SendPollResponseInvocation(pollStartId, answers))
- return sendPollResponseResult
+ return sendPollResponseResult(pollStartId, answers)
}
override suspend fun endPoll(
pollStartId: EventId,
text: String
): Result = simulateLongTask {
- _endPollInvocations.add(EndPollInvocation(pollStartId, text))
- return endPollResult
+ return endPollResult(pollStartId, text)
}
override suspend fun sendVoiceMessage(
@@ -509,8 +456,7 @@ class FakeMatrixRoom(
): Result = fakeSendMedia(progressCallback)
override suspend fun typingNotice(isTyping: Boolean): Result {
- _typingRecord += isTyping
- return Result.success(Unit)
+ return typingNoticeResult(isTyping)
}
override suspend fun generateWidgetWebViewUrl(
@@ -518,228 +464,34 @@ class FakeMatrixRoom(
clientId: String,
languageTag: String?,
theme: String?,
- ): Result = generateWidgetWebViewUrlResult
+ ): Result = generateWidgetWebViewUrlResult(
+ widgetSettings,
+ clientId,
+ languageTag,
+ theme,
+ )
override suspend fun sendCallNotificationIfNeeded(): Result {
return sendCallNotificationIfNeededResult()
}
- var setSendQueueEnabledLambda = { _: Boolean -> }
override suspend fun setSendQueueEnabled(enabled: Boolean) = setSendQueueEnabledLambda(enabled)
- var saveComposerDraftLambda = { _: ComposerDraft -> Result.success(Unit) }
override suspend fun saveComposerDraft(composerDraft: ComposerDraft) = saveComposerDraftLambda(composerDraft)
- var loadComposerDraftLambda = { Result.success(null) }
override suspend fun loadComposerDraft() = loadComposerDraftLambda()
- var clearComposerDraftLambda = { Result.success(Unit) }
override suspend fun clearComposerDraft() = clearComposerDraftLambda()
- override fun getWidgetDriver(widgetSettings: MatrixWidgetSettings): Result = getWidgetDriverResult
+ override fun getWidgetDriver(widgetSettings: MatrixWidgetSettings): Result {
+ return getWidgetDriverResult(widgetSettings)
+ }
fun givenRoomMembersState(state: MatrixRoomMembersState) {
membersStateFlow.value = state
}
-
- fun givenGetRoomMemberResult(result: Result) {
- getRoomMemberResult = result
- }
-
- fun givenUserDisplayNameResult(displayName: Result) {
- userDisplayNameResult = displayName
- }
-
- fun givenUserAvatarUrlResult(avatarUrl: Result) {
- userAvatarUrlResult = avatarUrl
- }
-
- fun givenUserRoleResult(role: Result) {
- userRoleResult = role
- }
-
- fun givenUpdateUserRoleResult(result: Result) {
- updateUserRoleResult = result
- }
-
- fun givenJoinRoomResult(result: Result) {
- joinRoomResult = result
- }
-
- fun givenCanKickResult(result: Result) {
- canKickResult = result
- }
-
- fun givenCanBanResult(result: Result) {
- canBanResult = result
- }
-
- fun givenInviteUserResult(result: Result) {
- inviteUserResult = result
- }
-
- fun givenCanInviteResult(result: Result) {
- canInviteResult = result
- }
-
- fun givenCanSendStateResult(type: StateEventType, result: Result) {
- canSendStateResults[type] = result
- }
-
- fun givenCanSendEventResult(type: MessageEventType, result: Result) {
- canSendEventResults[type] = result
- }
-
- fun givenCanTriggerRoomNotification(result: Result) {
- canUserTriggerRoomNotificationResult = result
- }
-
- fun givenCanUserJoinCall(result: Result) {
- canUserJoinCallResult = result
- }
-
- fun givenIgnoreResult(result: Result) {
- ignoreResult = result
- }
-
- fun givenUnIgnoreResult(result: Result) {
- unignoreResult = result
- }
-
- fun givenSendMediaResult(result: Result) {
- sendMediaResult = result
- }
-
- fun givenUpdateAvatarResult(result: Result