Merge branch 'main' into wallet
# Conflicts: # features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt # features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNavigator.kt # features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt # features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt # features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt # features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/SuggestionsPickerView.kt # features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/SuggestionsProcessor.kt # features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/ThreadedMessagesNode.kt # features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/topbars/MessagesViewTopBar.kt # libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/ResolvedSuggestion.kt
This commit is contained in:
commit
0ef6b69a79
912 changed files with 17051 additions and 4425 deletions
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
|
|
@ -56,7 +56,7 @@ jobs:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2
|
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
|
||||||
with:
|
with:
|
||||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
- name: Assemble debug APKs
|
- name: Assemble debug APKs
|
||||||
|
|
|
||||||
2
.github/workflows/build_enterprise.yml
vendored
2
.github/workflows/build_enterprise.yml
vendored
|
|
@ -61,7 +61,7 @@ jobs:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2
|
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
|
||||||
with:
|
with:
|
||||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
- name: Assemble debug Gplay Enterprise APK
|
- name: Assemble debug Gplay Enterprise APK
|
||||||
|
|
|
||||||
6
.github/workflows/generate_github_pages.yml
vendored
6
.github/workflows/generate_github_pages.yml
vendored
|
|
@ -12,16 +12,18 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
# Skip in forks
|
# Skip in forks
|
||||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
- name: ⏬ Checkout with LFS
|
- name: ⏬ Checkout with LFS
|
||||||
uses: nschloe/action-cached-lfs-checkout@1c185ad576953eab13e35ffe1bffef437d97e9d2 # v1.2.4
|
uses: nschloe/action-cached-lfs-checkout@385a8ecc719e50b8c71af6ab01a624b486b7c3bc # v1.2.5
|
||||||
- name: Use JDK 21
|
- name: Use JDK 21
|
||||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2
|
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
|
||||||
with:
|
with:
|
||||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
- name: Set up Python 3.12
|
- name: Set up Python 3.12
|
||||||
|
|
|
||||||
2
.github/workflows/maestro-local.yml
vendored
2
.github/workflows/maestro-local.yml
vendored
|
|
@ -50,7 +50,7 @@ jobs:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2
|
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
|
||||||
with:
|
with:
|
||||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
- name: Assemble debug APK
|
- name: Assemble debug APK
|
||||||
|
|
|
||||||
6
.github/workflows/nightlyReports.yml
vendored
6
.github/workflows/nightlyReports.yml
vendored
|
|
@ -34,7 +34,7 @@ jobs:
|
||||||
swap-storage: false
|
swap-storage: false
|
||||||
|
|
||||||
- name: ⏬ Checkout with LFS
|
- name: ⏬ Checkout with LFS
|
||||||
uses: nschloe/action-cached-lfs-checkout@1c185ad576953eab13e35ffe1bffef437d97e9d2 # v1.2.4
|
uses: nschloe/action-cached-lfs-checkout@385a8ecc719e50b8c71af6ab01a624b486b7c3bc # v1.2.5
|
||||||
|
|
||||||
- name: Use JDK 21
|
- name: Use JDK 21
|
||||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||||
|
|
@ -43,7 +43,7 @@ jobs:
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
|
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2
|
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
|
||||||
with:
|
with:
|
||||||
cache-read-only: false
|
cache-read-only: false
|
||||||
|
|
||||||
|
|
@ -85,7 +85,7 @@ jobs:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2
|
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
|
||||||
with:
|
with:
|
||||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
- name: Dependency analysis
|
- name: Dependency analysis
|
||||||
|
|
|
||||||
12
.github/workflows/quality.yml
vendored
12
.github/workflows/quality.yml
vendored
|
|
@ -74,7 +74,7 @@ jobs:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2
|
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
|
||||||
with:
|
with:
|
||||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
- name: Set up Python 3.12
|
- name: Set up Python 3.12
|
||||||
|
|
@ -113,7 +113,7 @@ jobs:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2
|
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
|
||||||
with:
|
with:
|
||||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
- name: Run Konsist tests
|
- name: Run Konsist tests
|
||||||
|
|
@ -154,7 +154,7 @@ jobs:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2
|
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
|
||||||
with:
|
with:
|
||||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
- name: Run compose tests
|
- name: Run compose tests
|
||||||
|
|
@ -188,7 +188,7 @@ jobs:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2
|
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
|
||||||
with:
|
with:
|
||||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
- name: Build Gplay Debug
|
- name: Build Gplay Debug
|
||||||
|
|
@ -233,7 +233,7 @@ jobs:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2
|
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
|
||||||
with:
|
with:
|
||||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
- name: Run Detekt
|
- name: Run Detekt
|
||||||
|
|
@ -274,7 +274,7 @@ jobs:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2
|
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
|
||||||
with:
|
with:
|
||||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
- name: Run Ktlint check
|
- name: Run Ktlint check
|
||||||
|
|
|
||||||
6
.github/workflows/recordScreenshots.yml
vendored
6
.github/workflows/recordScreenshots.yml
vendored
|
|
@ -43,13 +43,13 @@ jobs:
|
||||||
labels: Record-Screenshots
|
labels: Record-Screenshots
|
||||||
- name: ⏬ Checkout with LFS (PR)
|
- name: ⏬ Checkout with LFS (PR)
|
||||||
if: github.event.label.name == 'Record-Screenshots'
|
if: github.event.label.name == 'Record-Screenshots'
|
||||||
uses: nschloe/action-cached-lfs-checkout@1c185ad576953eab13e35ffe1bffef437d97e9d2 # v1.2.4
|
uses: nschloe/action-cached-lfs-checkout@385a8ecc719e50b8c71af6ab01a624b486b7c3bc # v1.2.5
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.ref || github.ref }}
|
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.ref || github.ref }}
|
||||||
- name: ⏬ Checkout with LFS (Branch)
|
- name: ⏬ Checkout with LFS (Branch)
|
||||||
if: github.event_name == 'workflow_dispatch'
|
if: github.event_name == 'workflow_dispatch'
|
||||||
uses: nschloe/action-cached-lfs-checkout@1c185ad576953eab13e35ffe1bffef437d97e9d2 # v1.2.4
|
uses: nschloe/action-cached-lfs-checkout@385a8ecc719e50b8c71af6ab01a624b486b7c3bc # v1.2.5
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: ☕️ Use JDK 21
|
- name: ☕️ Use JDK 21
|
||||||
|
|
@ -59,7 +59,7 @@ jobs:
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
# Add gradle cache, this should speed up the process
|
# Add gradle cache, this should speed up the process
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2
|
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
|
||||||
with:
|
with:
|
||||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
- name: Record screenshots
|
- name: Record screenshots
|
||||||
|
|
|
||||||
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
|
|
@ -43,7 +43,7 @@ jobs:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2
|
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
|
||||||
- name: Create app bundle
|
- name: Create app bundle
|
||||||
env:
|
env:
|
||||||
ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }}
|
ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }}
|
||||||
|
|
@ -87,7 +87,7 @@ jobs:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2
|
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
|
||||||
- name: Create Enterprise app bundle
|
- name: Create Enterprise app bundle
|
||||||
env:
|
env:
|
||||||
ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }}
|
ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }}
|
||||||
|
|
@ -131,7 +131,7 @@ jobs:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2
|
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
|
||||||
- name: Create APKs
|
- name: Create APKs
|
||||||
env:
|
env:
|
||||||
ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }}
|
ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,9 @@ adb install -r $1
|
||||||
echo "Starting the screen recording..."
|
echo "Starting the screen recording..."
|
||||||
adb push .github/workflows/scripts/maestro/local-recording.sh /data/local/tmp/
|
adb push .github/workflows/scripts/maestro/local-recording.sh /data/local/tmp/
|
||||||
adb shell "chmod +x /data/local/tmp/local-recording.sh"
|
adb shell "chmod +x /data/local/tmp/local-recording.sh"
|
||||||
|
mkdir -p ~/.maestro/tests
|
||||||
|
# Start logcat in the background and save the output to a file, use `org.matrix.rust.sdk` tag since the SDK handles the logging
|
||||||
|
adb logcat 'org.matrix.rust.sdk:D *:S' > ~/.maestro/tests/logcat.txt &
|
||||||
adb shell "/data/local/tmp/local-recording.sh & echo \$! > /data/local/tmp/screenrecord_pid.txt" &
|
adb shell "/data/local/tmp/local-recording.sh & echo \$! > /data/local/tmp/screenrecord_pid.txt" &
|
||||||
set +e
|
set +e
|
||||||
~/.maestro/bin/maestro test .maestro/allTests.yaml
|
~/.maestro/bin/maestro test .maestro/allTests.yaml
|
||||||
|
|
|
||||||
2
.github/workflows/sonar.yml
vendored
2
.github/workflows/sonar.yml
vendored
|
|
@ -50,7 +50,7 @@ jobs:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2
|
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
|
||||||
with:
|
with:
|
||||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
- name: Build debug code and test fixtures
|
- name: Build debug code and test fixtures
|
||||||
|
|
|
||||||
2
.github/workflows/sync-localazy.yml
vendored
2
.github/workflows/sync-localazy.yml
vendored
|
|
@ -22,7 +22,7 @@ jobs:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2
|
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
|
||||||
with:
|
with:
|
||||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
- name: Set up Python 3.12
|
- name: Set up Python 3.12
|
||||||
|
|
|
||||||
6
.github/workflows/tests.yml
vendored
6
.github/workflows/tests.yml
vendored
|
|
@ -49,7 +49,7 @@ jobs:
|
||||||
sudo swapon /mnt/swapfile
|
sudo swapon /mnt/swapfile
|
||||||
sudo swapon --show
|
sudo swapon --show
|
||||||
- name: ⏬ Checkout with LFS
|
- name: ⏬ Checkout with LFS
|
||||||
uses: nschloe/action-cached-lfs-checkout@1c185ad576953eab13e35ffe1bffef437d97e9d2 # v1.2.4
|
uses: nschloe/action-cached-lfs-checkout@385a8ecc719e50b8c71af6ab01a624b486b7c3bc # v1.2.5
|
||||||
with:
|
with:
|
||||||
# Ensure we are building the branch and not the branch after being merged on develop
|
# Ensure we are building the branch and not the branch after being merged on develop
|
||||||
# https://github.com/actions/checkout/issues/881
|
# https://github.com/actions/checkout/issues/881
|
||||||
|
|
@ -68,7 +68,7 @@ jobs:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2
|
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
|
||||||
with:
|
with:
|
||||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
|
|
||||||
|
|
@ -108,7 +108,7 @@ jobs:
|
||||||
|
|
||||||
# https://github.com/codecov/codecov-action
|
# https://github.com/codecov/codecov-action
|
||||||
- name: ☂️ Upload coverage reports to codecov
|
- name: ☂️ Upload coverage reports to codecov
|
||||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
|
||||||
with:
|
with:
|
||||||
fail_ci_if_error: true
|
fail_ci_if_error: true
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
|
|
||||||
2
.github/workflows/validate-lfs.yml
vendored
2
.github/workflows/validate-lfs.yml
vendored
|
|
@ -9,7 +9,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
name: Validate
|
name: Validate
|
||||||
steps:
|
steps:
|
||||||
- uses: nschloe/action-cached-lfs-checkout@1c185ad576953eab13e35ffe1bffef437d97e9d2 # v1.2.4
|
- uses: nschloe/action-cached-lfs-checkout@385a8ecc719e50b8c71af6ab01a624b486b7c3bc # v1.2.5
|
||||||
|
|
||||||
- run: |
|
- run: |
|
||||||
./tools/git/validate_lfs.sh
|
./tools/git/validate_lfs.sh
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,14 @@ appId: ${MAESTRO_APP_ID}
|
||||||
---
|
---
|
||||||
- tapOn:
|
- tapOn:
|
||||||
id: "home_screen-settings"
|
id: "home_screen-settings"
|
||||||
- tapOn: "Sign out"
|
- tapOn: "Remove this device"
|
||||||
- takeScreenshot: build/maestro/900-SignOutScreen
|
- takeScreenshot: build/maestro/900-SignOutScreen
|
||||||
- back
|
- back
|
||||||
- tapOn: "Sign out"
|
- tapOn: "Remove this device"
|
||||||
# Ensure cancel cancels
|
# Ensure cancel cancels
|
||||||
- tapOn:
|
- tapOn:
|
||||||
id: "dialog-negative"
|
id: "dialog-negative"
|
||||||
- tapOn: "Sign out"
|
- tapOn: "Remove this device"
|
||||||
- tapOn:
|
- tapOn:
|
||||||
id: "dialog-positive"
|
id: "dialog-positive"
|
||||||
- runFlow: ../assertions/assertInitDisplayed.yaml
|
- runFlow: ../assertions/assertInitDisplayed.yaml
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
appId: ${MAESTRO_APP_ID}
|
appId: ${MAESTRO_APP_ID}
|
||||||
---
|
---
|
||||||
- extendedWaitUntil:
|
- extendedWaitUntil:
|
||||||
visible: "Confirm your identity"
|
visible: "Confirm your digital identity"
|
||||||
timeout: 60000
|
timeout: 60000
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
appId: ${MAESTRO_APP_ID}
|
appId: ${MAESTRO_APP_ID}
|
||||||
---
|
---
|
||||||
# Purpose: Test the creation and deletion of a DM room.
|
# Purpose: Test the creation and deletion of a DM room.
|
||||||
- tapOn: "Create a new conversation or room"
|
- tapOn: "Create room"
|
||||||
- tapOn: "Search for someone"
|
- tapOn: "Search for someone"
|
||||||
- inputText: ${MAESTRO_INVITEE1_MXID}
|
- inputText: ${MAESTRO_INVITEE1_MXID}
|
||||||
- tapOn:
|
- tapOn:
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
appId: ${MAESTRO_APP_ID}
|
appId: ${MAESTRO_APP_ID}
|
||||||
---
|
---
|
||||||
# Purpose: Test the creation and deletion of a room
|
# Purpose: Test the creation and deletion of a room
|
||||||
- tapOn: "Create a new conversation or room"
|
- tapOn: "Create room"
|
||||||
- tapOn: "New room"
|
- tapOn: "New room"
|
||||||
- tapOn: "Add name…"
|
- tapOn: "Add name…"
|
||||||
- inputText: "aRoomName"
|
- inputText: "aRoomName"
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,6 @@ appId: ${MAESTRO_APP_ID}
|
||||||
---
|
---
|
||||||
- takeScreenshot: build/maestro/520-Timeline
|
- takeScreenshot: build/maestro/520-Timeline
|
||||||
- tapOn: "Add attachment"
|
- tapOn: "Add attachment"
|
||||||
- tapOn: "Location"
|
- tapOn: "Share location"
|
||||||
- tapOn: "Share my location"
|
- tapOn: "Share selected location"
|
||||||
- takeScreenshot: build/maestro/521-Timeline
|
- takeScreenshot: build/maestro/521-Timeline
|
||||||
|
|
|
||||||
139
CHANGES.md
139
CHANGES.md
|
|
@ -1,3 +1,142 @@
|
||||||
|
Changes in Element X v26.04.3
|
||||||
|
=============================
|
||||||
|
|
||||||
|
<!-- Release notes generated using configuration in .github/release.yml at v26.04.3 -->
|
||||||
|
|
||||||
|
## What's Changed
|
||||||
|
### ✨ Features
|
||||||
|
* Sign in with element classic final by @bmarty in https://github.com/element-hq/element-x-android/pull/6296
|
||||||
|
* Take into account homeserver capabilities by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6507
|
||||||
|
### 🙌 Improvements
|
||||||
|
* feat: Default to camera muted when joining ongoing voice call by @BillCarsonFr in https://github.com/element-hq/element-x-android/pull/6574
|
||||||
|
### 🐛 Bugfixes
|
||||||
|
* Fix crash in FetchPushForegroundService: No super method onTimeout by @bmarty in https://github.com/element-hq/element-x-android/pull/6547
|
||||||
|
* Ensure mark as fully read is called only once when leaving the timeline by @bmarty in https://github.com/element-hq/element-x-android/pull/6550
|
||||||
|
* Fix `isInAirGappedEnvironment` check for older APIs by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6573
|
||||||
|
* Fix loading initial items of non-live timelines by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6598
|
||||||
|
### 🗣 Translations
|
||||||
|
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/6537
|
||||||
|
* Sync Strings - new translations in Japanese and Vietnamese by @ElementBot in https://github.com/element-hq/element-x-android/pull/6568
|
||||||
|
### 🧱 Build
|
||||||
|
* Fix module dependencies by @bmarty in https://github.com/element-hq/element-x-android/pull/6559
|
||||||
|
### 🚧 In development 🚧
|
||||||
|
* Add confirmation dialog when inviting users with unknown identities by @kaylendog in https://github.com/element-hq/element-x-android/pull/6523
|
||||||
|
* Feature: add room threads list by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6575
|
||||||
|
### Dependency upgrades
|
||||||
|
* fix(deps): update media3 to v1.10.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6529
|
||||||
|
* fix(deps): update dependency io.github.sergio-sastre.composablepreviewscanner:android to v0.8.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6525
|
||||||
|
* fix(deps): update metro to v0.12.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6543
|
||||||
|
* fix(deps): update kotlinpoet to v2.3.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6528
|
||||||
|
* Sync compound tokens https://github.com/element-hq/compound-design-tokens/releases/tag/v10.0.0 by @bmarty in https://github.com/element-hq/element-x-android/pull/6517
|
||||||
|
* fix(deps): update dependency io.sentry:sentry-android to v8.37.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6508
|
||||||
|
* fix(deps): update dependency org.maplibre.gl:android-sdk to v13.0.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6546
|
||||||
|
* Update codecov/codecov-action action to v6 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6521
|
||||||
|
* Update telephoto to v0.19.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6558
|
||||||
|
* Update dependency net.zetetic:sqlcipher-android to v4.14.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6552
|
||||||
|
* Update dependency org.matrix.rustcomponents:sdk-android to v26.04.8 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6553
|
||||||
|
* Update gradle/actions action to v6.1.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6562
|
||||||
|
* Update metro to v0.13.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6565
|
||||||
|
* Update dependency org.matrix.rustcomponents:sdk-android to v26.04.13 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6570
|
||||||
|
* Update wysiwyg to v2.41.3 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6572
|
||||||
|
* Update dependency com.google.testparameterinjector:test-parameter-injector to v1.22 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6576
|
||||||
|
* Use `Coil3` for `ZoomableAsyncImage` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6582
|
||||||
|
* Update dependency org.matrix.rustcomponents:sdk-android to v26.04.15 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6595
|
||||||
|
* Update nschloe/action-cached-lfs-checkout action to v1.2.5 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6600
|
||||||
|
### Others
|
||||||
|
* Fix portrait image metadata when uploading without media optimization by @kalix127 in https://github.com/element-hq/element-x-android/pull/6362
|
||||||
|
* Fix Threads not tappable in pinned messages list by @bxdxnn in https://github.com/element-hq/element-x-android/pull/6535
|
||||||
|
* Reduce log level of activity lifecycle from warning to debug. by @bmarty in https://github.com/element-hq/element-x-android/pull/6548
|
||||||
|
* Remove spaces features flags by @bmarty in https://github.com/element-hq/element-x-android/pull/6560
|
||||||
|
* Remove space announcement by @bmarty in https://github.com/element-hq/element-x-android/pull/6561
|
||||||
|
* Update metro to v0.13.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6571
|
||||||
|
* Take into account the value of FeatureFlags.SignInWithClassic by @bmarty in https://github.com/element-hq/element-x-android/pull/6586
|
||||||
|
* Add extra logs for timeline pagination by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6589
|
||||||
|
* Scrollable media caption - tweaks by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6583
|
||||||
|
* Split developer settings into 2 screens to be able to access global settings when no logged in. by @bmarty in https://github.com/element-hq/element-x-android/pull/6587
|
||||||
|
|
||||||
|
|
||||||
|
**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v26.04.2...v26.04.3
|
||||||
|
|
||||||
|
Changes in Element X v26.04.2
|
||||||
|
=============================
|
||||||
|
|
||||||
|
## What's Changed
|
||||||
|
### 🐛 Bugfixes
|
||||||
|
* Restore enterprise submodule. by @bmarty in https://github.com/element-hq/element-x-android/pull/6541
|
||||||
|
### Dependency upgrades
|
||||||
|
* fix(deps): update dependency io.element.android:element-call-embedded to v0.19.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6538
|
||||||
|
|
||||||
|
**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v26.04.1...v26.04.2
|
||||||
|
|
||||||
|
Changes in Element X v26.04.1
|
||||||
|
=============================
|
||||||
|
|
||||||
|
## What's Changed
|
||||||
|
### ✨ Features
|
||||||
|
* Add support for slash commands (under Feature Flag) by @bmarty in https://github.com/element-hq/element-x-android/pull/6482
|
||||||
|
### Dependency upgrades
|
||||||
|
* chore(deps): update gradle/actions action to v6 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6489
|
||||||
|
* fix(deps): update dependency androidx.work:work-runtime-ktx to v2.11.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6479
|
||||||
|
* fix(deps): update dependency net.zetetic:sqlcipher-android to v4.14.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6460
|
||||||
|
* fix(deps): update metro to v0.12.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6503
|
||||||
|
* fix(deps): update dependency androidx.compose:compose-bom to v2026.03.01 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6511
|
||||||
|
* fix(deps): update dependency org.jetbrains.kotlinx:kover-gradle-plugin to v0.9.8 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6513
|
||||||
|
* fix(deps): update dependency androidx.browser:browser to v1.10.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6515
|
||||||
|
* fix(deps): update dependency io.element.android:emojibase-bindings to v1.5.3 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6493
|
||||||
|
* fix(deps): update core to v1.18.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6328
|
||||||
|
### Others
|
||||||
|
* Tentative fix for `ForegroundServiceStartNotAllowedException` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6509
|
||||||
|
* Fix a missing : in build-rust-sdk by @andybalaam in https://github.com/element-hq/element-x-android/pull/6522
|
||||||
|
|
||||||
|
|
||||||
|
**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v26.04.0...v26.04.1
|
||||||
|
|
||||||
|
Changes in Element X v26.04.0
|
||||||
|
=============================
|
||||||
|
|
||||||
|
## What's Changed
|
||||||
|
### ✨ Features
|
||||||
|
* Add floating/sticky date badge in the timeline by @kalix127 in https://github.com/element-hq/element-x-android/pull/6496
|
||||||
|
### 🐛 Bugfixes
|
||||||
|
* Fix `ForegroundServiceDidNotStartInTimeException` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6470
|
||||||
|
* Fix media cover placeholder floating by @bxdxnn in https://github.com/element-hq/element-x-android/pull/6484
|
||||||
|
* Try handling `ForegroundServiceStartNotAllowedException` better by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6483
|
||||||
|
* Fix crash when using `View.hideKeyboardAndAwaitAnimation` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6502
|
||||||
|
* Fix content scrolling not working in the RTE by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6492
|
||||||
|
### 🗣 Translations
|
||||||
|
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/6486
|
||||||
|
### 🧱 Build
|
||||||
|
* Add instructions for AI by @bmarty in https://github.com/element-hq/element-x-android/pull/6468
|
||||||
|
* Fix permissions to publish GitHub pages. by @bmarty in https://github.com/element-hq/element-x-android/pull/6500
|
||||||
|
* Try fixing location pin previews by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6495
|
||||||
|
* CI: yet another Maestro fix by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6505
|
||||||
|
### 📄 Documentation
|
||||||
|
* Add some instructions for features to the community PR notice message by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6465
|
||||||
|
### 🚧 In development 🚧
|
||||||
|
* Setup live location sharing feature by @ganfra in https://github.com/element-hq/element-x-android/pull/6342
|
||||||
|
### Dependency upgrades
|
||||||
|
* Update dependency io.sentry:sentry-android to v8.36.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6461
|
||||||
|
* Update metro to v0.11.4 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6448
|
||||||
|
* Sync compound tokens https://github.com/element-hq/compound-design-tokens/releases/tag/v8.0.0 by @bmarty in https://github.com/element-hq/element-x-android/pull/6459
|
||||||
|
* Update sqldelight to v2.3.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6449
|
||||||
|
* Update nschloe/action-cached-lfs-checkout action to v1.2.4 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6442
|
||||||
|
* Update kotlin to v2.3.20 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6437
|
||||||
|
* Update dependency io.element.android:element-call-embedded to v0.18.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6358
|
||||||
|
* fix(deps): update dependency io.element.android:emojibase-bindings to v1.5.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6474
|
||||||
|
* fix(deps): update dependency com.google.firebase:firebase-bom to v34.11.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6478
|
||||||
|
* fix(deps): update dependency io.element.android:emojibase-bindings to v1.5.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6487
|
||||||
|
* fix(deps): update dependency com.google.crypto.tink:tink-android to v1.21.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6499
|
||||||
|
* fix(deps): update dependency com.posthog:posthog-android to v3.39.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6504
|
||||||
|
* fix(deps): update dependency org.matrix.rustcomponents:sdk-android to v26.03.31 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6494
|
||||||
|
### Others
|
||||||
|
* Iterate on space header by @bmarty in https://github.com/element-hq/element-x-android/pull/6456
|
||||||
|
* Add margin after bullet points by @bxdxnn in https://github.com/element-hq/element-x-android/pull/6446
|
||||||
|
* chore: update the build-rust-sdk script by @bnjbvr in https://github.com/element-hq/element-x-android/pull/6476
|
||||||
|
* Update replied message UI by @bmarty in https://github.com/element-hq/element-x-android/pull/6472
|
||||||
|
|
||||||
|
|
||||||
|
**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v26.03.4...v26.04.0
|
||||||
|
|
||||||
Changes in Element X v26.03.4
|
Changes in Element X v26.03.4
|
||||||
=============================
|
=============================
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ class MainActivity : NodeActivity() {
|
||||||
private lateinit var appBindings: AppBindings
|
private lateinit var appBindings: AppBindings
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
Timber.tag(loggerTag.value).w("onCreate, with savedInstanceState: ${savedInstanceState != null}")
|
Timber.tag(loggerTag.value).d("onCreate, with savedInstanceState: ${savedInstanceState != null}")
|
||||||
installSplashScreen()
|
installSplashScreen()
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
appBindings = bindings()
|
appBindings = bindings()
|
||||||
|
|
@ -108,7 +108,7 @@ class MainActivity : NodeActivity() {
|
||||||
plugins = listOf(
|
plugins = listOf(
|
||||||
object : NodeReadyObserver<MainNode> {
|
object : NodeReadyObserver<MainNode> {
|
||||||
override fun init(node: MainNode) {
|
override fun init(node: MainNode) {
|
||||||
Timber.tag(loggerTag.value).w("onMainNodeInit")
|
Timber.tag(loggerTag.value).d("onMainNodeInit")
|
||||||
mainNode = node
|
mainNode = node
|
||||||
mainNode.handleIntent(intent)
|
mainNode.handleIntent(intent)
|
||||||
}
|
}
|
||||||
|
|
@ -144,7 +144,7 @@ class MainActivity : NodeActivity() {
|
||||||
*/
|
*/
|
||||||
override fun onNewIntent(intent: Intent) {
|
override fun onNewIntent(intent: Intent) {
|
||||||
super.onNewIntent(intent)
|
super.onNewIntent(intent)
|
||||||
Timber.tag(loggerTag.value).w("onNewIntent")
|
Timber.tag(loggerTag.value).d("onNewIntent")
|
||||||
// If the mainNode is not init yet, keep the intent for later.
|
// If the mainNode is not init yet, keep the intent for later.
|
||||||
// It can happen when the activity is killed by the system. The methods are called in this order :
|
// It can happen when the activity is killed by the system. The methods are called in this order :
|
||||||
// onCreate(savedInstanceState=true) -> onNewIntent -> onResume -> onMainNodeInit
|
// onCreate(savedInstanceState=true) -> onNewIntent -> onResume -> onMainNodeInit
|
||||||
|
|
@ -157,16 +157,16 @@ class MainActivity : NodeActivity() {
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
super.onPause()
|
super.onPause()
|
||||||
Timber.tag(loggerTag.value).w("onPause")
|
Timber.tag(loggerTag.value).d("onPause")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
Timber.tag(loggerTag.value).w("onResume")
|
Timber.tag(loggerTag.value).d("onResume")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
Timber.tag(loggerTag.value).w("onDestroy")
|
Timber.tag(loggerTag.value).d("onDestroy")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@
|
||||||
<locale android:name="hu"/>
|
<locale android:name="hu"/>
|
||||||
<locale android:name="in"/>
|
<locale android:name="in"/>
|
||||||
<locale android:name="it"/>
|
<locale android:name="it"/>
|
||||||
|
<locale android:name="ja"/>
|
||||||
<locale android:name="ka"/>
|
<locale android:name="ka"/>
|
||||||
<locale android:name="ko"/>
|
<locale android:name="ko"/>
|
||||||
<locale android:name="lt"/>
|
<locale android:name="lt"/>
|
||||||
|
|
@ -35,6 +36,7 @@
|
||||||
<locale android:name="uk"/>
|
<locale android:name="uk"/>
|
||||||
<locale android:name="ur"/>
|
<locale android:name="ur"/>
|
||||||
<locale android:name="uz"/>
|
<locale android:name="uz"/>
|
||||||
|
<locale android:name="vi"/>
|
||||||
<locale android:name="zh-CN"/>
|
<locale android:name="zh-CN"/>
|
||||||
<locale android:name="zh-TW"/>
|
<locale android:name="zh-TW"/>
|
||||||
</locale-config>
|
</locale-config>
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ object TimelineConfig {
|
||||||
*/
|
*/
|
||||||
val excludedEvents = listOf(
|
val excludedEvents = listOf(
|
||||||
StateEventType.CallMember,
|
StateEventType.CallMember,
|
||||||
StateEventType.RoomAliases,
|
|
||||||
StateEventType.RoomCanonicalAlias,
|
StateEventType.RoomCanonicalAlias,
|
||||||
StateEventType.RoomGuestAccess,
|
StateEventType.RoomGuestAccess,
|
||||||
StateEventType.RoomHistoryVisibility,
|
StateEventType.RoomHistoryVisibility,
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ dependencies {
|
||||||
implementation(projects.features.linknewdevice.api)
|
implementation(projects.features.linknewdevice.api)
|
||||||
implementation(projects.features.share.api)
|
implementation(projects.features.share.api)
|
||||||
|
|
||||||
implementation(projects.services.apperror.impl)
|
implementation(projects.services.apperror.api)
|
||||||
implementation(projects.services.appnavstate.api)
|
implementation(projects.services.appnavstate.api)
|
||||||
implementation(projects.services.analytics.api)
|
implementation(projects.services.analytics.api)
|
||||||
|
|
||||||
|
|
@ -67,8 +67,7 @@ dependencies {
|
||||||
testImplementation(projects.features.messages.test)
|
testImplementation(projects.features.messages.test)
|
||||||
testImplementation(projects.features.networkmonitor.test)
|
testImplementation(projects.features.networkmonitor.test)
|
||||||
testImplementation(projects.features.rageshake.test)
|
testImplementation(projects.features.rageshake.test)
|
||||||
testImplementation(projects.services.appnavstate.impl)
|
testImplementation(projects.services.apperror.test)
|
||||||
testImplementation(projects.services.appnavstate.test)
|
testImplementation(projects.services.appnavstate.test)
|
||||||
testImplementation(projects.services.analytics.test)
|
testImplementation(projects.services.analytics.test)
|
||||||
testImplementation(projects.services.toolbox.test)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -422,6 +422,10 @@ class LoggedInFlowNode(
|
||||||
override fun navigateToGlobalNotificationSettings() {
|
override fun navigateToGlobalNotificationSettings() {
|
||||||
backstack.push(NavTarget.Settings(PreferencesEntryPoint.InitialTarget.NotificationSettings))
|
backstack.push(NavTarget.Settings(PreferencesEntryPoint.InitialTarget.NotificationSettings))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun navigateToDeveloperSettings() {
|
||||||
|
backstack.push(NavTarget.Settings(PreferencesEntryPoint.InitialTarget.DeveloperSettings))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
val inputs = RoomFlowNode.Inputs(
|
val inputs = RoomFlowNode.Inputs(
|
||||||
roomIdOrAlias = navTarget.roomIdOrAlias,
|
roomIdOrAlias = navTarget.roomIdOrAlias,
|
||||||
|
|
|
||||||
|
|
@ -252,7 +252,8 @@ class RootFlowNode(
|
||||||
val transitionHandler = rememberDelegateTransitionHandler<NavTarget, BackStack.State> { navTarget ->
|
val transitionHandler = rememberDelegateTransitionHandler<NavTarget, BackStack.State> { navTarget ->
|
||||||
when (navTarget) {
|
when (navTarget) {
|
||||||
is NavTarget.SplashScreen,
|
is NavTarget.SplashScreen,
|
||||||
is NavTarget.LoggedInFlow -> backstackFader
|
is NavTarget.LoggedInFlow,
|
||||||
|
is NavTarget.NotLoggedInFlow -> backstackFader
|
||||||
else -> backstackSlider
|
else -> backstackSlider
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ import androidx.compose.runtime.setValue
|
||||||
import dev.zacsweers.metro.Inject
|
import dev.zacsweers.metro.Inject
|
||||||
import im.vector.app.features.analytics.plan.CryptoSessionStateChange
|
import im.vector.app.features.analytics.plan.CryptoSessionStateChange
|
||||||
import im.vector.app.features.analytics.plan.UserProperties
|
import im.vector.app.features.analytics.plan.UserProperties
|
||||||
|
import io.element.android.features.networkmonitor.api.NetworkMonitor
|
||||||
|
import io.element.android.features.networkmonitor.api.NetworkStatus
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.core.extensions.runCatchingExceptions
|
import io.element.android.libraries.core.extensions.runCatchingExceptions
|
||||||
|
|
@ -56,6 +58,7 @@ class LoggedInPresenter(
|
||||||
private val analyticsService: AnalyticsService,
|
private val analyticsService: AnalyticsService,
|
||||||
private val encryptionService: EncryptionService,
|
private val encryptionService: EncryptionService,
|
||||||
private val buildMeta: BuildMeta,
|
private val buildMeta: BuildMeta,
|
||||||
|
private val networkMonitor: NetworkMonitor,
|
||||||
) : Presenter<LoggedInState> {
|
) : Presenter<LoggedInState> {
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): LoggedInState {
|
override fun present(): LoggedInState {
|
||||||
|
|
@ -107,6 +110,14 @@ class LoggedInPresenter(
|
||||||
}.launchIn(this)
|
}.launchIn(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val networkConnectivity by networkMonitor.connectivity.collectAsState()
|
||||||
|
LaunchedEffect(networkConnectivity) {
|
||||||
|
if (networkConnectivity == NetworkStatus.Connected) {
|
||||||
|
// Refresh homeserver capabilities when the network is back
|
||||||
|
matrixClient.homeserverCapabilities().refresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun handleEvent(event: LoggedInEvents) {
|
fun handleEvent(event: LoggedInEvents) {
|
||||||
when (event) {
|
when (event) {
|
||||||
is LoggedInEvents.CloseErrorDialog -> {
|
is LoggedInEvents.CloseErrorDialog -> {
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,7 @@ class JoinedRoomLoadedFlowNode(
|
||||||
fun navigateToRoom(roomId: RoomId, serverNames: List<String>)
|
fun navigateToRoom(roomId: RoomId, serverNames: List<String>)
|
||||||
fun handlePermalinkClick(data: PermalinkData, pushToBackstack: Boolean)
|
fun handlePermalinkClick(data: PermalinkData, pushToBackstack: Boolean)
|
||||||
fun navigateToGlobalNotificationSettings()
|
fun navigateToGlobalNotificationSettings()
|
||||||
|
fun navigateToDeveloperSettings()
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Inputs(
|
data class Inputs(
|
||||||
|
|
@ -145,6 +146,10 @@ class JoinedRoomLoadedFlowNode(
|
||||||
callback.navigateToGlobalNotificationSettings()
|
callback.navigateToGlobalNotificationSettings()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun navigateToDeveloperSettings() {
|
||||||
|
callback.navigateToDeveloperSettings()
|
||||||
|
}
|
||||||
|
|
||||||
override fun navigateToRoom(roomId: RoomId, serverNames: List<String>) {
|
override fun navigateToRoom(roomId: RoomId, serverNames: List<String>) {
|
||||||
callback.navigateToRoom(roomId, serverNames)
|
callback.navigateToRoom(roomId, serverNames)
|
||||||
}
|
}
|
||||||
|
|
@ -252,6 +257,10 @@ class JoinedRoomLoadedFlowNode(
|
||||||
override fun navigateToRoom(roomId: RoomId) {
|
override fun navigateToRoom(roomId: RoomId) {
|
||||||
callback.navigateToRoom(roomId, emptyList())
|
callback.navigateToRoom(roomId, emptyList())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun navigateToDeveloperSettings() {
|
||||||
|
callback.navigateToDeveloperSettings()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
val params = MessagesEntryPoint.Params(
|
val params = MessagesEntryPoint.Params(
|
||||||
MessagesEntryPoint.InitialTarget.Messages(navTarget.focusedEventId)
|
MessagesEntryPoint.InitialTarget.Messages(navTarget.focusedEventId)
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ import io.element.android.features.rageshake.api.detection.RageshakeDetectionVie
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.designsystem.theme.components.Text
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
import io.element.android.services.apperror.impl.AppErrorView
|
import io.element.android.services.apperror.api.AppErrorView
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RootView(
|
fun RootView(
|
||||||
|
|
|
||||||
6
appnav/src/main/res/values-ja/translations.xml
Normal file
6
appnav/src/main/res/values-ja/translations.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="banner_migrate_to_native_sliding_sync_action">"ログアウトしてアップグレード"</string>
|
||||||
|
<string name="banner_migrate_to_native_sliding_sync_app_force_logout_title">"%1$s は古いプロトコルに非対応になりました。アプリを引き続き使用するには、ログアウトしてから再度ログインしてください。"</string>
|
||||||
|
<string name="banner_migrate_to_native_sliding_sync_force_logout_title">"使用しているホームサーバーは古いプロトコルに非対応になりました。アプリケーションを引き続き使用するには、ログアウトしてから再度ログインしてください。"</string>
|
||||||
|
</resources>
|
||||||
6
appnav/src/main/res/values-vi/translations.xml
Normal file
6
appnav/src/main/res/values-vi/translations.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="banner_migrate_to_native_sliding_sync_action">"Đăng xuất & Nâng cấp"</string>
|
||||||
|
<string name="banner_migrate_to_native_sliding_sync_app_force_logout_title">"%1$s không còn hỗ trợ giao thức cũ. Vui lòng đăng xuất và đăng nhập lại để tiếp tục sử dụng ứng dụng."</string>
|
||||||
|
<string name="banner_migrate_to_native_sliding_sync_force_logout_title">"Homeserver của bạn không còn hỗ trợ giao thức cũ. Vui lòng đăng xuất và đăng nhập lại để tiếp tục sử dụng ứng dụng."</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -43,7 +43,7 @@ import io.element.android.services.analytics.api.watchers.AnalyticsSendMessageWa
|
||||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||||
import io.element.android.services.analytics.test.watchers.FakeAnalyticsSendMessageWatcher
|
import io.element.android.services.analytics.test.watchers.FakeAnalyticsSendMessageWatcher
|
||||||
import io.element.android.services.appnavstate.api.ActiveRoomsHolder
|
import io.element.android.services.appnavstate.api.ActiveRoomsHolder
|
||||||
import io.element.android.services.appnavstate.impl.DefaultActiveRoomsHolder
|
import io.element.android.services.appnavstate.test.FakeActiveRoomsHolder
|
||||||
import io.element.android.services.appnavstate.test.FakeAppNavigationStateService
|
import io.element.android.services.appnavstate.test.FakeAppNavigationStateService
|
||||||
import kotlinx.coroutines.test.TestScope
|
import kotlinx.coroutines.test.TestScope
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
|
|
@ -128,7 +128,7 @@ class JoinedRoomLoadedFlowNodeTest {
|
||||||
roomDetailsEntryPoint: RoomDetailsEntryPoint = FakeRoomDetailsEntryPoint(),
|
roomDetailsEntryPoint: RoomDetailsEntryPoint = FakeRoomDetailsEntryPoint(),
|
||||||
spaceEntryPoint: SpaceEntryPoint = FakeSpaceEntryPoint(),
|
spaceEntryPoint: SpaceEntryPoint = FakeSpaceEntryPoint(),
|
||||||
forwardEntryPoint: ForwardEntryPoint = FakeForwardEntryPoint(),
|
forwardEntryPoint: ForwardEntryPoint = FakeForwardEntryPoint(),
|
||||||
activeRoomsHolder: ActiveRoomsHolder = DefaultActiveRoomsHolder(),
|
activeRoomsHolder: ActiveRoomsHolder = FakeActiveRoomsHolder(),
|
||||||
matrixClient: FakeMatrixClient = FakeMatrixClient(),
|
matrixClient: FakeMatrixClient = FakeMatrixClient(),
|
||||||
) = JoinedRoomLoadedFlowNode(
|
) = JoinedRoomLoadedFlowNode(
|
||||||
buildContext = BuildContext.root(savedStateMap = null),
|
buildContext = BuildContext.root(savedStateMap = null),
|
||||||
|
|
@ -213,7 +213,7 @@ class JoinedRoomLoadedFlowNodeTest {
|
||||||
val fakeMessagesEntryPoint = FakeMessagesEntryPoint()
|
val fakeMessagesEntryPoint = FakeMessagesEntryPoint()
|
||||||
val fakeRoomDetailsEntryPoint = FakeRoomDetailsEntryPoint()
|
val fakeRoomDetailsEntryPoint = FakeRoomDetailsEntryPoint()
|
||||||
val inputs = JoinedRoomLoadedFlowNode.Inputs(room, RoomNavigationTarget.Root())
|
val inputs = JoinedRoomLoadedFlowNode.Inputs(room, RoomNavigationTarget.Root())
|
||||||
val activeRoomsHolder = DefaultActiveRoomsHolder()
|
val activeRoomsHolder = FakeActiveRoomsHolder()
|
||||||
val roomFlowNode = createJoinedRoomLoadedFlowNode(
|
val roomFlowNode = createJoinedRoomLoadedFlowNode(
|
||||||
plugins = listOf(inputs, FakeJoinedRoomLoadedFlowNodeCallback()),
|
plugins = listOf(inputs, FakeJoinedRoomLoadedFlowNodeCallback()),
|
||||||
messagesEntryPoint = fakeMessagesEntryPoint,
|
messagesEntryPoint = fakeMessagesEntryPoint,
|
||||||
|
|
@ -236,7 +236,7 @@ class JoinedRoomLoadedFlowNodeTest {
|
||||||
val fakeMessagesEntryPoint = FakeMessagesEntryPoint()
|
val fakeMessagesEntryPoint = FakeMessagesEntryPoint()
|
||||||
val fakeRoomDetailsEntryPoint = FakeRoomDetailsEntryPoint()
|
val fakeRoomDetailsEntryPoint = FakeRoomDetailsEntryPoint()
|
||||||
val inputs = JoinedRoomLoadedFlowNode.Inputs(room, RoomNavigationTarget.Root())
|
val inputs = JoinedRoomLoadedFlowNode.Inputs(room, RoomNavigationTarget.Root())
|
||||||
val activeRoomsHolder = DefaultActiveRoomsHolder().apply {
|
val activeRoomsHolder = FakeActiveRoomsHolder().apply {
|
||||||
addRoom(room)
|
addRoom(room)
|
||||||
}
|
}
|
||||||
val roomFlowNode = createJoinedRoomLoadedFlowNode(
|
val roomFlowNode = createJoinedRoomLoadedFlowNode(
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,7 @@ import io.element.android.libraries.matrix.test.FakeSdkMetadata
|
||||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||||
import io.element.android.services.apperror.api.AppErrorState
|
import io.element.android.services.apperror.api.AppErrorState
|
||||||
import io.element.android.services.apperror.api.AppErrorStateService
|
import io.element.android.services.apperror.api.AppErrorStateService
|
||||||
import io.element.android.services.apperror.impl.DefaultAppErrorStateService
|
import io.element.android.services.apperror.test.FakeAppErrorStateService
|
||||||
import io.element.android.services.toolbox.test.strings.FakeStringProvider
|
|
||||||
import io.element.android.tests.testutils.WarmUpRule
|
import io.element.android.tests.testutils.WarmUpRule
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
|
|
@ -44,10 +43,16 @@ class RootPresenterTest {
|
||||||
@Test
|
@Test
|
||||||
fun `present - passes app error state`() = runTest {
|
fun `present - passes app error state`() = runTest {
|
||||||
val presenter = createRootPresenter(
|
val presenter = createRootPresenter(
|
||||||
appErrorService = DefaultAppErrorStateService(
|
appErrorService = FakeAppErrorStateService().apply {
|
||||||
stringProvider = FakeStringProvider(),
|
setAppErrorState(
|
||||||
).apply {
|
AppErrorState.Error(
|
||||||
showError("Bad news", "Something bad happened")
|
title = "Bad news",
|
||||||
|
body = "Something bad happened",
|
||||||
|
dismiss = {
|
||||||
|
setAppErrorState(AppErrorState.NoError)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
|
@ -65,9 +70,7 @@ class RootPresenterTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createRootPresenter(
|
private fun createRootPresenter(
|
||||||
appErrorService: AppErrorStateService = DefaultAppErrorStateService(
|
appErrorService: AppErrorStateService = FakeAppErrorStateService(),
|
||||||
stringProvider = FakeStringProvider(),
|
|
||||||
),
|
|
||||||
): RootPresenter {
|
): RootPresenter {
|
||||||
return RootPresenter(
|
return RootPresenter(
|
||||||
crashDetectionPresenter = { aCrashDetectionState() },
|
crashDetectionPresenter = { aCrashDetectionState() },
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ import app.cash.turbine.ReceiveTurbine
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import im.vector.app.features.analytics.plan.CryptoSessionStateChange
|
import im.vector.app.features.analytics.plan.CryptoSessionStateChange
|
||||||
import im.vector.app.features.analytics.plan.UserProperties
|
import im.vector.app.features.analytics.plan.UserProperties
|
||||||
|
import io.element.android.features.networkmonitor.api.NetworkStatus
|
||||||
|
import io.element.android.features.networkmonitor.test.FakeNetworkMonitor
|
||||||
import io.element.android.libraries.core.meta.BuildMeta
|
import io.element.android.libraries.core.meta.BuildMeta
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
|
|
@ -27,6 +29,7 @@ import io.element.android.libraries.matrix.api.verification.SessionVerificationS
|
||||||
import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus
|
import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus
|
||||||
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
||||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||||
|
import io.element.android.libraries.matrix.test.FakeHomeserverCapabilitiesProvider
|
||||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||||
import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService
|
import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService
|
||||||
|
|
@ -109,6 +112,7 @@ class LoggedInPresenterTest {
|
||||||
val verificationService = FakeSessionVerificationService()
|
val verificationService = FakeSessionVerificationService()
|
||||||
val encryptionService = FakeEncryptionService()
|
val encryptionService = FakeEncryptionService()
|
||||||
val buildMeta = aBuildMeta()
|
val buildMeta = aBuildMeta()
|
||||||
|
val networkMonitor = FakeNetworkMonitor()
|
||||||
LoggedInPresenter(
|
LoggedInPresenter(
|
||||||
matrixClient = FakeMatrixClient(
|
matrixClient = FakeMatrixClient(
|
||||||
roomListService = roomListService,
|
roomListService = roomListService,
|
||||||
|
|
@ -122,6 +126,7 @@ class LoggedInPresenterTest {
|
||||||
analyticsService = analyticsService,
|
analyticsService = analyticsService,
|
||||||
encryptionService = encryptionService,
|
encryptionService = encryptionService,
|
||||||
buildMeta = buildMeta,
|
buildMeta = buildMeta,
|
||||||
|
networkMonitor = networkMonitor,
|
||||||
).test {
|
).test {
|
||||||
encryptionService.emitRecoveryState(RecoveryState.UNKNOWN)
|
encryptionService.emitRecoveryState(RecoveryState.UNKNOWN)
|
||||||
encryptionService.emitRecoveryState(RecoveryState.INCOMPLETE)
|
encryptionService.emitRecoveryState(RecoveryState.INCOMPLETE)
|
||||||
|
|
@ -319,6 +324,27 @@ class LoggedInPresenterTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - refreshes homeserver capabilities when network is back`() = runTest {
|
||||||
|
val refreshLambda = lambdaRecorder<Result<Unit>> { Result.success(Unit) }
|
||||||
|
val matrixClient = FakeMatrixClient(
|
||||||
|
homeserverCapabilitiesProvider = FakeHomeserverCapabilitiesProvider(refresh = refreshLambda),
|
||||||
|
accountManagementUrlResult = { Result.success(null) },
|
||||||
|
)
|
||||||
|
val networkMonitor = FakeNetworkMonitor()
|
||||||
|
createLoggedInPresenter(
|
||||||
|
matrixClient = matrixClient,
|
||||||
|
networkMonitor = networkMonitor,
|
||||||
|
).test {
|
||||||
|
awaitItem()
|
||||||
|
networkMonitor.connectivity.value = NetworkStatus.Connected
|
||||||
|
|
||||||
|
advanceUntilIdle()
|
||||||
|
|
||||||
|
refreshLambda.assertions().isCalledOnce()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun <T> ReceiveTurbine<T>.awaitFirstItem(): T {
|
private suspend fun <T> ReceiveTurbine<T>.awaitFirstItem(): T {
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
return awaitItem()
|
return awaitItem()
|
||||||
|
|
@ -334,6 +360,7 @@ class LoggedInPresenterTest {
|
||||||
accountManagementUrlResult = { Result.success(null) },
|
accountManagementUrlResult = { Result.success(null) },
|
||||||
),
|
),
|
||||||
buildMeta: BuildMeta = aBuildMeta(),
|
buildMeta: BuildMeta = aBuildMeta(),
|
||||||
|
networkMonitor: FakeNetworkMonitor = FakeNetworkMonitor(),
|
||||||
): LoggedInPresenter {
|
): LoggedInPresenter {
|
||||||
return LoggedInPresenter(
|
return LoggedInPresenter(
|
||||||
matrixClient = matrixClient,
|
matrixClient = matrixClient,
|
||||||
|
|
@ -343,6 +370,7 @@ class LoggedInPresenterTest {
|
||||||
analyticsService = analyticsService,
|
analyticsService = analyticsService,
|
||||||
encryptionService = encryptionService,
|
encryptionService = encryptionService,
|
||||||
buildMeta = buildMeta,
|
buildMeta = buildMeta,
|
||||||
|
networkMonitor = networkMonitor,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,4 +16,5 @@ class FakeJoinedRoomLoadedFlowNodeCallback : JoinedRoomLoadedFlowNode.Callback {
|
||||||
override fun navigateToRoom(roomId: RoomId, serverNames: List<String>) = lambdaError()
|
override fun navigateToRoom(roomId: RoomId, serverNames: List<String>) = lambdaError()
|
||||||
override fun handlePermalinkClick(data: PermalinkData, pushToBackstack: Boolean) = lambdaError()
|
override fun handlePermalinkClick(data: PermalinkData, pushToBackstack: Boolean) = lambdaError()
|
||||||
override fun navigateToGlobalNotificationSettings() = lambdaError()
|
override fun navigateToGlobalNotificationSettings() = lambdaError()
|
||||||
|
override fun navigateToDeveloperSettings() = lambdaError()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
fastlane/metadata/android/en-US/changelogs/202604000.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/202604000.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
Main changes in this version: bug fixes for crashes from the SDK and notifications and UI improvements.
|
||||||
|
Full changelog: https://github.com/element-hq/element-x-android/releases
|
||||||
2
fastlane/metadata/android/en-US/changelogs/202604010.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/202604010.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
Main changes in this version: bug fixes and improvements.
|
||||||
|
Full changelog: https://github.com/element-hq/element-x-android/releases
|
||||||
2
fastlane/metadata/android/en-US/changelogs/202604020.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/202604020.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
Main changes in this version: bug fixes and improvements.
|
||||||
|
Full changelog: https://github.com/element-hq/element-x-android/releases
|
||||||
2
fastlane/metadata/android/en-US/changelogs/202604030.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/202604030.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
Main changes in this version: bug fixes and improvements.
|
||||||
|
Full changelog: https://github.com/element-hq/element-x-android/releases
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_analytics_settings_help_us_improve">"問題発見のため、匿名の使用データの共有にご協力ください。"</string>
|
||||||
|
<string name="screen_analytics_settings_read_terms">"利用規約の全文を%1$sから確認することができます。"</string>
|
||||||
|
<string name="screen_analytics_settings_read_terms_content_link">"こちら"</string>
|
||||||
|
<string name="screen_analytics_settings_share_data">"使用データを共有"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="screen_analytics_settings_help_us_improve">"Dalinkitės anoniminiais naudojimo duomenimis ir padėkite mums nustatyti problemas."</string>
|
<string name="screen_analytics_settings_help_us_improve">"Bendrinkite anoniminius naudojimo duomenis, kad padėtumėte mums nustatyti problemas."</string>
|
||||||
<string name="screen_analytics_settings_read_terms">"Galite perskaityti visas mūsų sąlygas %1$s."</string>
|
<string name="screen_analytics_settings_read_terms">"Galite perskaityti visas mūsų sąlygas %1$s."</string>
|
||||||
<string name="screen_analytics_settings_read_terms_content_link">"čia"</string>
|
<string name="screen_analytics_settings_read_terms_content_link">"čia"</string>
|
||||||
<string name="screen_analytics_settings_share_data">"Dalytis analitiniais duomenimis"</string>
|
<string name="screen_analytics_settings_share_data">"Bendrinti analitinius duomenis"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_analytics_settings_help_us_improve">"Chia sẻ dữ liệu sử dụng ẩn danh để giúp chúng tôi xác định vấn đề."</string>
|
||||||
|
<string name="screen_analytics_settings_read_terms">"Bạn có thể xem tất cả điều khoản của chúng tôi tại %1$s"</string>
|
||||||
|
<string name="screen_analytics_settings_read_terms_content_link">"tại đây"</string>
|
||||||
|
<string name="screen_analytics_settings_share_data">"Chia sẻ dữ liệu phân tích"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_analytics_prompt_data_usage">"いかなる個人情報も記録, 分析されることはありません"</string>
|
||||||
|
<string name="screen_analytics_prompt_help_us_improve">"問題発見のため、匿名の使用データの共有にご協力ください。"</string>
|
||||||
|
<string name="screen_analytics_prompt_read_terms">"利用規約の全文を%1$sから確認することができます。"</string>
|
||||||
|
<string name="screen_analytics_prompt_read_terms_content_link">"こちら"</string>
|
||||||
|
<string name="screen_analytics_prompt_settings">"いつでも設定は変更できます"</string>
|
||||||
|
<string name="screen_analytics_prompt_third_party_sharing">"情報が第三者に共有されることはありません"</string>
|
||||||
|
<string name="screen_analytics_prompt_title">"%1$s の改善にご協力ください"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="screen_analytics_prompt_data_usage">"Mes nekaupsime ir neprofiliuosime jokių asmens duomenų"</string>
|
<string name="screen_analytics_prompt_data_usage">"Mes neįrašysime ar neprofiliuosime jokių asmeninių duomenų."</string>
|
||||||
<string name="screen_analytics_prompt_help_us_improve">"Dalinkitės anoniminiais naudojimo duomenimis ir padėkite mums nustatyti problemas."</string>
|
<string name="screen_analytics_prompt_help_us_improve">"Bendrinkite anoniminius naudojimo duomenis, kad padėtumėte mums nustatyti problemas."</string>
|
||||||
<string name="screen_analytics_prompt_read_terms">"Galite perskaityti visas mūsų sąlygas %1$s."</string>
|
<string name="screen_analytics_prompt_read_terms">"Galite perskaityti visas mūsų sąlygas %1$s."</string>
|
||||||
<string name="screen_analytics_prompt_read_terms_content_link">"čia"</string>
|
<string name="screen_analytics_prompt_read_terms_content_link">"čia"</string>
|
||||||
<string name="screen_analytics_prompt_settings">"Tai galite bet kada išjungti"</string>
|
<string name="screen_analytics_prompt_settings">"Tai galite išjungti bet kuriuo metu."</string>
|
||||||
<string name="screen_analytics_prompt_third_party_sharing">"Mes nesidalinsime Jūsų duomenimis su trečiosiomis šalimis"</string>
|
<string name="screen_analytics_prompt_third_party_sharing">"Mes nebendrinsime jūsų duomenų su trečiosiomis šalimis."</string>
|
||||||
<string name="screen_analytics_prompt_title">"Padėkite pagerinti %1$s"</string>
|
<string name="screen_analytics_prompt_title">"Padėkite patobulinti „%1$s“"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_analytics_prompt_data_usage">"Chúng tôi sẽ không ghi lại hoặc lập hồ sơ bất kỳ dữ liệu cá nhân nào."</string>
|
||||||
|
<string name="screen_analytics_prompt_help_us_improve">"Chia sẻ dữ liệu sử dụng ẩn danh để giúp chúng tôi xác định vấn đề."</string>
|
||||||
|
<string name="screen_analytics_prompt_read_terms">"Bạn có thể xem tất cả điều khoản của chúng tôi tại %1$s"</string>
|
||||||
|
<string name="screen_analytics_prompt_read_terms_content_link">"tại đây"</string>
|
||||||
|
<string name="screen_analytics_prompt_settings">"Bạn có thể tắt tính năng này bất cứ lúc nào"</string>
|
||||||
|
<string name="screen_analytics_prompt_third_party_sharing">"Chúng tôi sẽ không chia sẻ dữ liệu của bạn với bên thứ ba."</string>
|
||||||
|
<string name="screen_analytics_prompt_title">"Giúp cải thiện %1$s"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -8,7 +8,13 @@
|
||||||
|
|
||||||
package io.element.android.features.announcement.api
|
package io.element.android.features.announcement.api
|
||||||
|
|
||||||
enum class Announcement {
|
import androidx.compose.runtime.Immutable
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
sealed interface Announcement {
|
||||||
|
enum class Fullscreen : Announcement {
|
||||||
Space,
|
Space,
|
||||||
NewNotificationSound,
|
}
|
||||||
|
|
||||||
|
data object NewNotificationSound : Announcement
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Element Creations Ltd.
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.announcement.impl
|
||||||
|
|
||||||
|
import io.element.android.features.announcement.api.Announcement
|
||||||
|
|
||||||
|
sealed interface AnnouncementEvent {
|
||||||
|
data class Continue(
|
||||||
|
val announcement: Announcement.Fullscreen,
|
||||||
|
) : AnnouncementEvent
|
||||||
|
}
|
||||||
|
|
@ -12,12 +12,16 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import dev.zacsweers.metro.Inject
|
import dev.zacsweers.metro.Inject
|
||||||
import io.element.android.features.announcement.api.Announcement
|
import io.element.android.features.announcement.api.Announcement
|
||||||
import io.element.android.features.announcement.impl.store.AnnouncementStatus
|
import io.element.android.features.announcement.impl.store.AnnouncementStatus
|
||||||
import io.element.android.features.announcement.impl.store.AnnouncementStore
|
import io.element.android.features.announcement.impl.store.AnnouncementStore
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.flowOf
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
class AnnouncementPresenter(
|
class AnnouncementPresenter(
|
||||||
|
|
@ -25,13 +29,39 @@ class AnnouncementPresenter(
|
||||||
) : Presenter<AnnouncementState> {
|
) : Presenter<AnnouncementState> {
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): AnnouncementState {
|
override fun present(): AnnouncementState {
|
||||||
val showSpaceAnnouncement by remember {
|
val coroutineScope = rememberCoroutineScope()
|
||||||
announcementStore.announcementStatusFlow(Announcement.Space).map {
|
|
||||||
|
val fullscreenAnnouncementToShow by remember {
|
||||||
|
combine(
|
||||||
|
flowOf(Unit),
|
||||||
|
announcementStore.announcementStatusFlow(Announcement.Fullscreen.Space).map {
|
||||||
it == AnnouncementStatus.Show
|
it == AnnouncementStatus.Show
|
||||||
|
},
|
||||||
|
// Add other announcements here when needed
|
||||||
|
) { _, showFullscreenSpace ->
|
||||||
|
when {
|
||||||
|
showFullscreenSpace -> Announcement.Fullscreen.Space
|
||||||
|
else -> {
|
||||||
|
null
|
||||||
}
|
}
|
||||||
}.collectAsState(false)
|
}
|
||||||
|
}
|
||||||
|
}.collectAsState(null)
|
||||||
|
|
||||||
|
fun handle(event: AnnouncementEvent) {
|
||||||
|
when (event) {
|
||||||
|
is AnnouncementEvent.Continue -> coroutineScope.launch {
|
||||||
|
announcementStore.setAnnouncementStatus(
|
||||||
|
announcement = event.announcement,
|
||||||
|
status = AnnouncementStatus.Shown,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return AnnouncementState(
|
return AnnouncementState(
|
||||||
showSpaceAnnouncement = showSpaceAnnouncement,
|
announcement = fullscreenAnnouncementToShow,
|
||||||
|
eventSink = ::handle,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,9 @@
|
||||||
|
|
||||||
package io.element.android.features.announcement.impl
|
package io.element.android.features.announcement.impl
|
||||||
|
|
||||||
data class AnnouncementState(
|
import io.element.android.features.announcement.api.Announcement
|
||||||
val showSpaceAnnouncement: Boolean,
|
|
||||||
)
|
|
||||||
|
|
||||||
fun anAnnouncementState(
|
data class AnnouncementState(
|
||||||
showSpaceAnnouncement: Boolean = false,
|
val announcement: Announcement.Fullscreen?,
|
||||||
) = AnnouncementState(
|
val eventSink: (AnnouncementEvent) -> Unit,
|
||||||
showSpaceAnnouncement = showSpaceAnnouncement,
|
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Element Creations Ltd.
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.announcement.impl
|
||||||
|
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
|
import io.element.android.features.announcement.api.Announcement
|
||||||
|
|
||||||
|
open class AnnouncementStateProvider : PreviewParameterProvider<AnnouncementState> {
|
||||||
|
override val values: Sequence<AnnouncementState>
|
||||||
|
get() = sequenceOf(
|
||||||
|
anAnnouncementState(),
|
||||||
|
anAnnouncementState(
|
||||||
|
announcement = Announcement.Fullscreen.Space,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun anAnnouncementState(
|
||||||
|
announcement: Announcement.Fullscreen? = null,
|
||||||
|
eventSink: (AnnouncementEvent) -> Unit = {},
|
||||||
|
) = AnnouncementState(
|
||||||
|
announcement = announcement,
|
||||||
|
eventSink = eventSink,
|
||||||
|
)
|
||||||
|
|
@ -8,35 +8,28 @@
|
||||||
|
|
||||||
package io.element.android.features.announcement.impl
|
package io.element.android.features.announcement.impl
|
||||||
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
|
||||||
import androidx.compose.animation.fadeIn
|
|
||||||
import androidx.compose.animation.fadeOut
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import dev.zacsweers.metro.AppScope
|
import dev.zacsweers.metro.AppScope
|
||||||
import dev.zacsweers.metro.ContributesBinding
|
import dev.zacsweers.metro.ContributesBinding
|
||||||
import io.element.android.features.announcement.api.Announcement
|
import io.element.android.features.announcement.api.Announcement
|
||||||
import io.element.android.features.announcement.api.AnnouncementService
|
import io.element.android.features.announcement.api.AnnouncementService
|
||||||
import io.element.android.features.announcement.impl.spaces.SpaceAnnouncementState
|
import io.element.android.features.announcement.impl.fullscreen.FullscreenAnnouncementView
|
||||||
import io.element.android.features.announcement.impl.spaces.SpaceAnnouncementView
|
|
||||||
import io.element.android.features.announcement.impl.store.AnnouncementStatus
|
import io.element.android.features.announcement.impl.store.AnnouncementStatus
|
||||||
import io.element.android.features.announcement.impl.store.AnnouncementStore
|
import io.element.android.features.announcement.impl.store.AnnouncementStore
|
||||||
import io.element.android.libraries.architecture.Presenter
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.flow.flowOf
|
||||||
|
|
||||||
@ContributesBinding(AppScope::class)
|
@ContributesBinding(AppScope::class)
|
||||||
class DefaultAnnouncementService(
|
class DefaultAnnouncementService(
|
||||||
private val announcementStore: AnnouncementStore,
|
private val announcementStore: AnnouncementStore,
|
||||||
private val announcementPresenter: Presenter<AnnouncementState>,
|
private val announcementPresenter: AnnouncementPresenter,
|
||||||
private val spaceAnnouncementPresenter: Presenter<SpaceAnnouncementState>,
|
|
||||||
) : AnnouncementService {
|
) : AnnouncementService {
|
||||||
override suspend fun showAnnouncement(announcement: Announcement) {
|
override suspend fun showAnnouncement(announcement: Announcement) {
|
||||||
when (announcement) {
|
when (announcement) {
|
||||||
Announcement.Space -> showSpaceAnnouncement()
|
is Announcement.Fullscreen -> showFullscreenAnnouncement(announcement)
|
||||||
Announcement.NewNotificationSound -> {
|
Announcement.NewNotificationSound -> {
|
||||||
announcementStore.setAnnouncementStatus(Announcement.NewNotificationSound, AnnouncementStatus.Show)
|
announcementStore.setAnnouncementStatus(Announcement.NewNotificationSound, AnnouncementStatus.Show)
|
||||||
}
|
}
|
||||||
|
|
@ -49,13 +42,10 @@ class DefaultAnnouncementService(
|
||||||
|
|
||||||
override fun announcementsToShowFlow(): Flow<List<Announcement>> {
|
override fun announcementsToShowFlow(): Flow<List<Announcement>> {
|
||||||
return combine(
|
return combine(
|
||||||
announcementStore.announcementStatusFlow(Announcement.Space),
|
flowOf(Unit),
|
||||||
announcementStore.announcementStatusFlow(Announcement.NewNotificationSound),
|
announcementStore.announcementStatusFlow(Announcement.NewNotificationSound),
|
||||||
) { spaceAnnouncementStatus, newNotificationSoundStatus ->
|
) { _, newNotificationSoundStatus ->
|
||||||
buildList {
|
buildList {
|
||||||
if (spaceAnnouncementStatus == AnnouncementStatus.Show) {
|
|
||||||
add(Announcement.Space)
|
|
||||||
}
|
|
||||||
if (newNotificationSoundStatus == AnnouncementStatus.Show) {
|
if (newNotificationSoundStatus == AnnouncementStatus.Show) {
|
||||||
add(Announcement.NewNotificationSound)
|
add(Announcement.NewNotificationSound)
|
||||||
}
|
}
|
||||||
|
|
@ -63,27 +53,19 @@ class DefaultAnnouncementService(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun showSpaceAnnouncement() {
|
private suspend fun showFullscreenAnnouncement(announcement: Announcement.Fullscreen) {
|
||||||
val currentValue = announcementStore.announcementStatusFlow(Announcement.Space).first()
|
val currentValue = announcementStore.announcementStatusFlow(announcement).first()
|
||||||
if (currentValue == AnnouncementStatus.NeverShown) {
|
if (currentValue == AnnouncementStatus.NeverShown) {
|
||||||
announcementStore.setAnnouncementStatus(Announcement.Space, AnnouncementStatus.Show)
|
announcementStore.setAnnouncementStatus(announcement, AnnouncementStatus.Show)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Render(modifier: Modifier) {
|
override fun Render(modifier: Modifier) {
|
||||||
val announcementState = announcementPresenter.present()
|
val announcementState = announcementPresenter.present()
|
||||||
Box(modifier = modifier.fillMaxSize()) {
|
FullscreenAnnouncementView(
|
||||||
AnimatedVisibility(
|
state = announcementState,
|
||||||
visible = announcementState.showSpaceAnnouncement,
|
modifier = modifier,
|
||||||
enter = fadeIn(),
|
|
||||||
exit = fadeOut(),
|
|
||||||
) {
|
|
||||||
val spaceAnnouncementState = spaceAnnouncementPresenter.present()
|
|
||||||
SpaceAnnouncementView(
|
|
||||||
state = spaceAnnouncementState,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2025 Element Creations Ltd.
|
|
||||||
* Copyright 2025 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
|
||||||
* Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.element.android.features.announcement.impl.di
|
|
||||||
|
|
||||||
import dev.zacsweers.metro.AppScope
|
|
||||||
import dev.zacsweers.metro.BindingContainer
|
|
||||||
import dev.zacsweers.metro.Binds
|
|
||||||
import dev.zacsweers.metro.ContributesTo
|
|
||||||
import io.element.android.features.announcement.impl.AnnouncementPresenter
|
|
||||||
import io.element.android.features.announcement.impl.AnnouncementState
|
|
||||||
import io.element.android.features.announcement.impl.spaces.SpaceAnnouncementPresenter
|
|
||||||
import io.element.android.features.announcement.impl.spaces.SpaceAnnouncementState
|
|
||||||
import io.element.android.libraries.architecture.Presenter
|
|
||||||
|
|
||||||
@ContributesTo(AppScope::class)
|
|
||||||
@BindingContainer
|
|
||||||
interface AnnouncementModule {
|
|
||||||
@Binds
|
|
||||||
fun bindAnnouncementPresenter(presenter: AnnouncementPresenter): Presenter<AnnouncementState>
|
|
||||||
|
|
||||||
@Binds
|
|
||||||
fun bindSpaceAnnouncementPresenter(presenter: SpaceAnnouncementPresenter): Presenter<SpaceAnnouncementState>
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,225 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Element Creations Ltd.
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.announcement.impl.fullscreen
|
||||||
|
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import io.element.android.compound.theme.ElementTheme
|
||||||
|
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||||
|
import io.element.android.features.announcement.api.Announcement
|
||||||
|
import io.element.android.features.announcement.impl.AnnouncementEvent
|
||||||
|
import io.element.android.features.announcement.impl.AnnouncementState
|
||||||
|
import io.element.android.features.announcement.impl.AnnouncementStateProvider
|
||||||
|
import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule
|
||||||
|
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
|
||||||
|
import io.element.android.libraries.designsystem.atomic.organisms.InfoListItem
|
||||||
|
import io.element.android.libraries.designsystem.atomic.organisms.InfoListOrganism
|
||||||
|
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
|
||||||
|
import io.element.android.libraries.designsystem.components.BigIcon
|
||||||
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.Button
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ref: https://www.figma.com/design/kcnHxunG1LDWXsJhaNuiHz/ER-145--Spaces-on-Element-X?node-id=4593-40181
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun FullscreenAnnouncementView(
|
||||||
|
state: AnnouncementState,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
// Ensure that the content stays visible during the exit animation
|
||||||
|
var fullscreenAnnouncement by remember { mutableStateOf<Announcement.Fullscreen?>(null) }
|
||||||
|
if (state.announcement != null) {
|
||||||
|
fullscreenAnnouncement = state.announcement
|
||||||
|
}
|
||||||
|
Box(modifier = modifier.fillMaxSize()) {
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = state.announcement != null,
|
||||||
|
enter = fadeIn(),
|
||||||
|
exit = fadeOut(),
|
||||||
|
) {
|
||||||
|
fullscreenAnnouncement?.let {
|
||||||
|
FullscreenAnnouncementView(
|
||||||
|
announcement = it,
|
||||||
|
eventSink = state.eventSink,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun FullscreenAnnouncementView(
|
||||||
|
announcement: Announcement.Fullscreen,
|
||||||
|
eventSink: (AnnouncementEvent) -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
fun onContinue() {
|
||||||
|
eventSink(AnnouncementEvent.Continue(announcement))
|
||||||
|
}
|
||||||
|
|
||||||
|
BackHandler(onBack = ::onContinue)
|
||||||
|
HeaderFooterPage(
|
||||||
|
modifier = modifier,
|
||||||
|
isScrollable = true,
|
||||||
|
contentPadding = PaddingValues(top = 24.dp, start = 16.dp, end = 16.dp, bottom = 24.dp),
|
||||||
|
header = {
|
||||||
|
FullscreenAnnouncementHeader(announcement)
|
||||||
|
},
|
||||||
|
content = {
|
||||||
|
FullscreenAnnouncementContent(
|
||||||
|
modifier = Modifier.padding(horizontal = 8.dp),
|
||||||
|
announcement = announcement,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
footer = {
|
||||||
|
FullscreenAnnouncementFooter(
|
||||||
|
onContinue = ::onContinue,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun FullscreenAnnouncementHeader(
|
||||||
|
announcement: Announcement.Fullscreen,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
IconTitleSubtitleMolecule(
|
||||||
|
modifier = modifier.padding(top = 16.dp, bottom = 16.dp),
|
||||||
|
title = announcement.title(),
|
||||||
|
showBetaLabel = true,
|
||||||
|
subTitle = announcement.subtitle(),
|
||||||
|
iconStyle = BigIcon.Style.Default(
|
||||||
|
vectorIcon = announcement.icon(),
|
||||||
|
usePrimaryTint = true,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun FullscreenAnnouncementContent(
|
||||||
|
announcement: Announcement.Fullscreen,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier.fillMaxSize(),
|
||||||
|
) {
|
||||||
|
InfoListOrganism(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
items = announcement.items(),
|
||||||
|
textStyle = ElementTheme.typography.fontBodyLgMedium,
|
||||||
|
iconTint = ElementTheme.colors.iconSecondary,
|
||||||
|
iconSize = 24.dp
|
||||||
|
)
|
||||||
|
announcement.notice()?.let { notice ->
|
||||||
|
Text(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 16.dp),
|
||||||
|
text = notice,
|
||||||
|
style = ElementTheme.typography.fontBodyMdRegular,
|
||||||
|
color = ElementTheme.colors.textSecondary,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun FullscreenAnnouncementFooter(
|
||||||
|
onContinue: () -> Unit,
|
||||||
|
) {
|
||||||
|
ButtonColumnMolecule(
|
||||||
|
modifier = Modifier.padding(bottom = 8.dp)
|
||||||
|
) {
|
||||||
|
Button(
|
||||||
|
text = stringResource(id = CommonStrings.action_continue),
|
||||||
|
onClick = onContinue,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun Announcement.Fullscreen.title() = when (this) {
|
||||||
|
Announcement.Fullscreen.Space -> "Introducing Spaces"
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun Announcement.Fullscreen.subtitle() = when (this) {
|
||||||
|
Announcement.Fullscreen.Space -> "Welcome to the beta version of Spaces! With this first version you can:"
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun Announcement.Fullscreen.icon() = when (this) {
|
||||||
|
Announcement.Fullscreen.Space -> CompoundIcons.SpaceSolid()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun Announcement.Fullscreen.items(): ImmutableList<InfoListItem> = when (this) {
|
||||||
|
Announcement.Fullscreen.Space -> persistentListOf(
|
||||||
|
InfoListItem(
|
||||||
|
message = "View spaces you\'ve created or joined",
|
||||||
|
iconVector = CompoundIcons.VisibilityOn(),
|
||||||
|
),
|
||||||
|
InfoListItem(
|
||||||
|
message = "Accept or decline invites to spaces",
|
||||||
|
iconVector = CompoundIcons.Email(),
|
||||||
|
),
|
||||||
|
InfoListItem(
|
||||||
|
message = "Discover any rooms you can join in your spaces",
|
||||||
|
iconVector = CompoundIcons.Search(),
|
||||||
|
),
|
||||||
|
InfoListItem(
|
||||||
|
message = "Join public spaces",
|
||||||
|
iconVector = CompoundIcons.Explore(),
|
||||||
|
),
|
||||||
|
InfoListItem(
|
||||||
|
message = "Leave any spaces you’ve joined",
|
||||||
|
iconVector = CompoundIcons.Leave(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun Announcement.Fullscreen.notice(): String? = when (this) {
|
||||||
|
Announcement.Fullscreen.Space -> "Filtering, creating and managing spaces is coming soon."
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreviewsDayNight
|
||||||
|
@Composable
|
||||||
|
internal fun FullscreenAnnouncementViewPreview(@PreviewParameter(AnnouncementStateProvider::class) state: AnnouncementState) = ElementPreview {
|
||||||
|
FullscreenAnnouncementView(
|
||||||
|
state = state,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2025 Element Creations Ltd.
|
|
||||||
* Copyright 2025 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
|
||||||
* Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.element.android.features.announcement.impl.spaces
|
|
||||||
|
|
||||||
sealed interface SpaceAnnouncementEvents {
|
|
||||||
data object Continue : SpaceAnnouncementEvents
|
|
||||||
}
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2025 Element Creations Ltd.
|
|
||||||
* Copyright 2025 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
|
||||||
* Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.element.android.features.announcement.impl.spaces
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import dev.zacsweers.metro.Inject
|
|
||||||
import io.element.android.features.announcement.api.Announcement
|
|
||||||
import io.element.android.features.announcement.impl.store.AnnouncementStatus
|
|
||||||
import io.element.android.features.announcement.impl.store.AnnouncementStore
|
|
||||||
import io.element.android.libraries.architecture.Presenter
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
class SpaceAnnouncementPresenter(
|
|
||||||
private val announcementStore: AnnouncementStore,
|
|
||||||
) : Presenter<SpaceAnnouncementState> {
|
|
||||||
@Composable
|
|
||||||
override fun present(): SpaceAnnouncementState {
|
|
||||||
val localCoroutineScope = rememberCoroutineScope()
|
|
||||||
|
|
||||||
fun handleEvent(event: SpaceAnnouncementEvents) {
|
|
||||||
when (event) {
|
|
||||||
SpaceAnnouncementEvents.Continue -> localCoroutineScope.launch {
|
|
||||||
announcementStore.setAnnouncementStatus(Announcement.Space, AnnouncementStatus.Shown)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return SpaceAnnouncementState(
|
|
||||||
eventSink = ::handleEvent,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2025 Element Creations Ltd.
|
|
||||||
* Copyright 2025 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
|
||||||
* Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.element.android.features.announcement.impl.spaces
|
|
||||||
|
|
||||||
data class SpaceAnnouncementState(
|
|
||||||
val eventSink: (SpaceAnnouncementEvents) -> Unit
|
|
||||||
)
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2025 Element Creations Ltd.
|
|
||||||
* Copyright 2025 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
|
||||||
* Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.element.android.features.announcement.impl.spaces
|
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
|
||||||
|
|
||||||
open class SpaceAnnouncementStateProvider : PreviewParameterProvider<SpaceAnnouncementState> {
|
|
||||||
override val values: Sequence<SpaceAnnouncementState>
|
|
||||||
get() = sequenceOf(
|
|
||||||
aSpaceAnnouncementState(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun aSpaceAnnouncementState(
|
|
||||||
eventSink: (SpaceAnnouncementEvents) -> Unit = {},
|
|
||||||
) = SpaceAnnouncementState(
|
|
||||||
eventSink = eventSink,
|
|
||||||
)
|
|
||||||
|
|
@ -1,158 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2025 Element Creations Ltd.
|
|
||||||
* Copyright 2025 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
|
||||||
* Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.element.android.features.announcement.impl.spaces
|
|
||||||
|
|
||||||
import androidx.activity.compose.BackHandler
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import io.element.android.compound.theme.ElementTheme
|
|
||||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
|
||||||
import io.element.android.features.announcement.impl.R
|
|
||||||
import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule
|
|
||||||
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
|
|
||||||
import io.element.android.libraries.designsystem.atomic.organisms.InfoListItem
|
|
||||||
import io.element.android.libraries.designsystem.atomic.organisms.InfoListOrganism
|
|
||||||
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
|
|
||||||
import io.element.android.libraries.designsystem.components.BigIcon
|
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
|
||||||
import io.element.android.libraries.designsystem.theme.components.Button
|
|
||||||
import io.element.android.libraries.designsystem.theme.components.Text
|
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ref: https://www.figma.com/design/kcnHxunG1LDWXsJhaNuiHz/ER-145--Spaces-on-Element-X?node-id=4593-40181
|
|
||||||
*/
|
|
||||||
@Composable
|
|
||||||
fun SpaceAnnouncementView(
|
|
||||||
state: SpaceAnnouncementState,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
) {
|
|
||||||
val eventSink = state.eventSink
|
|
||||||
|
|
||||||
fun onContinue() {
|
|
||||||
eventSink(SpaceAnnouncementEvents.Continue)
|
|
||||||
}
|
|
||||||
|
|
||||||
BackHandler(onBack = ::onContinue)
|
|
||||||
HeaderFooterPage(
|
|
||||||
modifier = modifier,
|
|
||||||
isScrollable = true,
|
|
||||||
contentPadding = PaddingValues(top = 24.dp, start = 16.dp, end = 16.dp, bottom = 24.dp),
|
|
||||||
header = {
|
|
||||||
SpaceAnnouncementHeader()
|
|
||||||
},
|
|
||||||
content = {
|
|
||||||
SpaceAnnouncementContent(
|
|
||||||
modifier = Modifier.padding(horizontal = 8.dp),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
footer = {
|
|
||||||
SpaceAnnouncementFooter(
|
|
||||||
onContinue = ::onContinue,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun SpaceAnnouncementHeader(
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
) {
|
|
||||||
IconTitleSubtitleMolecule(
|
|
||||||
modifier = modifier.padding(top = 16.dp, bottom = 16.dp),
|
|
||||||
title = stringResource(id = R.string.screen_space_announcement_title),
|
|
||||||
showBetaLabel = true,
|
|
||||||
subTitle = stringResource(id = R.string.screen_space_announcement_subtitle),
|
|
||||||
iconStyle = BigIcon.Style.Default(
|
|
||||||
vectorIcon = CompoundIcons.SpaceSolid(),
|
|
||||||
usePrimaryTint = true,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun SpaceAnnouncementContent(
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = modifier.fillMaxSize(),
|
|
||||||
) {
|
|
||||||
InfoListOrganism(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
items = persistentListOf(
|
|
||||||
InfoListItem(
|
|
||||||
message = stringResource(id = R.string.screen_space_announcement_item1),
|
|
||||||
iconVector = CompoundIcons.VisibilityOn(),
|
|
||||||
),
|
|
||||||
InfoListItem(
|
|
||||||
message = stringResource(id = R.string.screen_space_announcement_item2),
|
|
||||||
iconVector = CompoundIcons.Email(),
|
|
||||||
),
|
|
||||||
InfoListItem(
|
|
||||||
message = stringResource(id = R.string.screen_space_announcement_item3),
|
|
||||||
iconVector = CompoundIcons.Search(),
|
|
||||||
),
|
|
||||||
InfoListItem(
|
|
||||||
message = stringResource(id = R.string.screen_space_announcement_item4),
|
|
||||||
iconVector = CompoundIcons.Explore(),
|
|
||||||
),
|
|
||||||
InfoListItem(
|
|
||||||
message = stringResource(id = R.string.screen_space_announcement_item5),
|
|
||||||
iconVector = CompoundIcons.Leave(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
textStyle = ElementTheme.typography.fontBodyLgMedium,
|
|
||||||
iconTint = ElementTheme.colors.iconSecondary,
|
|
||||||
iconSize = 24.dp
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(vertical = 16.dp),
|
|
||||||
text = stringResource(id = R.string.screen_space_announcement_notice),
|
|
||||||
style = ElementTheme.typography.fontBodyMdRegular,
|
|
||||||
color = ElementTheme.colors.textSecondary,
|
|
||||||
textAlign = TextAlign.Center,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun SpaceAnnouncementFooter(
|
|
||||||
onContinue: () -> Unit,
|
|
||||||
) {
|
|
||||||
ButtonColumnMolecule(
|
|
||||||
modifier = Modifier.padding(bottom = 8.dp)
|
|
||||||
) {
|
|
||||||
Button(
|
|
||||||
text = stringResource(id = CommonStrings.action_continue),
|
|
||||||
onClick = onContinue,
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@PreviewsDayNight
|
|
||||||
@Composable
|
|
||||||
internal fun SpaceAnnouncementViewPreview(@PreviewParameter(SpaceAnnouncementStateProvider::class) state: SpaceAnnouncementState) = ElementPreview {
|
|
||||||
SpaceAnnouncementView(
|
|
||||||
state = state,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -35,9 +35,10 @@ class DefaultAnnouncementStore(
|
||||||
|
|
||||||
override fun announcementStatusFlow(announcement: Announcement): Flow<AnnouncementStatus> {
|
override fun announcementStatusFlow(announcement: Announcement): Flow<AnnouncementStatus> {
|
||||||
val key = announcement.toKey()
|
val key = announcement.toKey()
|
||||||
|
// Announcement.Fullscreen.Space is disabled, consider it's shown
|
||||||
// For NewNotificationSound, a migration will set it to Show on application upgrade (see AppMigration08)
|
// For NewNotificationSound, a migration will set it to Show on application upgrade (see AppMigration08)
|
||||||
val defaultStatus = when (announcement) {
|
val defaultStatus = when (announcement) {
|
||||||
Announcement.Space -> AnnouncementStatus.NeverShown
|
Announcement.Fullscreen.Space -> AnnouncementStatus.Shown
|
||||||
Announcement.NewNotificationSound -> AnnouncementStatus.Shown
|
Announcement.NewNotificationSound -> AnnouncementStatus.Shown
|
||||||
}
|
}
|
||||||
return store.data.map { prefs ->
|
return store.data.map { prefs ->
|
||||||
|
|
@ -52,6 +53,6 @@ class DefaultAnnouncementStore(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Announcement.toKey() = when (this) {
|
private fun Announcement.toKey() = when (this) {
|
||||||
Announcement.Space -> spaceAnnouncementKey
|
Announcement.Fullscreen.Space -> spaceAnnouncementKey
|
||||||
Announcement.NewNotificationSound -> newNotificationSoundKey
|
Announcement.NewNotificationSound -> newNotificationSoundKey
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_space_announcement_item1">"作成または参加したスペースを表示できます"</string>
|
||||||
|
<string name="screen_space_announcement_item2">"スペースへの招待を受諾または拒否できます"</string>
|
||||||
|
<string name="screen_space_announcement_item3">"スペース内の参加可能なルームを検索できます"</string>
|
||||||
|
<string name="screen_space_announcement_item4">"公開スペースに参加できます"</string>
|
||||||
|
<string name="screen_space_announcement_item5">"参加したスペースを退出できます"</string>
|
||||||
|
<string name="screen_space_announcement_notice">"スペースの作成や管理, フィルター検索は近日実装予定です。"</string>
|
||||||
|
<string name="screen_space_announcement_subtitle">"ベータ版のスペースにようこそ。この最新のバージョンでは:"</string>
|
||||||
|
<string name="screen_space_announcement_title">"スペースの紹介"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -6,6 +6,6 @@
|
||||||
<string name="screen_space_announcement_item4">"加入公共空间"</string>
|
<string name="screen_space_announcement_item4">"加入公共空间"</string>
|
||||||
<string name="screen_space_announcement_item5">"离开你加入的所有空间"</string>
|
<string name="screen_space_announcement_item5">"离开你加入的所有空间"</string>
|
||||||
<string name="screen_space_announcement_notice">"筛选、创建及管理空间功能即将上线。"</string>
|
<string name="screen_space_announcement_notice">"筛选、创建及管理空间功能即将上线。"</string>
|
||||||
<string name="screen_space_announcement_subtitle">"欢迎使用 Spaces 测试版!使用首个版本,您可以:"</string>
|
<string name="screen_space_announcement_subtitle">"欢迎使用空间测试版!使用首个版本,您可以:"</string>
|
||||||
<string name="screen_space_announcement_title">"Spaces 简介"</string>
|
<string name="screen_space_announcement_title">"空间简介"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import io.element.android.features.announcement.impl.store.AnnouncementStatus
|
||||||
import io.element.android.features.announcement.impl.store.AnnouncementStore
|
import io.element.android.features.announcement.impl.store.AnnouncementStore
|
||||||
import io.element.android.features.announcement.impl.store.InMemoryAnnouncementStore
|
import io.element.android.features.announcement.impl.store.InMemoryAnnouncementStore
|
||||||
import io.element.android.tests.testutils.test
|
import io.element.android.tests.testutils.test
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
|
|
@ -23,25 +24,47 @@ class AnnouncementPresenterTest {
|
||||||
val presenter = createAnnouncementPresenter()
|
val presenter = createAnnouncementPresenter()
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val state = awaitItem()
|
val state = awaitItem()
|
||||||
assertThat(state.showSpaceAnnouncement).isFalse()
|
assertThat(state.announcement).isNull()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - showSpaceAnnouncement value depends on the value in the store`() = runTest {
|
fun `present - showFullscreen value depends on the value in the store`() = runTest {
|
||||||
val store = InMemoryAnnouncementStore()
|
val store = InMemoryAnnouncementStore()
|
||||||
val presenter = createAnnouncementPresenter(
|
val presenter = createAnnouncementPresenter(
|
||||||
announcementStore = store,
|
announcementStore = store,
|
||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val state = awaitItem()
|
val state = awaitItem()
|
||||||
assertThat(state.showSpaceAnnouncement).isFalse()
|
assertThat(state.announcement).isNull()
|
||||||
store.setAnnouncementStatus(Announcement.Space, AnnouncementStatus.Show)
|
store.setAnnouncementStatus(Announcement.Fullscreen.Space, AnnouncementStatus.Show)
|
||||||
val updatedState = awaitItem()
|
val updatedState = awaitItem()
|
||||||
assertThat(updatedState.showSpaceAnnouncement).isTrue()
|
assertThat(updatedState.announcement).isEqualTo(Announcement.Fullscreen.Space)
|
||||||
store.setAnnouncementStatus(Announcement.Space, AnnouncementStatus.Shown)
|
store.setAnnouncementStatus(Announcement.Fullscreen.Space, AnnouncementStatus.Shown)
|
||||||
val finalState = awaitItem()
|
val finalState = awaitItem()
|
||||||
assertThat(finalState.showSpaceAnnouncement).isFalse()
|
assertThat(finalState.announcement).isNull()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - continue event will mark the announcement as Shown`() = runTest {
|
||||||
|
val store = InMemoryAnnouncementStore()
|
||||||
|
val presenter = createAnnouncementPresenter(
|
||||||
|
announcementStore = store,
|
||||||
|
)
|
||||||
|
presenter.test {
|
||||||
|
val state = awaitItem()
|
||||||
|
assertThat(state.announcement).isNull()
|
||||||
|
store.setAnnouncementStatus(Announcement.Fullscreen.Space, AnnouncementStatus.Show)
|
||||||
|
val statusShow = store.announcementStatusFlow(Announcement.Fullscreen.Space).first()
|
||||||
|
assertThat(statusShow).isEqualTo(AnnouncementStatus.Show)
|
||||||
|
val updatedState = awaitItem()
|
||||||
|
assertThat(updatedState.announcement).isEqualTo(Announcement.Fullscreen.Space)
|
||||||
|
updatedState.eventSink(AnnouncementEvent.Continue(Announcement.Fullscreen.Space))
|
||||||
|
val statusShown = store.announcementStatusFlow(Announcement.Fullscreen.Space).first()
|
||||||
|
assertThat(statusShown).isEqualTo(AnnouncementStatus.Shown)
|
||||||
|
val finalState = awaitItem()
|
||||||
|
assertThat(finalState.announcement).isNull()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,31 +11,28 @@ package io.element.android.features.announcement.impl
|
||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.features.announcement.api.Announcement
|
import io.element.android.features.announcement.api.Announcement
|
||||||
import io.element.android.features.announcement.impl.spaces.SpaceAnnouncementState
|
|
||||||
import io.element.android.features.announcement.impl.spaces.aSpaceAnnouncementState
|
|
||||||
import io.element.android.features.announcement.impl.store.AnnouncementStatus
|
import io.element.android.features.announcement.impl.store.AnnouncementStatus
|
||||||
import io.element.android.features.announcement.impl.store.AnnouncementStore
|
import io.element.android.features.announcement.impl.store.AnnouncementStore
|
||||||
import io.element.android.features.announcement.impl.store.InMemoryAnnouncementStore
|
import io.element.android.features.announcement.impl.store.InMemoryAnnouncementStore
|
||||||
import io.element.android.libraries.architecture.Presenter
|
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class DefaultAnnouncementServiceTest {
|
class DefaultAnnouncementServiceTest {
|
||||||
@Test
|
@Test
|
||||||
fun `when showing Space announcement, space announcement is set to show only if it was never shown`() = runTest {
|
fun `when showing Fullscreen announcement, Fullscreen announcement is set to show only if it was never shown`() = runTest {
|
||||||
val announcementStore = InMemoryAnnouncementStore()
|
val announcementStore = InMemoryAnnouncementStore()
|
||||||
val sut = createDefaultAnnouncementService(
|
val sut = createDefaultAnnouncementService(
|
||||||
announcementStore = announcementStore,
|
announcementStore = announcementStore,
|
||||||
)
|
)
|
||||||
assertThat(announcementStore.announcementStatusFlow(Announcement.Space).first()).isEqualTo(AnnouncementStatus.NeverShown)
|
assertThat(announcementStore.announcementStatusFlow(Announcement.Fullscreen.Space).first()).isEqualTo(AnnouncementStatus.NeverShown)
|
||||||
sut.showAnnouncement(Announcement.Space)
|
sut.showAnnouncement(Announcement.Fullscreen.Space)
|
||||||
assertThat(announcementStore.announcementStatusFlow(Announcement.Space).first()).isEqualTo(AnnouncementStatus.Show)
|
assertThat(announcementStore.announcementStatusFlow(Announcement.Fullscreen.Space).first()).isEqualTo(AnnouncementStatus.Show)
|
||||||
// Simulate user close the announcement
|
// Simulate user close the announcement
|
||||||
sut.onAnnouncementDismissed(Announcement.Space)
|
sut.onAnnouncementDismissed(Announcement.Fullscreen.Space)
|
||||||
// Entering again the space tab should not change the value
|
// Entering again the space tab should not change the value
|
||||||
sut.showAnnouncement(Announcement.Space)
|
sut.showAnnouncement(Announcement.Fullscreen.Space)
|
||||||
assertThat(announcementStore.announcementStatusFlow(Announcement.Space).first()).isEqualTo(AnnouncementStatus.Shown)
|
assertThat(announcementStore.announcementStatusFlow(Announcement.Fullscreen.Space).first()).isEqualTo(AnnouncementStatus.Shown)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -62,11 +59,7 @@ class DefaultAnnouncementServiceTest {
|
||||||
)
|
)
|
||||||
sut.announcementsToShowFlow().test {
|
sut.announcementsToShowFlow().test {
|
||||||
assertThat(awaitItem()).isEmpty()
|
assertThat(awaitItem()).isEmpty()
|
||||||
announcementStore.setAnnouncementStatus(Announcement.Space, AnnouncementStatus.Show)
|
|
||||||
assertThat(awaitItem()).containsExactly(Announcement.Space)
|
|
||||||
announcementStore.setAnnouncementStatus(Announcement.NewNotificationSound, AnnouncementStatus.Show)
|
announcementStore.setAnnouncementStatus(Announcement.NewNotificationSound, AnnouncementStatus.Show)
|
||||||
assertThat(awaitItem()).containsExactly(Announcement.Space, Announcement.NewNotificationSound)
|
|
||||||
announcementStore.setAnnouncementStatus(Announcement.Space, AnnouncementStatus.Shown)
|
|
||||||
assertThat(awaitItem()).containsExactly(Announcement.NewNotificationSound)
|
assertThat(awaitItem()).containsExactly(Announcement.NewNotificationSound)
|
||||||
announcementStore.setAnnouncementStatus(Announcement.NewNotificationSound, AnnouncementStatus.Shown)
|
announcementStore.setAnnouncementStatus(Announcement.NewNotificationSound, AnnouncementStatus.Shown)
|
||||||
assertThat(awaitItem()).isEmpty()
|
assertThat(awaitItem()).isEmpty()
|
||||||
|
|
@ -75,11 +68,9 @@ class DefaultAnnouncementServiceTest {
|
||||||
|
|
||||||
private fun createDefaultAnnouncementService(
|
private fun createDefaultAnnouncementService(
|
||||||
announcementStore: AnnouncementStore = InMemoryAnnouncementStore(),
|
announcementStore: AnnouncementStore = InMemoryAnnouncementStore(),
|
||||||
announcementPresenter: Presenter<AnnouncementState> = Presenter { anAnnouncementState() },
|
announcementPresenter: AnnouncementPresenter = AnnouncementPresenter(announcementStore),
|
||||||
spaceAnnouncementPresenter: Presenter<SpaceAnnouncementState> = Presenter { aSpaceAnnouncementState() },
|
|
||||||
) = DefaultAnnouncementService(
|
) = DefaultAnnouncementService(
|
||||||
announcementStore = announcementStore,
|
announcementStore = announcementStore,
|
||||||
announcementPresenter = announcementPresenter,
|
announcementPresenter = announcementPresenter,
|
||||||
spaceAnnouncementPresenter = spaceAnnouncementPresenter,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,16 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.announcement.impl.spaces
|
package io.element.android.features.announcement.impl.fullscreen
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import io.element.android.features.announcement.api.Announcement
|
||||||
|
import io.element.android.features.announcement.impl.AnnouncementEvent
|
||||||
|
import io.element.android.features.announcement.impl.AnnouncementState
|
||||||
|
import io.element.android.features.announcement.impl.anAnnouncementState
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import io.element.android.tests.testutils.EventsRecorder
|
import io.element.android.tests.testutils.EventsRecorder
|
||||||
import io.element.android.tests.testutils.clickOn
|
import io.element.android.tests.testutils.clickOn
|
||||||
|
|
@ -22,39 +26,41 @@ import org.junit.rules.TestRule
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class SpaceAnnouncementViewTest {
|
class FullscreenAnnouncementViewTest {
|
||||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on back sends a SpaceAnnouncementEvents`() {
|
fun `clicking on back sends a AnnouncementEvent`() {
|
||||||
val eventsRecorder = EventsRecorder<SpaceAnnouncementEvents>()
|
val eventsRecorder = EventsRecorder<AnnouncementEvent>()
|
||||||
rule.setSpaceAnnouncementView(
|
rule.setFullscreenAnnouncementView(
|
||||||
aSpaceAnnouncementState(
|
anAnnouncementState(
|
||||||
|
announcement = Announcement.Fullscreen.Space,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.pressBackKey()
|
rule.pressBackKey()
|
||||||
eventsRecorder.assertSingle(SpaceAnnouncementEvents.Continue)
|
eventsRecorder.assertSingle(AnnouncementEvent.Continue(Announcement.Fullscreen.Space))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on Continue sends a SpaceAnnouncementEvents`() {
|
fun `clicking on Continue sends a AnnouncementEvent`() {
|
||||||
val eventsRecorder = EventsRecorder<SpaceAnnouncementEvents>()
|
val eventsRecorder = EventsRecorder<AnnouncementEvent>()
|
||||||
rule.setSpaceAnnouncementView(
|
rule.setFullscreenAnnouncementView(
|
||||||
aSpaceAnnouncementState(
|
anAnnouncementState(
|
||||||
|
announcement = Announcement.Fullscreen.Space,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_continue)
|
rule.clickOn(CommonStrings.action_continue)
|
||||||
eventsRecorder.assertSingle(SpaceAnnouncementEvents.Continue)
|
eventsRecorder.assertSingle(AnnouncementEvent.Continue(Announcement.Fullscreen.Space))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setSpaceAnnouncementView(
|
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setFullscreenAnnouncementView(
|
||||||
state: SpaceAnnouncementState,
|
state: AnnouncementState,
|
||||||
) {
|
) {
|
||||||
setContent {
|
setContent {
|
||||||
SpaceAnnouncementView(
|
FullscreenAnnouncementView(
|
||||||
state = state,
|
state = state,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2025 Element Creations Ltd.
|
|
||||||
* Copyright 2025 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
|
||||||
* Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.element.android.features.announcement.impl.spaces
|
|
||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
|
||||||
import io.element.android.features.announcement.api.Announcement
|
|
||||||
import io.element.android.features.announcement.impl.store.AnnouncementStatus
|
|
||||||
import io.element.android.features.announcement.impl.store.AnnouncementStore
|
|
||||||
import io.element.android.features.announcement.impl.store.InMemoryAnnouncementStore
|
|
||||||
import io.element.android.tests.testutils.test
|
|
||||||
import kotlinx.coroutines.flow.first
|
|
||||||
import kotlinx.coroutines.test.runTest
|
|
||||||
import org.junit.Test
|
|
||||||
|
|
||||||
class SpaceAnnouncementPresenterTest {
|
|
||||||
@Test
|
|
||||||
fun `present - when user continues, the store is updated`() = runTest {
|
|
||||||
val store = InMemoryAnnouncementStore()
|
|
||||||
val presenter = createSpaceAnnouncementPresenter(
|
|
||||||
announcementStore = store,
|
|
||||||
)
|
|
||||||
presenter.test {
|
|
||||||
assertThat(store.announcementStatusFlow(Announcement.Space).first()).isEqualTo(AnnouncementStatus.NeverShown)
|
|
||||||
val state = awaitItem()
|
|
||||||
state.eventSink(SpaceAnnouncementEvents.Continue)
|
|
||||||
assertThat(store.announcementStatusFlow(Announcement.Space).first()).isEqualTo(AnnouncementStatus.Shown)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createSpaceAnnouncementPresenter(
|
|
||||||
announcementStore: AnnouncementStore = InMemoryAnnouncementStore(),
|
|
||||||
) = SpaceAnnouncementPresenter(
|
|
||||||
announcementStore = announcementStore,
|
|
||||||
)
|
|
||||||
|
|
@ -14,10 +14,10 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
|
||||||
class InMemoryAnnouncementStore(
|
class InMemoryAnnouncementStore(
|
||||||
initialSpaceAnnouncementStatus: AnnouncementStatus = AnnouncementStatus.NeverShown,
|
initialFullscreenAnnouncementStatus: AnnouncementStatus = AnnouncementStatus.NeverShown,
|
||||||
initialNewNotificationSoundAnnouncementStatus: AnnouncementStatus = AnnouncementStatus.NeverShown,
|
initialNewNotificationSoundAnnouncementStatus: AnnouncementStatus = AnnouncementStatus.NeverShown,
|
||||||
) : AnnouncementStore {
|
) : AnnouncementStore {
|
||||||
private val spaceAnnouncement = MutableStateFlow(initialSpaceAnnouncementStatus)
|
private val fullScreenAnnouncement = MutableStateFlow(initialFullscreenAnnouncementStatus)
|
||||||
private val newNotificationSoundAnnouncement = MutableStateFlow(initialNewNotificationSoundAnnouncementStatus)
|
private val newNotificationSoundAnnouncement = MutableStateFlow(initialNewNotificationSoundAnnouncementStatus)
|
||||||
|
|
||||||
override suspend fun setAnnouncementStatus(announcement: Announcement, status: AnnouncementStatus) {
|
override suspend fun setAnnouncementStatus(announcement: Announcement, status: AnnouncementStatus) {
|
||||||
|
|
@ -29,12 +29,12 @@ class InMemoryAnnouncementStore(
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun reset() {
|
override suspend fun reset() {
|
||||||
spaceAnnouncement.value = AnnouncementStatus.NeverShown
|
fullScreenAnnouncement.value = AnnouncementStatus.NeverShown
|
||||||
newNotificationSoundAnnouncement.value = AnnouncementStatus.NeverShown
|
newNotificationSoundAnnouncement.value = AnnouncementStatus.NeverShown
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Announcement.toMutableStateFlow() = when (this) {
|
private fun Announcement.toMutableStateFlow() = when (this) {
|
||||||
Announcement.Space -> spaceAnnouncement
|
is Announcement.Fullscreen -> fullScreenAnnouncement
|
||||||
Announcement.NewNotificationSound -> newNotificationSoundAnnouncement
|
Announcement.NewNotificationSound -> newNotificationSoundAnnouncement
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@ dependencies {
|
||||||
implementation(projects.libraries.core)
|
implementation(projects.libraries.core)
|
||||||
implementation(projects.libraries.designsystem)
|
implementation(projects.libraries.designsystem)
|
||||||
implementation(projects.libraries.featureflag.api)
|
implementation(projects.libraries.featureflag.api)
|
||||||
implementation(projects.libraries.matrix.impl)
|
implementation(projects.libraries.matrix.api)
|
||||||
implementation(projects.libraries.matrixmedia.api)
|
implementation(projects.libraries.matrixmedia.api)
|
||||||
implementation(projects.libraries.network)
|
implementation(projects.libraries.network)
|
||||||
implementation(projects.libraries.preferences.api)
|
implementation(projects.libraries.preferences.api)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="call_foreground_service_channel_title_android">"通話中"</string>
|
||||||
|
<string name="call_foreground_service_message_android">"タップして通話に戻る"</string>
|
||||||
|
<string name="call_foreground_service_title_android">"☎️ 通話中"</string>
|
||||||
|
<string name="call_invalid_audio_device_bluetooth_devices_disabled">"Element CallはこのAndroidバージョンにおいて、Bluetoothオーディオデバイスの使用をサポートしていません。別のオーディオデバイスを選択してください。"</string>
|
||||||
|
<string name="screen_incoming_call_subtitle_android">"Element Call の着信"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="call_foreground_service_channel_title_android">"Cuộc gọi đang diễn ra"</string>
|
||||||
|
<string name="call_foreground_service_message_android">"Nhấn để quay lại cuộc gọi."</string>
|
||||||
|
<string name="call_foreground_service_title_android">"☎️ Cuộc gọi đang diễn ra"</string>
|
||||||
|
<string name="call_invalid_audio_device_bluetooth_devices_disabled">"Ứng dụng Element Call không hỗ trợ sử dụng thiết bị âm thanh Bluetooth trên phiên bản Android này. Vui lòng chọn thiết bị âm thanh khác."</string>
|
||||||
|
<string name="screen_incoming_call_subtitle_android">"Cuộc gọi Element đến"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -6,9 +6,8 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.call.test
|
package io.element.android.features.call.impl.notifications
|
||||||
|
|
||||||
import io.element.android.features.call.impl.notifications.CallNotificationData
|
|
||||||
import io.element.android.libraries.matrix.api.core.EventId
|
import io.element.android.libraries.matrix.api.core.EventId
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
|
|
@ -15,11 +15,11 @@ import androidx.test.platform.app.InstrumentationRegistry
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.features.call.api.CallType
|
import io.element.android.features.call.api.CallType
|
||||||
import io.element.android.features.call.impl.notifications.RingingCallNotificationCreator
|
import io.element.android.features.call.impl.notifications.RingingCallNotificationCreator
|
||||||
|
import io.element.android.features.call.impl.notifications.aCallNotificationData
|
||||||
import io.element.android.features.call.impl.utils.ActiveCall
|
import io.element.android.features.call.impl.utils.ActiveCall
|
||||||
import io.element.android.features.call.impl.utils.CallState
|
import io.element.android.features.call.impl.utils.CallState
|
||||||
import io.element.android.features.call.impl.utils.DefaultActiveCallManager
|
import io.element.android.features.call.impl.utils.DefaultActiveCallManager
|
||||||
import io.element.android.features.call.impl.utils.DefaultCurrentCallService
|
import io.element.android.features.call.impl.utils.DefaultCurrentCallService
|
||||||
import io.element.android.features.call.test.aCallNotificationData
|
|
||||||
import io.element.android.libraries.matrix.api.core.EventId
|
import io.element.android.libraries.matrix.api.core.EventId
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ dependencies {
|
||||||
implementation(projects.libraries.core)
|
implementation(projects.libraries.core)
|
||||||
|
|
||||||
api(projects.features.call.api)
|
api(projects.features.call.api)
|
||||||
implementation(projects.features.call.impl)
|
|
||||||
implementation(projects.libraries.matrix.api)
|
implementation(projects.libraries.matrix.api)
|
||||||
implementation(projects.libraries.matrix.test)
|
implementation(projects.libraries.matrix.test)
|
||||||
implementation(projects.tests.testutils)
|
implementation(projects.tests.testutils)
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,6 @@ dependencies {
|
||||||
implementation(projects.libraries.mediaupload.api)
|
implementation(projects.libraries.mediaupload.api)
|
||||||
implementation(projects.libraries.permissions.api)
|
implementation(projects.libraries.permissions.api)
|
||||||
implementation(projects.libraries.previewutils)
|
implementation(projects.libraries.previewutils)
|
||||||
implementation(projects.libraries.usersearch.impl)
|
|
||||||
implementation(projects.services.analytics.api)
|
implementation(projects.services.analytics.api)
|
||||||
implementation(libs.coil.compose)
|
implementation(libs.coil.compose)
|
||||||
implementation(projects.libraries.featureflag.api)
|
implementation(projects.libraries.featureflag.api)
|
||||||
|
|
@ -52,7 +51,6 @@ dependencies {
|
||||||
testImplementation(projects.libraries.mediapickers.test)
|
testImplementation(projects.libraries.mediapickers.test)
|
||||||
testImplementation(projects.libraries.mediaupload.test)
|
testImplementation(projects.libraries.mediaupload.test)
|
||||||
testImplementation(projects.libraries.permissions.test)
|
testImplementation(projects.libraries.permissions.test)
|
||||||
testImplementation(projects.libraries.usersearch.test)
|
|
||||||
testImplementation(projects.features.startchat.test)
|
testImplementation(projects.features.startchat.test)
|
||||||
testImplementation(projects.libraries.featureflag.test)
|
testImplementation(projects.libraries.featureflag.test)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,6 @@ class ConfigureRoomPresenter(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): ConfigureRoomState {
|
override fun present(): ConfigureRoomState {
|
||||||
val canAddRoomToSpace by featureFlagService.isFeatureEnabledFlow(FeatureFlags.CreateSpaces).collectAsState(false)
|
|
||||||
val cameraPermissionState = cameraPermissionPresenter.present()
|
val cameraPermissionState = cameraPermissionPresenter.present()
|
||||||
val createRoomConfig by dataStore.getCreateRoomConfigFlow().collectAsState()
|
val createRoomConfig by dataStore.getCreateRoomConfigFlow().collectAsState()
|
||||||
val homeserverName = remember { matrixClient.userIdServerName() }
|
val homeserverName = remember { matrixClient.userIdServerName() }
|
||||||
|
|
@ -113,12 +112,8 @@ class ConfigureRoomPresenter(
|
||||||
}
|
}
|
||||||
|
|
||||||
var spaces by remember { mutableStateOf<ImmutableList<SpaceRoom>>(persistentListOf()) }
|
var spaces by remember { mutableStateOf<ImmutableList<SpaceRoom>>(persistentListOf()) }
|
||||||
LaunchedEffect(canAddRoomToSpace) {
|
LaunchedEffect(Unit) {
|
||||||
spaces = if (canAddRoomToSpace) {
|
spaces = matrixClient.spaceService.editableSpaces().getOrElse { emptyList() }.toImmutableList()
|
||||||
matrixClient.spaceService.editableSpaces().getOrElse { emptyList() }.toImmutableList()
|
|
||||||
} else {
|
|
||||||
persistentListOf()
|
|
||||||
}
|
|
||||||
val parentSpace = spaces.find { it.roomId == initialParentSpaceId }
|
val parentSpace = spaces.find { it.roomId == initialParentSpaceId }
|
||||||
parentSpace?.let {
|
parentSpace?.let {
|
||||||
dataStore.setParentSpace(parentSpace = parentSpace, updateVisibility = true)
|
dataStore.setParentSpace(parentSpace = parentSpace, updateVisibility = true)
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,34 @@
|
||||||
<string name="screen_create_room_action_create_room">"Nuova stanza"</string>
|
<string name="screen_create_room_action_create_room">"Nuova stanza"</string>
|
||||||
<string name="screen_create_room_add_people_title">"Invita persone"</string>
|
<string name="screen_create_room_add_people_title">"Invita persone"</string>
|
||||||
<string name="screen_create_room_error_creating_room">"Si è verificato un errore durante la creazione della stanza"</string>
|
<string name="screen_create_room_error_creating_room">"Si è verificato un errore durante la creazione della stanza"</string>
|
||||||
<string name="screen_create_room_private_option_description">"Solo le persone invitate possono accedere a questa stanza. Tutti i messaggi sono cifrati end-to-end."</string>
|
<string name="screen_create_room_error_creating_space">"Non è stato possibile creare lo spazio a causa di un errore sconosciuto. Riprova più tardi."</string>
|
||||||
|
<string name="screen_create_room_name_placeholder">"Aggiungi nome…"</string>
|
||||||
|
<string name="screen_create_room_new_room_title">"Nuova stanza"</string>
|
||||||
|
<string name="screen_create_room_new_space_title">"Nuovo spazio"</string>
|
||||||
|
<string name="screen_create_room_private_option_description">"Possono partecipare solo le persone invitate."</string>
|
||||||
|
<string name="screen_create_room_private_option_title">"Privato"</string>
|
||||||
<string name="screen_create_room_public_option_description">"Chiunque può trovare questa stanza.
|
<string name="screen_create_room_public_option_description">"Chiunque può trovare questa stanza.
|
||||||
Puoi modificarlo in qualsiasi momento nelle impostazioni della stanza."</string>
|
Puoi modificarlo in qualsiasi momento nelle impostazioni della stanza."</string>
|
||||||
<string name="screen_create_room_room_access_section_knocking_option_description">"Chiunque può chiedere di entrare nella stanza, ma un amministratore o un moderatore dovrà accettare la richiesta"</string>
|
<string name="screen_create_room_public_option_short_description">"Chiunque può partecipare."</string>
|
||||||
<string name="screen_create_room_room_access_section_knocking_option_title">"Chiedi di entrare"</string>
|
<string name="screen_create_room_public_option_title">"Pubblico"</string>
|
||||||
<string name="screen_create_room_room_access_section_public_option_description">"Chiunque può entrare in questa stanza"</string>
|
<string name="screen_create_room_room_access_section_knocking_option_description">"Chiunque può chiedere di partecipare, ma un amministratore o un moderatore deve accettare la richiesta."</string>
|
||||||
<string name="screen_create_room_room_address_section_footer">"Affinché questa stanza sia visibile nell\'elenco delle stanze pubbliche, è necessario un indirizzo della stanza."</string>
|
<string name="screen_create_room_room_access_section_knocking_option_title">"Consenti di chiedere di partecipare"</string>
|
||||||
<string name="screen_create_room_room_address_section_title">"Indirizzo della stanza"</string>
|
<string name="screen_create_room_room_access_section_knocking_restricted_option_description">"Chiunque sia membro di %1$s può partecipare, mentre tutti gli altri devono richiedere l\'accesso."</string>
|
||||||
|
<string name="screen_create_room_room_access_section_knocking_restricted_option_title">"Richiedi accesso"</string>
|
||||||
|
<string name="screen_create_room_room_access_section_private_option_description">"Possono partecipare solo le persone invitate."</string>
|
||||||
|
<string name="screen_create_room_room_access_section_private_option_title">"Privato"</string>
|
||||||
|
<string name="screen_create_room_room_access_section_public_option_description">"Chiunque può partecipare."</string>
|
||||||
|
<string name="screen_create_room_room_access_section_public_option_title">"Pubblico"</string>
|
||||||
|
<string name="screen_create_room_room_access_section_restricted_option_description">"Chiunque in %1$s può unirsi."</string>
|
||||||
|
<string name="screen_create_room_room_access_section_restricted_option_title">"Standard"</string>
|
||||||
|
<string name="screen_create_room_room_access_section_title">"Chi ha accesso"</string>
|
||||||
|
<string name="screen_create_room_room_address_section_footer">"Avrai bisogno di un indirizzo per renderlo visibile nella directory pubblica."</string>
|
||||||
|
<string name="screen_create_room_room_address_section_title">"Indirizzo"</string>
|
||||||
<string name="screen_create_room_room_visibility_section_title">"Visibilità della stanza"</string>
|
<string name="screen_create_room_room_visibility_section_title">"Visibilità della stanza"</string>
|
||||||
|
<string name="screen_create_room_space_selection_no_space_description">"(nessuno spazio)"</string>
|
||||||
|
<string name="screen_create_room_space_selection_no_space_option">"Non aggiungere a uno spazio"</string>
|
||||||
|
<string name="screen_create_room_space_selection_no_space_title">"Nessuno spazio selezionato"</string>
|
||||||
|
<string name="screen_create_room_space_selection_sheet_title">"Aggiungi allo spazio"</string>
|
||||||
<string name="screen_create_room_topic_label">"Argomento (facoltativo)"</string>
|
<string name="screen_create_room_topic_label">"Argomento (facoltativo)"</string>
|
||||||
|
<string name="screen_create_room_topic_placeholder">"Aggiungi descrizione…"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_create_room_action_create_room">"新しいルーム"</string>
|
||||||
|
<string name="screen_create_room_add_people_title">"ユーザーを招待"</string>
|
||||||
|
<string name="screen_create_room_error_creating_room">"ルームの作成中に問題が発生しました"</string>
|
||||||
|
<string name="screen_create_room_error_creating_space">"不明な問題のためスペースを作成できませんでした。再度お試しください。"</string>
|
||||||
|
<string name="screen_create_room_name_placeholder">"名前を追加…"</string>
|
||||||
|
<string name="screen_create_room_new_room_title">"新しいルーム"</string>
|
||||||
|
<string name="screen_create_room_new_space_title">"新しいスペース"</string>
|
||||||
|
<string name="screen_create_room_private_option_description">"招待されたユーザーのみ参加できます。"</string>
|
||||||
|
<string name="screen_create_room_private_option_title">"非公開"</string>
|
||||||
|
<string name="screen_create_room_public_option_description">"ルームは全世界に公開されます。
|
||||||
|
ルーム設定でいつでも変更できます。"</string>
|
||||||
|
<string name="screen_create_room_public_option_short_description">"誰でも参加できます。"</string>
|
||||||
|
<string name="screen_create_room_public_option_title">"公開"</string>
|
||||||
|
<string name="screen_create_room_room_access_section_knocking_option_description">"誰でも参加できますが、管理者またはモデレーターの承認が必要です。"</string>
|
||||||
|
<string name="screen_create_room_room_access_section_knocking_option_title">"参加の要求を許可"</string>
|
||||||
|
<string name="screen_create_room_room_access_section_knocking_restricted_option_description">"%1$s にいる全員が参加することができますが、事前に参加の要求をする必要があります。"</string>
|
||||||
|
<string name="screen_create_room_room_access_section_knocking_restricted_option_title">"参加を要求"</string>
|
||||||
|
<string name="screen_create_room_room_access_section_private_option_description">"招待されたユーザーのみが参加できます。"</string>
|
||||||
|
<string name="screen_create_room_room_access_section_private_option_title">"非公開"</string>
|
||||||
|
<string name="screen_create_room_room_access_section_public_option_description">"誰でも参加できます。"</string>
|
||||||
|
<string name="screen_create_room_room_access_section_public_option_title">"公開"</string>
|
||||||
|
<string name="screen_create_room_room_access_section_restricted_option_description">"%1$s にいる全員が参加することができます。"</string>
|
||||||
|
<string name="screen_create_room_room_access_section_restricted_option_title">"スタンダード"</string>
|
||||||
|
<string name="screen_create_room_room_access_section_title">"参加できるユーザー"</string>
|
||||||
|
<string name="screen_create_room_room_address_section_footer">"公開ディレクトリで自分を見つけられるようにするには、アドレスが必要です。"</string>
|
||||||
|
<string name="screen_create_room_room_address_section_title">"アドレス"</string>
|
||||||
|
<string name="screen_create_room_room_visibility_section_title">"ルームの公開度"</string>
|
||||||
|
<string name="screen_create_room_space_selection_no_space_description">"(スペースなし)"</string>
|
||||||
|
<string name="screen_create_room_space_selection_no_space_option">"スペースに追加しない"</string>
|
||||||
|
<string name="screen_create_room_space_selection_no_space_title">"スペースが選択されていません"</string>
|
||||||
|
<string name="screen_create_room_space_selection_sheet_title">"スペースに追加"</string>
|
||||||
|
<string name="screen_create_room_topic_label">"トピック (任意)"</string>
|
||||||
|
<string name="screen_create_room_topic_placeholder">"説明を追加…"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_create_room_action_create_room">"Phòng mới"</string>
|
||||||
|
<string name="screen_create_room_add_people_title">"Mời ai đó"</string>
|
||||||
|
<string name="screen_create_room_error_creating_room">"Đã xảy ra lỗi khi tạo phòng."</string>
|
||||||
|
<string name="screen_create_room_private_option_description">"Chỉ những người được mời mới có thể tham gia."</string>
|
||||||
|
<string name="screen_create_room_public_option_description">"Bất kỳ ai cũng có thể tìm thấy phòng này.
|
||||||
|
Bạn có thể thay đổi cài đặt phòng bất cứ lúc nào."</string>
|
||||||
|
<string name="screen_create_room_topic_label">"Chủ đề (tùy chọn)"</string>
|
||||||
|
<string name="screen_create_room_topic_placeholder">"Thêm mô tả…"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_deactivate_account_confirmation_dialog_content">"アカウントを無効化することを再度確認します。この操作は元に戻せません。"</string>
|
||||||
|
<string name="screen_deactivate_account_delete_all_messages">"メッセージをすべて削除"</string>
|
||||||
|
<string name="screen_deactivate_account_delete_all_messages_notice">"注意: 新しいユーザーには断片的な会話が表示されます"</string>
|
||||||
|
<string name="screen_deactivate_account_description">"アカウントを無効化することは %1$s であり、次の変化が生じます:"</string>
|
||||||
|
<string name="screen_deactivate_account_description_bold_part">"不可逆"</string>
|
||||||
|
<string name="screen_deactivate_account_list_item_1">"アカウントを %1$s (再度ログイン不可, 同一のIDを再利用不可)"</string>
|
||||||
|
<string name="screen_deactivate_account_list_item_1_bold_part">"恒久的に無効化する"</string>
|
||||||
|
<string name="screen_deactivate_account_list_item_2">"すべてのチャットルームから退出します。"</string>
|
||||||
|
<string name="screen_deactivate_account_list_item_3">"アカウント提供元サーバーからアカウント情報を削除します。"</string>
|
||||||
|
<string name="screen_deactivate_account_list_item_4">"あなたの会話は、既存ユーザーには引き続き表示されますが、新規ユーザーには表示されなくなります。"</string>
|
||||||
|
<string name="screen_deactivate_account_title">"アカウントを無効化"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_deactivate_account_delete_all_messages">"Xóa tất cả tin nhắn của tôi"</string>
|
||||||
|
<string name="screen_deactivate_account_delete_all_messages_notice">"Cảnh báo: Người dùng sau này có thể thấy các cuộc trò chuyện chưa hoàn chỉnh."</string>
|
||||||
|
<string name="screen_deactivate_account_list_item_4">"Tin nhắn của bạn vẫn sẽ hiển thị cho người dùng đã đăng ký nhưng sẽ không hiển thị cho người dùng mới hoặc chưa đăng ký nếu bạn chọn xóa chúng."</string>
|
||||||
|
<string name="screen_deactivate_account_title">"Vô hiệu hóa tài khoản"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -2,8 +2,8 @@
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="screen_identity_confirmation_cannot_confirm">"Nemůžete potvrdit?"</string>
|
<string name="screen_identity_confirmation_cannot_confirm">"Nemůžete potvrdit?"</string>
|
||||||
<string name="screen_identity_confirmation_create_new_recovery_key">"Vytvoření nového klíče pro obnovení"</string>
|
<string name="screen_identity_confirmation_create_new_recovery_key">"Vytvoření nového klíče pro obnovení"</string>
|
||||||
<string name="screen_identity_confirmation_subtitle">"Ověřte toto zařízení a nastavte zabezpečené zasílání zpráv."</string>
|
<string name="screen_identity_confirmation_subtitle">"Vyberte způsob ověření pro nastavení zabezpečeného zasílání zpráv."</string>
|
||||||
<string name="screen_identity_confirmation_title">"Potvrďte, že jste to vy"</string>
|
<string name="screen_identity_confirmation_title">"Potvrďte svou digitální identitu"</string>
|
||||||
<string name="screen_identity_confirmation_use_another_device">"Použít jiné zařízení"</string>
|
<string name="screen_identity_confirmation_use_another_device">"Použít jiné zařízení"</string>
|
||||||
<string name="screen_identity_confirmation_use_recovery_key">"Použít klíč pro obnovení"</string>
|
<string name="screen_identity_confirmation_use_recovery_key">"Použít klíč pro obnovení"</string>
|
||||||
<string name="screen_identity_confirmed_subtitle">"Nyní můžete bezpečně číst nebo odesílat zprávy, a kdokoli, s kým chatujete, může tomuto zařízení důvěřovat."</string>
|
<string name="screen_identity_confirmed_subtitle">"Nyní můžete bezpečně číst nebo odesílat zprávy, a kdokoli, s kým chatujete, může tomuto zařízení důvěřovat."</string>
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="screen_identity_confirmation_cannot_confirm">"Kan ikke bekræfte?"</string>
|
<string name="screen_identity_confirmation_cannot_confirm">"Kan ikke bekræfte?"</string>
|
||||||
<string name="screen_identity_confirmation_create_new_recovery_key">"Opret en ny gendannelsesnøgle"</string>
|
<string name="screen_identity_confirmation_create_new_recovery_key">"Opret en ny gendannelsesnøgle"</string>
|
||||||
<string name="screen_identity_confirmation_subtitle">"Verificér denne enhed for at konfigurere sikre meddelelser."</string>
|
<string name="screen_identity_confirmation_subtitle">"Vælg, hvordan du vil verificere dig for at konfigurere sikre beskeder."</string>
|
||||||
<string name="screen_identity_confirmation_title">"Bekræft din identitet"</string>
|
<string name="screen_identity_confirmation_title">"Bekræft din digitale identitet"</string>
|
||||||
<string name="screen_identity_confirmation_use_another_device">"Brug en anden enhed"</string>
|
<string name="screen_identity_confirmation_use_another_device">"Brug en anden enhed"</string>
|
||||||
<string name="screen_identity_confirmation_use_recovery_key">"Brug gendannelsesnøgle"</string>
|
<string name="screen_identity_confirmation_use_recovery_key">"Brug gendannelsesnøgle"</string>
|
||||||
<string name="screen_identity_confirmed_subtitle">"Nu kan du læse eller sende beskeder sikkert, og enhver du samtaler med kan også stole på denne enhed."</string>
|
<string name="screen_identity_confirmed_subtitle">"Nu kan du læse eller sende beskeder sikkert, og enhver du samtaler med kan også stole på denne enhed."</string>
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="screen_identity_confirmation_cannot_confirm">"Non puoi confermare?"</string>
|
<string name="screen_identity_confirmation_cannot_confirm">"Non puoi confermare?"</string>
|
||||||
<string name="screen_identity_confirmation_create_new_recovery_key">"Crea una nuova chiave di recupero"</string>
|
<string name="screen_identity_confirmation_create_new_recovery_key">"Crea una nuova chiave di recupero"</string>
|
||||||
<string name="screen_identity_confirmation_subtitle">"Verifica questo dispositivo per segnare i tuoi messaggi come sicuri."</string>
|
<string name="screen_identity_confirmation_subtitle">"Scegli come effettuare la verifica per configurare la messaggistica sicura."</string>
|
||||||
<string name="screen_identity_confirmation_title">"Conferma la tua identità"</string>
|
<string name="screen_identity_confirmation_title">"Conferma la tua identità digitale"</string>
|
||||||
<string name="screen_identity_confirmation_use_another_device">"Usa un altro dispositivo"</string>
|
<string name="screen_identity_confirmation_use_another_device">"Usa un altro dispositivo"</string>
|
||||||
<string name="screen_identity_confirmation_use_recovery_key">"Usa la chiave di recupero"</string>
|
<string name="screen_identity_confirmation_use_recovery_key">"Usa la chiave di recupero"</string>
|
||||||
<string name="screen_identity_confirmed_subtitle">"Ora puoi leggere o inviare messaggi in tutta sicurezza e anche chi chatta con te può fidarsi di questo dispositivo."</string>
|
<string name="screen_identity_confirmed_subtitle">"Ora puoi leggere o inviare messaggi in tutta sicurezza e anche chi chatta con te può fidarsi di questo dispositivo."</string>
|
||||||
|
|
|
||||||
15
features/ftue/impl/src/main/res/values-ja/translations.xml
Normal file
15
features/ftue/impl/src/main/res/values-ja/translations.xml
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_identity_confirmation_cannot_confirm">"認証できませんか?"</string>
|
||||||
|
<string name="screen_identity_confirmation_create_new_recovery_key">"回復鍵を新規作成します"</string>
|
||||||
|
<string name="screen_identity_confirmation_subtitle">"安全なメッセージを設定するための検証方法を選択してください。"</string>
|
||||||
|
<string name="screen_identity_confirmation_title">"デジタルIDの認証"</string>
|
||||||
|
<string name="screen_identity_confirmation_use_another_device">"他の端末を使用"</string>
|
||||||
|
<string name="screen_identity_confirmation_use_recovery_key">"回復鍵を使用"</string>
|
||||||
|
<string name="screen_identity_confirmed_subtitle">"メッセージのやり取りを安全に行えるようになりました。他のユーザーはこの端末を信頼できます。"</string>
|
||||||
|
<string name="screen_identity_confirmed_title">"検証済みの端末"</string>
|
||||||
|
<string name="screen_identity_use_another_device">"他の端末を使用"</string>
|
||||||
|
<string name="screen_identity_waiting_on_other_device">"一方の端末を待機中…"</string>
|
||||||
|
<string name="screen_notification_optin_subtitle">"設定は後で変更することができます。"</string>
|
||||||
|
<string name="screen_notification_optin_title">"メッセージを見逃さないため通知を許可"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="screen_identity_confirmation_cannot_confirm">"Не можете подтвердить?"</string>
|
<string name="screen_identity_confirmation_cannot_confirm">"Не можете подтвердить?"</string>
|
||||||
<string name="screen_identity_confirmation_create_new_recovery_key">"Создайте новый ключ восстановления"</string>
|
<string name="screen_identity_confirmation_create_new_recovery_key">"Создайте новый ключ восстановления"</string>
|
||||||
<string name="screen_identity_confirmation_subtitle">"Подтвердите это устройство, чтобы настроить безопасный обмен сообщениями."</string>
|
<string name="screen_identity_confirmation_subtitle">"Выберите способ подтверждения для настройки защищенного обмена сообщениями."</string>
|
||||||
<string name="screen_identity_confirmation_title">"Подтвердите личность"</string>
|
<string name="screen_identity_confirmation_title">"Подтвердите личность"</string>
|
||||||
<string name="screen_identity_confirmation_use_another_device">"Использовать другое устройство"</string>
|
<string name="screen_identity_confirmation_use_another_device">"Использовать другое устройство"</string>
|
||||||
<string name="screen_identity_confirmation_use_recovery_key">"Использовать ключ восстановления"</string>
|
<string name="screen_identity_confirmation_use_recovery_key">"Использовать ключ восстановления"</string>
|
||||||
|
|
|
||||||
10
features/ftue/impl/src/main/res/values-vi/translations.xml
Normal file
10
features/ftue/impl/src/main/res/values-vi/translations.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_identity_confirmation_subtitle">"Chọn phương thức xác minh để bật nhắn tin bảo mật."</string>
|
||||||
|
<string name="screen_identity_confirmation_title">"Xác nhận danh tính kỹ thuật số của bạn"</string>
|
||||||
|
<string name="screen_identity_confirmed_subtitle">"Giờ đây bạn có thể đọc và gửi tin nhắn một cách an toàn, và những người bạn trò chuyện cũng có thể tin tưởng thiết bị này."</string>
|
||||||
|
<string name="screen_identity_confirmed_title">"Thiết bị được xác thực"</string>
|
||||||
|
<string name="screen_identity_waiting_on_other_device">"Đang chờ trên thiết bị khác…"</string>
|
||||||
|
<string name="screen_notification_optin_subtitle">"Bạn có thể thay đổi cài đặt sau."</string>
|
||||||
|
<string name="screen_notification_optin_title">"Cho phép thông báo để không bỏ lỡ bất kỳ tin nhắn nào"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -2,8 +2,8 @@
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="screen_identity_confirmation_cannot_confirm">"无法确认?"</string>
|
<string name="screen_identity_confirmation_cannot_confirm">"无法确认?"</string>
|
||||||
<string name="screen_identity_confirmation_create_new_recovery_key">"创建新的恢复密钥"</string>
|
<string name="screen_identity_confirmation_create_new_recovery_key">"创建新的恢复密钥"</string>
|
||||||
<string name="screen_identity_confirmation_subtitle">"验证此设备以开始安全地收发消息。"</string>
|
<string name="screen_identity_confirmation_subtitle">"选择验证方式以设置安全的消息传输。"</string>
|
||||||
<string name="screen_identity_confirmation_title">"确认这是你"</string>
|
<string name="screen_identity_confirmation_title">"确认您的数字身份"</string>
|
||||||
<string name="screen_identity_confirmation_use_another_device">"使用其他设备"</string>
|
<string name="screen_identity_confirmation_use_another_device">"使用其他设备"</string>
|
||||||
<string name="screen_identity_confirmation_use_recovery_key">"使用恢复密钥"</string>
|
<string name="screen_identity_confirmation_use_recovery_key">"使用恢复密钥"</string>
|
||||||
<string name="screen_identity_confirmed_subtitle">"现在,您可以安全地阅读或发送消息,与您聊天的人也会信任此设备。"</string>
|
<string name="screen_identity_confirmed_subtitle">"现在,您可以安全地阅读或发送消息,与您聊天的人也会信任此设备。"</string>
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,6 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import dev.zacsweers.metro.Inject
|
import dev.zacsweers.metro.Inject
|
||||||
import io.element.android.features.announcement.api.Announcement
|
|
||||||
import io.element.android.features.announcement.api.AnnouncementService
|
|
||||||
import io.element.android.features.home.impl.roomlist.RoomListState
|
import io.element.android.features.home.impl.roomlist.RoomListState
|
||||||
import io.element.android.features.home.impl.spaces.HomeSpacesState
|
import io.element.android.features.home.impl.spaces.HomeSpacesState
|
||||||
import io.element.android.features.logout.api.direct.DirectLogoutState
|
import io.element.android.features.logout.api.direct.DirectLogoutState
|
||||||
|
|
@ -47,7 +45,6 @@ class HomePresenter(
|
||||||
private val logoutPresenter: Presenter<DirectLogoutState>,
|
private val logoutPresenter: Presenter<DirectLogoutState>,
|
||||||
private val rageshakeFeatureAvailability: RageshakeFeatureAvailability,
|
private val rageshakeFeatureAvailability: RageshakeFeatureAvailability,
|
||||||
private val sessionStore: SessionStore,
|
private val sessionStore: SessionStore,
|
||||||
private val announcementService: AnnouncementService,
|
|
||||||
) : Presenter<HomeState> {
|
) : Presenter<HomeState> {
|
||||||
private val currentUserWithNeighborsBuilder = CurrentUserWithNeighborsBuilder()
|
private val currentUserWithNeighborsBuilder = CurrentUserWithNeighborsBuilder()
|
||||||
|
|
||||||
|
|
@ -82,10 +79,7 @@ class HomePresenter(
|
||||||
|
|
||||||
fun handleEvent(event: HomeEvent) {
|
fun handleEvent(event: HomeEvent) {
|
||||||
when (event) {
|
when (event) {
|
||||||
is HomeEvent.SelectHomeNavigationBarItem -> coroutineState.launch {
|
is HomeEvent.SelectHomeNavigationBarItem -> {
|
||||||
if (event.item == HomeNavigationBarItem.Spaces) {
|
|
||||||
announcementService.showAnnouncement(Announcement.Space)
|
|
||||||
}
|
|
||||||
currentHomeNavigationBarItemOrdinal = event.item.ordinal
|
currentHomeNavigationBarItemOrdinal = event.item.ordinal
|
||||||
}
|
}
|
||||||
is HomeEvent.SwitchToAccount -> coroutineState.launch {
|
is HomeEvent.SwitchToAccount -> coroutineState.launch {
|
||||||
|
|
@ -94,12 +88,6 @@ class HomePresenter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(homeSpacesState.canCreateSpaces, homeSpacesState.spaceRooms.isEmpty()) {
|
|
||||||
// If the flag to create spaces is disabled and the last space is left, ensure that the Chat view is rendered.
|
|
||||||
if (!homeSpacesState.canCreateSpaces && homeSpacesState.spaceRooms.isEmpty()) {
|
|
||||||
currentHomeNavigationBarItemOrdinal = HomeNavigationBarItem.Chats.ordinal
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val snackbarMessage by snackbarDispatcher.collectSnackbarMessageAsState()
|
val snackbarMessage by snackbarDispatcher.collectSnackbarMessageAsState()
|
||||||
return HomeState(
|
return HomeState(
|
||||||
currentUserAndNeighbors = currentUserAndNeighbors,
|
currentUserAndNeighbors = currentUserAndNeighbors,
|
||||||
|
|
|
||||||
|
|
@ -34,5 +34,4 @@ data class HomeState(
|
||||||
) {
|
) {
|
||||||
val isBackHandlerEnabled = currentHomeNavigationBarItem != HomeNavigationBarItem.Chats || roomListState.spaceFiltersState is SpaceFiltersState.Selected
|
val isBackHandlerEnabled = currentHomeNavigationBarItem != HomeNavigationBarItem.Chats || roomListState.spaceFiltersState is SpaceFiltersState.Selected
|
||||||
val displayRoomListFilters = currentHomeNavigationBarItem == HomeNavigationBarItem.Chats && roomListState.displayFilters
|
val displayRoomListFilters = currentHomeNavigationBarItem == HomeNavigationBarItem.Chats && roomListState.displayFilters
|
||||||
val showNavigationBar = homeSpacesState.canCreateSpaces || homeSpacesState.spaceRooms.isNotEmpty()
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -199,7 +199,6 @@ private fun HomeScaffold(
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
floatingActionButton = {
|
floatingActionButton = {
|
||||||
if (state.showNavigationBar) {
|
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
HomeBottomBar(
|
HomeBottomBar(
|
||||||
currentHomeNavigationBarItem = state.currentHomeNavigationBarItem,
|
currentHomeNavigationBarItem = state.currentHomeNavigationBarItem,
|
||||||
|
|
@ -222,27 +221,19 @@ private fun HomeScaffold(
|
||||||
state.eventSink(HomeEvent.SelectHomeNavigationBarItem(item))
|
state.eventSink(HomeEvent.SelectHomeNavigationBarItem(item))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
floatingActionButton = when (state.currentHomeNavigationBarItem) {
|
floatingActionButton = {
|
||||||
|
when (state.currentHomeNavigationBarItem) {
|
||||||
HomeNavigationBarItem.Chats -> {
|
HomeNavigationBarItem.Chats -> {
|
||||||
{
|
|
||||||
HomeFloatingActionButton(onStartChatClick, CommonStrings.action_create_room)
|
HomeFloatingActionButton(onStartChatClick, CommonStrings.action_create_room)
|
||||||
}
|
}
|
||||||
}
|
HomeNavigationBarItem.Spaces -> {
|
||||||
HomeNavigationBarItem.Spaces -> if (state.homeSpacesState.canCreateSpaces) {
|
|
||||||
{
|
|
||||||
HomeFloatingActionButton(onCreateSpaceClick, CommonStrings.action_create_space)
|
HomeFloatingActionButton(onCreateSpaceClick, CommonStrings.action_create_space)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// No FAB for spaces if we cannot create spaces
|
|
||||||
null
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
} else {
|
|
||||||
HomeFloatingActionButton(onStartChatClick, CommonStrings.action_create_room)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
floatingActionButtonPosition = if (state.showNavigationBar) FabPosition.Center else FabPosition.End,
|
floatingActionButtonPosition = FabPosition.Center,
|
||||||
content = { padding ->
|
content = { padding ->
|
||||||
val contentPadding = PaddingValues(
|
val contentPadding = PaddingValues(
|
||||||
bottom = 96.dp,
|
bottom = 96.dp,
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,7 @@ import io.element.android.libraries.designsystem.theme.roomListRoomMessage
|
||||||
import io.element.android.libraries.designsystem.theme.roomListRoomMessageDate
|
import io.element.android.libraries.designsystem.theme.roomListRoomMessageDate
|
||||||
import io.element.android.libraries.designsystem.theme.roomListRoomName
|
import io.element.android.libraries.designsystem.theme.roomListRoomName
|
||||||
import io.element.android.libraries.designsystem.theme.unreadIndicator
|
import io.element.android.libraries.designsystem.theme.unreadIndicator
|
||||||
|
import io.element.android.libraries.matrix.api.notification.CallIntent
|
||||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||||
import io.element.android.libraries.matrix.ui.components.InviteSenderView
|
import io.element.android.libraries.matrix.ui.components.InviteSenderView
|
||||||
import io.element.android.libraries.matrix.ui.model.InviteSender
|
import io.element.android.libraries.matrix.ui.model.InviteSender
|
||||||
|
|
@ -349,6 +350,7 @@ private fun MessagePreviewAndIndicatorRow(
|
||||||
if (room.hasRoomCall) {
|
if (room.hasRoomCall) {
|
||||||
OnGoingCallIcon(
|
OnGoingCallIcon(
|
||||||
color = tint,
|
color = tint,
|
||||||
|
isAudio = room.activeCallIntent == CallIntent.AUDIO
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (room.userDefinedNotificationMode == RoomNotificationMode.MUTE) {
|
if (room.userDefinedNotificationMode == RoomNotificationMode.MUTE) {
|
||||||
|
|
@ -398,10 +400,11 @@ private fun InviteNameAndIndicatorRow(
|
||||||
@Composable
|
@Composable
|
||||||
private fun OnGoingCallIcon(
|
private fun OnGoingCallIcon(
|
||||||
color: Color,
|
color: Color,
|
||||||
|
isAudio: Boolean
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
modifier = Modifier.size(16.dp),
|
modifier = Modifier.size(16.dp),
|
||||||
imageVector = CompoundIcons.VideoCallSolid(),
|
imageVector = if (isAudio) CompoundIcons.VoiceCallSolid() else CompoundIcons.VideoCallSolid(),
|
||||||
contentDescription = stringResource(CommonStrings.a11y_notifications_ongoing_call),
|
contentDescription = stringResource(CommonStrings.a11y_notifications_ongoing_call),
|
||||||
tint = color,
|
tint = color,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import io.element.android.libraries.dateformatter.api.DateFormatter
|
||||||
import io.element.android.libraries.dateformatter.api.DateFormatterMode
|
import io.element.android.libraries.dateformatter.api.DateFormatterMode
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||||
import io.element.android.libraries.eventformatter.api.RoomLatestEventFormatter
|
import io.element.android.libraries.eventformatter.api.RoomLatestEventFormatter
|
||||||
|
import io.element.android.libraries.matrix.api.room.CallIntentConsensus
|
||||||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||||
import io.element.android.libraries.matrix.api.room.isDm
|
import io.element.android.libraries.matrix.api.room.isDm
|
||||||
import io.element.android.libraries.matrix.api.roomlist.LatestEventValue
|
import io.element.android.libraries.matrix.api.roomlist.LatestEventValue
|
||||||
|
|
@ -50,6 +51,11 @@ class RoomListRoomSummaryFactory(
|
||||||
avatarData = avatarData,
|
avatarData = avatarData,
|
||||||
userDefinedNotificationMode = roomInfo.userDefinedNotificationMode,
|
userDefinedNotificationMode = roomInfo.userDefinedNotificationMode,
|
||||||
hasRoomCall = roomInfo.hasRoomCall,
|
hasRoomCall = roomInfo.hasRoomCall,
|
||||||
|
activeCallIntent = when (val consensus = roomInfo.activeCallIntentConsensus) {
|
||||||
|
is CallIntentConsensus.Full -> consensus.callIntent
|
||||||
|
is CallIntentConsensus.Partial -> consensus.callIntent
|
||||||
|
CallIntentConsensus.None -> null
|
||||||
|
},
|
||||||
isDirect = roomInfo.isDirect,
|
isDirect = roomInfo.isDirect,
|
||||||
isFavorite = roomInfo.isFavorite,
|
isFavorite = roomInfo.isFavorite,
|
||||||
inviteSender = roomInfo.inviter?.toInviteSender(),
|
inviteSender = roomInfo.inviter?.toInviteSender(),
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import io.element.android.features.invite.api.InviteData
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
import io.element.android.libraries.matrix.api.notification.CallIntent
|
||||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||||
import io.element.android.libraries.matrix.ui.model.InviteSender
|
import io.element.android.libraries.matrix.ui.model.InviteSender
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
@ -33,6 +34,7 @@ data class RoomListRoomSummary(
|
||||||
val avatarData: AvatarData,
|
val avatarData: AvatarData,
|
||||||
val userDefinedNotificationMode: RoomNotificationMode?,
|
val userDefinedNotificationMode: RoomNotificationMode?,
|
||||||
val hasRoomCall: Boolean,
|
val hasRoomCall: Boolean,
|
||||||
|
val activeCallIntent: CallIntent?,
|
||||||
val isDirect: Boolean,
|
val isDirect: Boolean,
|
||||||
val isDm: Boolean,
|
val isDm: Boolean,
|
||||||
val isFavorite: Boolean,
|
val isFavorite: Boolean,
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
|
import io.element.android.libraries.matrix.api.notification.CallIntent
|
||||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||||
import io.element.android.libraries.matrix.ui.model.InviteSender
|
import io.element.android.libraries.matrix.ui.model.InviteSender
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
@ -132,6 +133,14 @@ open class RoomListRoomSummaryProvider : PreviewParameterProvider<RoomListRoomSu
|
||||||
listOf(
|
listOf(
|
||||||
aRoomListRoomSummary(latestEvent = LatestEvent.Sending("A sending message")),
|
aRoomListRoomSummary(latestEvent = LatestEvent.Sending("A sending message")),
|
||||||
aRoomListRoomSummary(latestEvent = LatestEvent.Error),
|
aRoomListRoomSummary(latestEvent = LatestEvent.Error),
|
||||||
|
),
|
||||||
|
listOf(
|
||||||
|
aRoomListRoomSummary(
|
||||||
|
name = "Active voice call",
|
||||||
|
latestEvent = LatestEvent.Synced("No activity, call"),
|
||||||
|
hasRoomCall = true,
|
||||||
|
activeCallIntent = CallIntent.AUDIO
|
||||||
|
),
|
||||||
)
|
)
|
||||||
).flatten()
|
).flatten()
|
||||||
}
|
}
|
||||||
|
|
@ -158,6 +167,7 @@ internal fun aRoomListRoomSummary(
|
||||||
timestamp: String? = latestEvent.takeIf { it !is LatestEvent.None }?.let { "88:88" },
|
timestamp: String? = latestEvent.takeIf { it !is LatestEvent.None }?.let { "88:88" },
|
||||||
notificationMode: RoomNotificationMode? = null,
|
notificationMode: RoomNotificationMode? = null,
|
||||||
hasRoomCall: Boolean = false,
|
hasRoomCall: Boolean = false,
|
||||||
|
activeCallIntent: CallIntent? = null,
|
||||||
avatarData: AvatarData = AvatarData(id, name, size = AvatarSize.RoomListItem),
|
avatarData: AvatarData = AvatarData(id, name, size = AvatarSize.RoomListItem),
|
||||||
isDirect: Boolean = false,
|
isDirect: Boolean = false,
|
||||||
isDm: Boolean = false,
|
isDm: Boolean = false,
|
||||||
|
|
@ -181,6 +191,7 @@ internal fun aRoomListRoomSummary(
|
||||||
avatarData = avatarData,
|
avatarData = avatarData,
|
||||||
userDefinedNotificationMode = notificationMode,
|
userDefinedNotificationMode = notificationMode,
|
||||||
hasRoomCall = hasRoomCall,
|
hasRoomCall = hasRoomCall,
|
||||||
|
activeCallIntent = activeCallIntent,
|
||||||
isDirect = isDirect,
|
isDirect = isDirect,
|
||||||
isDm = isDm,
|
isDm = isDm,
|
||||||
isFavorite = isFavorite,
|
isFavorite = isFavorite,
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,6 @@ import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import dev.zacsweers.metro.Inject
|
import dev.zacsweers.metro.Inject
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
import io.element.android.libraries.matrix.api.spaces.SpaceServiceFilter
|
import io.element.android.libraries.matrix.api.spaces.SpaceServiceFilter
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
@ -27,20 +25,15 @@ import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
class SpaceFiltersPresenter(
|
class SpaceFiltersPresenter(
|
||||||
private val featureFlagService: FeatureFlagService,
|
|
||||||
private val matrixClient: MatrixClient,
|
private val matrixClient: MatrixClient,
|
||||||
) : Presenter<SpaceFiltersState> {
|
) : Presenter<SpaceFiltersState> {
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): SpaceFiltersState {
|
override fun present(): SpaceFiltersState {
|
||||||
val isFeatureEnabled by featureFlagService
|
|
||||||
.isFeatureEnabledFlow(FeatureFlags.RoomListSpaceFilters)
|
|
||||||
.collectAsState(initial = false)
|
|
||||||
|
|
||||||
val availableFilters by remember {
|
val availableFilters by remember {
|
||||||
matrixClient.spaceService.spaceFiltersFlow.map { it.toImmutableList() }
|
matrixClient.spaceService.spaceFiltersFlow.map { it.toImmutableList() }
|
||||||
}.collectAsState(initial = persistentListOf())
|
}.collectAsState(initial = persistentListOf())
|
||||||
|
|
||||||
if (!isFeatureEnabled || availableFilters.isEmpty()) {
|
if (availableFilters.isEmpty()) {
|
||||||
return SpaceFiltersState.Disabled
|
return SpaceFiltersState.Disabled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,6 @@ import androidx.compose.runtime.remember
|
||||||
import dev.zacsweers.metro.Inject
|
import dev.zacsweers.metro.Inject
|
||||||
import io.element.android.features.invite.api.SeenInvitesStore
|
import io.element.android.features.invite.api.SeenInvitesStore
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
import io.element.android.libraries.matrix.ui.safety.rememberHideInvitesAvatar
|
import io.element.android.libraries.matrix.ui.safety.rememberHideInvitesAvatar
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
@ -29,11 +27,9 @@ import kotlinx.coroutines.flow.map
|
||||||
class HomeSpacesPresenter(
|
class HomeSpacesPresenter(
|
||||||
private val client: MatrixClient,
|
private val client: MatrixClient,
|
||||||
private val seenInvitesStore: SeenInvitesStore,
|
private val seenInvitesStore: SeenInvitesStore,
|
||||||
private val featureFlagsService: FeatureFlagService,
|
|
||||||
) : Presenter<HomeSpacesState> {
|
) : Presenter<HomeSpacesState> {
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): HomeSpacesState {
|
override fun present(): HomeSpacesState {
|
||||||
val canCreateSpaces by featureFlagsService.isFeatureEnabledFlow(FeatureFlags.CreateSpaces).collectAsState(false)
|
|
||||||
val hideInvitesAvatar by client.rememberHideInvitesAvatar()
|
val hideInvitesAvatar by client.rememberHideInvitesAvatar()
|
||||||
val spaceRooms by remember {
|
val spaceRooms by remember {
|
||||||
client.spaceService.topLevelSpacesFlow.map { it.toImmutableList() }
|
client.spaceService.topLevelSpacesFlow.map { it.toImmutableList() }
|
||||||
|
|
@ -52,7 +48,6 @@ class HomeSpacesPresenter(
|
||||||
spaceRooms = spaceRooms,
|
spaceRooms = spaceRooms,
|
||||||
seenSpaceInvites = seenSpaceInvites,
|
seenSpaceInvites = seenSpaceInvites,
|
||||||
hideInvitesAvatar = hideInvitesAvatar,
|
hideInvitesAvatar = hideInvitesAvatar,
|
||||||
canCreateSpaces = canCreateSpaces,
|
|
||||||
// TODO enable once we can link to the screen to explore public spaces
|
// TODO enable once we can link to the screen to explore public spaces
|
||||||
canExploreSpaces = false,
|
canExploreSpaces = false,
|
||||||
eventSink = ::handleEvent,
|
eventSink = ::handleEvent,
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ data class HomeSpacesState(
|
||||||
val spaceRooms: ImmutableList<SpaceRoom>,
|
val spaceRooms: ImmutableList<SpaceRoom>,
|
||||||
val seenSpaceInvites: ImmutableSet<RoomId>,
|
val seenSpaceInvites: ImmutableSet<RoomId>,
|
||||||
val hideInvitesAvatar: Boolean,
|
val hideInvitesAvatar: Boolean,
|
||||||
val canCreateSpaces: Boolean,
|
|
||||||
val canExploreSpaces: Boolean,
|
val canExploreSpaces: Boolean,
|
||||||
val eventSink: (HomeSpacesEvents) -> Unit,
|
val eventSink: (HomeSpacesEvents) -> Unit,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -30,17 +30,9 @@ open class HomeSpacesStateProvider : PreviewParameterProvider<HomeSpacesState> {
|
||||||
),
|
),
|
||||||
spaceRooms = aListOfSpaceRooms(),
|
spaceRooms = aListOfSpaceRooms(),
|
||||||
),
|
),
|
||||||
aHomeSpacesState(
|
|
||||||
space = CurrentSpace.Space(
|
|
||||||
spaceRoom = aSpaceRoom(roomId = RoomId("!mySpace:example.com"))
|
|
||||||
),
|
|
||||||
spaceRooms = aListOfSpaceRooms(),
|
|
||||||
canCreateSpaces = false,
|
|
||||||
),
|
|
||||||
aHomeSpacesState(
|
aHomeSpacesState(
|
||||||
space = CurrentSpace.Root,
|
space = CurrentSpace.Root,
|
||||||
spaceRooms = emptyList(),
|
spaceRooms = emptyList(),
|
||||||
canCreateSpaces = true,
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -50,7 +42,6 @@ internal fun aHomeSpacesState(
|
||||||
spaceRooms: List<SpaceRoom> = aListOfSpaceRooms(),
|
spaceRooms: List<SpaceRoom> = aListOfSpaceRooms(),
|
||||||
seenSpaceInvites: Set<RoomId> = emptySet(),
|
seenSpaceInvites: Set<RoomId> = emptySet(),
|
||||||
hideInvitesAvatar: Boolean = false,
|
hideInvitesAvatar: Boolean = false,
|
||||||
canCreateSpaces: Boolean = true,
|
|
||||||
canExploreSpaces: Boolean = true,
|
canExploreSpaces: Boolean = true,
|
||||||
eventSink: (HomeSpacesEvents) -> Unit = {},
|
eventSink: (HomeSpacesEvents) -> Unit = {},
|
||||||
) = HomeSpacesState(
|
) = HomeSpacesState(
|
||||||
|
|
@ -58,7 +49,6 @@ internal fun aHomeSpacesState(
|
||||||
spaceRooms = spaceRooms.toImmutableList(),
|
spaceRooms = spaceRooms.toImmutableList(),
|
||||||
seenSpaceInvites = seenSpaceInvites.toImmutableSet(),
|
seenSpaceInvites = seenSpaceInvites.toImmutableSet(),
|
||||||
hideInvitesAvatar = hideInvitesAvatar,
|
hideInvitesAvatar = hideInvitesAvatar,
|
||||||
canCreateSpaces = canCreateSpaces,
|
|
||||||
canExploreSpaces = canExploreSpaces,
|
canExploreSpaces = canExploreSpaces,
|
||||||
eventSink = eventSink,
|
eventSink = eventSink,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ fun HomeSpacesView(
|
||||||
onExploreClick: () -> Unit,
|
onExploreClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
if (state.canCreateSpaces && state.spaceRooms.isEmpty()) {
|
if (state.spaceRooms.isEmpty()) {
|
||||||
EmptySpaceHomeView(
|
EmptySpaceHomeView(
|
||||||
modifier = modifier.padding(contentPadding),
|
modifier = modifier.padding(contentPadding),
|
||||||
onCreateSpaceClick = onCreateSpaceClick,
|
onCreateSpaceClick = onCreateSpaceClick,
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,9 @@
|
||||||
<string name="banner_battery_optimization_title_android">"Nepřicházejí vám oznámení?"</string>
|
<string name="banner_battery_optimization_title_android">"Nepřicházejí vám oznámení?"</string>
|
||||||
<string name="banner_new_sound_message">"Váš zvuk oznámení byl aktualizován – je jasnější, rychlejší a méně rušivý."</string>
|
<string name="banner_new_sound_message">"Váš zvuk oznámení byl aktualizován – je jasnější, rychlejší a méně rušivý."</string>
|
||||||
<string name="banner_new_sound_title">"Aktualizovali jsme vaše zvuky"</string>
|
<string name="banner_new_sound_title">"Aktualizovali jsme vaše zvuky"</string>
|
||||||
<string name="banner_set_up_recovery_content">"Vygenerujte nový klíč pro obnovení, který lze použít k obnovení historie šifrovaných zpráv v případě, že ztratíte přístup ke svým zařízením."</string>
|
<string name="banner_set_up_recovery_content">"Vaše chaty jsou automaticky zálohovány pomocí koncového šifrování. Chcete-li tuto zálohu obnovit a zachovat si svou digitální identitu v případě, že ztratíte přístup ke všem svým zařízením, budete potřebovat svůj klíč pro obnovení."</string>
|
||||||
<string name="banner_set_up_recovery_submit">"Nastavení obnovy"</string>
|
<string name="banner_set_up_recovery_submit">"Získat klíč pro obnovení"</string>
|
||||||
<string name="banner_set_up_recovery_title">"Nastavení obnovy"</string>
|
<string name="banner_set_up_recovery_title">"Zálohujte své chaty"</string>
|
||||||
<string name="confirm_recovery_key_banner_message">"Potvrďte klíč pro obnovení, abyste zachovali přístup k úložišti klíčů a historii zpráv."</string>
|
<string name="confirm_recovery_key_banner_message">"Potvrďte klíč pro obnovení, abyste zachovali přístup k úložišti klíčů a historii zpráv."</string>
|
||||||
<string name="confirm_recovery_key_banner_primary_button_title">"Zadejte klíč pro obnovení"</string>
|
<string name="confirm_recovery_key_banner_primary_button_title">"Zadejte klíč pro obnovení"</string>
|
||||||
<string name="confirm_recovery_key_banner_secondary_button_title">"Zapomněli jste klíč pro obnovení?"</string>
|
<string name="confirm_recovery_key_banner_secondary_button_title">"Zapomněli jste klíč pro obnovení?"</string>
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,9 @@
|
||||||
<string name="banner_battery_optimization_title_android">"Modtager du ikke notifikationer?"</string>
|
<string name="banner_battery_optimization_title_android">"Modtager du ikke notifikationer?"</string>
|
||||||
<string name="banner_new_sound_message">"Dit notifikationsping er blevet opdateret – tydeligere, hurtigere og mindre forstyrrende."</string>
|
<string name="banner_new_sound_message">"Dit notifikationsping er blevet opdateret – tydeligere, hurtigere og mindre forstyrrende."</string>
|
||||||
<string name="banner_new_sound_title">"Vi har opdateret dine lyde"</string>
|
<string name="banner_new_sound_title">"Vi har opdateret dine lyde"</string>
|
||||||
<string name="banner_set_up_recovery_content">"Gendan din kryptografiske identitet og meddelelseshistorik med en gendannelsesnøgle, hvis du har mistet alle dine eksisterende enheder."</string>
|
<string name="banner_set_up_recovery_content">"Dine chats sikkerhedskopieres automatisk med end-to-end-kryptering. For at kunne gendanne denne sikkerhedskopi og bevare din digitale identitet, hvis du mister adgang til alle dine enheder, får du brug for din gendannelsesnøgle."</string>
|
||||||
<string name="banner_set_up_recovery_submit">"Opsæt gendannelse"</string>
|
<string name="banner_set_up_recovery_submit">"Hent gendannelsesnøgle"</string>
|
||||||
<string name="banner_set_up_recovery_title">"Konfigurer gendannelse for at beskytte din konto"</string>
|
<string name="banner_set_up_recovery_title">"Sikkerhedskopier dine samtaler"</string>
|
||||||
<string name="confirm_recovery_key_banner_message">"Bekræft din gendannelsesnøgle for at bevare adgangen til nøglelager og meddelelseshistorik."</string>
|
<string name="confirm_recovery_key_banner_message">"Bekræft din gendannelsesnøgle for at bevare adgangen til nøglelager og meddelelseshistorik."</string>
|
||||||
<string name="confirm_recovery_key_banner_primary_button_title">"Indtast din gendannelsesnøgle"</string>
|
<string name="confirm_recovery_key_banner_primary_button_title">"Indtast din gendannelsesnøgle"</string>
|
||||||
<string name="confirm_recovery_key_banner_secondary_button_title">"Har du glemt din gendannelsesnøgle?"</string>
|
<string name="confirm_recovery_key_banner_secondary_button_title">"Har du glemt din gendannelsesnøgle?"</string>
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,9 @@
|
||||||
<string name="banner_battery_optimization_title_android">"Le notifiche non arrivano?"</string>
|
<string name="banner_battery_optimization_title_android">"Le notifiche non arrivano?"</string>
|
||||||
<string name="banner_new_sound_message">"Il ping delle notifiche è stato aggiornato: ora è più chiaro, più rapido e meno fastidioso."</string>
|
<string name="banner_new_sound_message">"Il ping delle notifiche è stato aggiornato: ora è più chiaro, più rapido e meno fastidioso."</string>
|
||||||
<string name="banner_new_sound_title">"Abbiamo rinnovato i tuoi suoni"</string>
|
<string name="banner_new_sound_title">"Abbiamo rinnovato i tuoi suoni"</string>
|
||||||
<string name="banner_set_up_recovery_content">"Recupera la tua identità crittografica e la cronologia dei messaggi con una chiave di recupero se hai perso tutti i tuoi dispositivi."</string>
|
<string name="banner_set_up_recovery_content">"Le tue conversazioni vengono automaticamente salvate con crittografia end-to-end. Per ripristinare questo backup e conservare la tua identità digitale quando perdi l\'accesso a tutti i tuoi dispositivi, avrai bisogno della tua chiave di recupero."</string>
|
||||||
<string name="banner_set_up_recovery_submit">"Configura il recupero"</string>
|
<string name="banner_set_up_recovery_submit">"Ottieni la chiave di recupero"</string>
|
||||||
<string name="banner_set_up_recovery_title">"Configura il ripristino"</string>
|
<string name="banner_set_up_recovery_title">"Esegui il backup delle tue conversazioni"</string>
|
||||||
<string name="confirm_recovery_key_banner_message">"Conferma la chiave di recupero per mantenere l\'accesso all\'archiviazione delle chiavi e alla cronologia dei messaggi."</string>
|
<string name="confirm_recovery_key_banner_message">"Conferma la chiave di recupero per mantenere l\'accesso all\'archiviazione delle chiavi e alla cronologia dei messaggi."</string>
|
||||||
<string name="confirm_recovery_key_banner_primary_button_title">"Inserisci la tua chiave di recupero"</string>
|
<string name="confirm_recovery_key_banner_primary_button_title">"Inserisci la tua chiave di recupero"</string>
|
||||||
<string name="confirm_recovery_key_banner_secondary_button_title">"Hai dimenticato la chiave di recupero?"</string>
|
<string name="confirm_recovery_key_banner_secondary_button_title">"Hai dimenticato la chiave di recupero?"</string>
|
||||||
|
|
@ -50,6 +50,7 @@ Non hai messaggi non letti!"</string>
|
||||||
<string name="screen_roomlist_mark_as_read">"Segna come letto"</string>
|
<string name="screen_roomlist_mark_as_read">"Segna come letto"</string>
|
||||||
<string name="screen_roomlist_mark_as_unread">"Segna come non letto"</string>
|
<string name="screen_roomlist_mark_as_unread">"Segna come non letto"</string>
|
||||||
<string name="screen_roomlist_tombstoned_room_description">"Questa stanza è stata aggiornata"</string>
|
<string name="screen_roomlist_tombstoned_room_description">"Questa stanza è stata aggiornata"</string>
|
||||||
|
<string name="screen_roomlist_your_spaces">"I tuoi spazi"</string>
|
||||||
<string name="session_verification_banner_message">"Sembra che tu stia usando un nuovo dispositivo. Verificati con un altro dispositivo per accedere ai tuoi messaggi cifrati."</string>
|
<string name="session_verification_banner_message">"Sembra che tu stia usando un nuovo dispositivo. Verificati con un altro dispositivo per accedere ai tuoi messaggi cifrati."</string>
|
||||||
<string name="session_verification_banner_title">"Verifica che sei tu"</string>
|
<string name="session_verification_banner_title">"Verifica che sei tu"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue