Merge branch 'release/0.4.13' into main
This commit is contained in:
commit
cf7c8b8f15
641 changed files with 8769 additions and 2517 deletions
2
.github/workflows/nightlyReports.yml
vendored
2
.github/workflows/nightlyReports.yml
vendored
|
|
@ -33,7 +33,7 @@ jobs:
|
||||||
run: ./gradlew verifyPaparazziDebug $CI_GRADLE_ARG_PROPERTIES
|
run: ./gradlew verifyPaparazziDebug $CI_GRADLE_ARG_PROPERTIES
|
||||||
|
|
||||||
- name: 📈 Generate kover report and verify coverage
|
- name: 📈 Generate kover report and verify coverage
|
||||||
run: ./gradlew :app:koverXmlReportGplayDebug :app:koverHtmlReportGplayDebug :app:koverVerifyGplayDebug $CI_GRADLE_ARG_PROPERTIES
|
run: ./gradlew :app:koverXmlReportGplayDebug :app:koverHtmlReportGplayDebug :app:koverVerifyAll $CI_GRADLE_ARG_PROPERTIES
|
||||||
|
|
||||||
- name: ✅ Upload kover report
|
- name: ✅ Upload kover report
|
||||||
if: always()
|
if: always()
|
||||||
|
|
|
||||||
147
.github/workflows/quality.yml
vendored
147
.github/workflows/quality.yml
vendored
|
|
@ -10,7 +10,7 @@ on:
|
||||||
# Enrich gradle.properties for CI/CD
|
# Enrich gradle.properties for CI/CD
|
||||||
env:
|
env:
|
||||||
GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx6g -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError" -Dkotlin.incremental=false -XX:+UseParallelGC
|
GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx6g -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError" -Dkotlin.incremental=false -XX:+UseParallelGC
|
||||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace -PpreDexEnable=false --max-workers 8 --no-daemon --warn
|
CI_GRADLE_ARG_PROPERTIES: --stacktrace -PpreDexEnable=false --max-workers 8 --no-daemon
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
checkScript:
|
checkScript:
|
||||||
|
|
@ -33,12 +33,13 @@ jobs:
|
||||||
- name: Search for invalid screenshot files
|
- name: Search for invalid screenshot files
|
||||||
run: ./tools/test/checkInvalidScreenshots.py
|
run: ./tools/test/checkInvalidScreenshots.py
|
||||||
|
|
||||||
check:
|
# Code checks
|
||||||
name: Project Check Suite
|
konsist:
|
||||||
|
name: Konsist tests
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
# Allow all jobs on main and develop. Just one per PR.
|
# Allow all jobs on main and develop. Just one per PR.
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.ref == 'refs/heads/main' && format('check-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('check-develop-{0}', github.sha) || format('check-{0}', github.ref) }}
|
group: ${{ github.ref == 'refs/heads/main' && format('check-konsist-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('check-konsist-develop-{0}', github.sha) || format('check-konsist-{0}', github.ref) }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
@ -55,8 +56,40 @@ jobs:
|
||||||
uses: gradle/actions/setup-gradle@v3
|
uses: gradle/actions/setup-gradle@v3
|
||||||
with:
|
with:
|
||||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
- name: Run code quality check suite
|
- name: Run Konsist tests
|
||||||
run: ./gradlew runQualityChecks $CI_GRADLE_ARG_PROPERTIES
|
run: ./gradlew :tests:konsist:testDebugUnitTest $CI_GRADLE_ARG_PROPERTIES --no-daemon
|
||||||
|
- name: Upload reports
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: konsist-report
|
||||||
|
path: |
|
||||||
|
**/build/reports/**/*.*
|
||||||
|
|
||||||
|
lint:
|
||||||
|
name: Android lint check
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
# Allow all jobs on main and develop. Just one per PR.
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.ref == 'refs/heads/main' && format('check-lint-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('check-lint-develop-{0}', github.sha) || format('check-lint-{0}', github.ref) }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
# Ensure we are building the branch and not the branch after being merged on develop
|
||||||
|
# https://github.com/actions/checkout/issues/881
|
||||||
|
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }}
|
||||||
|
- name: Use JDK 17
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
|
java-version: '17'
|
||||||
|
- name: Configure gradle
|
||||||
|
uses: gradle/actions/setup-gradle@v3
|
||||||
|
with:
|
||||||
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
|
- name: Run lint
|
||||||
|
run: ./gradlew :app:lintGplayDebug :app:lintFdroidDebug $CI_GRADLE_ARG_PROPERTIES
|
||||||
- name: Upload reports
|
- name: Upload reports
|
||||||
if: always()
|
if: always()
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
|
|
@ -64,6 +97,108 @@ jobs:
|
||||||
name: linting-report
|
name: linting-report
|
||||||
path: |
|
path: |
|
||||||
**/build/reports/**/*.*
|
**/build/reports/**/*.*
|
||||||
|
|
||||||
|
detekt:
|
||||||
|
name: Detekt checks
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
# Allow all jobs on main and develop. Just one per PR.
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.ref == 'refs/heads/main' && format('check-detekt-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('check-detekt-develop-{0}', github.sha) || format('check-detekt-{0}', github.ref) }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
# Ensure we are building the branch and not the branch after being merged on develop
|
||||||
|
# https://github.com/actions/checkout/issues/881
|
||||||
|
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }}
|
||||||
|
- name: Use JDK 17
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
|
java-version: '17'
|
||||||
|
- name: Configure gradle
|
||||||
|
uses: gradle/actions/setup-gradle@v3
|
||||||
|
with:
|
||||||
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
|
- name: Run Detekt
|
||||||
|
run: ./gradlew detekt $CI_GRADLE_ARG_PROPERTIES --no-daemon
|
||||||
|
- name: Upload reports
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: detekt-report
|
||||||
|
path: |
|
||||||
|
**/build/reports/**/*.*
|
||||||
|
|
||||||
|
ktlint:
|
||||||
|
name: Ktlint checks
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
# Allow all jobs on main and develop. Just one per PR.
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.ref == 'refs/heads/main' && format('check-ktlint-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('check-ktlint-develop-{0}', github.sha) || format('check-ktlint-{0}', github.ref) }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
# Ensure we are building the branch and not the branch after being merged on develop
|
||||||
|
# https://github.com/actions/checkout/issues/881
|
||||||
|
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }}
|
||||||
|
- name: Use JDK 17
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
|
java-version: '17'
|
||||||
|
- name: Configure gradle
|
||||||
|
uses: gradle/actions/setup-gradle@v3
|
||||||
|
with:
|
||||||
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
|
- name: Run Ktlint check
|
||||||
|
run: ./gradlew ktlintCheck $CI_GRADLE_ARG_PROPERTIES
|
||||||
|
- name: Upload reports
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ktlint-report
|
||||||
|
path: |
|
||||||
|
**/build/reports/**/*.*
|
||||||
|
|
||||||
|
knit:
|
||||||
|
name: Knit checks
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
# Allow all jobs on main and develop. Just one per PR.
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.ref == 'refs/heads/main' && format('check-knit-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('check-knit-develop-{0}', github.sha) || format('check-knit-{0}', github.ref) }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
# Ensure we are building the branch and not the branch after being merged on develop
|
||||||
|
# https://github.com/actions/checkout/issues/881
|
||||||
|
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }}
|
||||||
|
- name: Use JDK 17
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
|
java-version: '17'
|
||||||
|
- name: Configure gradle
|
||||||
|
uses: gradle/actions/setup-gradle@v3
|
||||||
|
with:
|
||||||
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
|
- name: Run Knit
|
||||||
|
run: ./gradlew knitCheck $CI_GRADLE_ARG_PROPERTIES
|
||||||
|
|
||||||
|
upload_reports:
|
||||||
|
name: Project Check Suite
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [konsist, lint, ktlint, detekt]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
# Ensure we are building the branch and not the branch after being merged on develop
|
||||||
|
# https://github.com/actions/checkout/issues/881
|
||||||
|
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }}
|
||||||
|
- name: Download reports from previous jobs
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
- name: Prepare Danger
|
- name: Prepare Danger
|
||||||
if: always()
|
if: always()
|
||||||
run: |
|
run: |
|
||||||
|
|
|
||||||
32
.github/workflows/release.yml
vendored
32
.github/workflows/release.yml
vendored
|
|
@ -11,8 +11,8 @@ env:
|
||||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace -PpreDexEnable=false --max-workers 8 --no-daemon
|
CI_GRADLE_ARG_PROPERTIES: --stacktrace -PpreDexEnable=false --max-workers 8 --no-daemon
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
gplay:
|
||||||
name: Create App Bundle
|
name: Create App Bundle (Gplay)
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.ref == 'refs/head/main' && format('build-release-main-{0}', github.sha) }}
|
group: ${{ github.ref == 'refs/head/main' && format('build-release-main-{0}', github.sha) }}
|
||||||
|
|
@ -38,3 +38,31 @@ jobs:
|
||||||
name: elementx-app-gplay-bundle-unsigned
|
name: elementx-app-gplay-bundle-unsigned
|
||||||
path: |
|
path: |
|
||||||
app/build/outputs/bundle/gplayRelease/app-gplay-release.aab
|
app/build/outputs/bundle/gplayRelease/app-gplay-release.aab
|
||||||
|
|
||||||
|
fdroid:
|
||||||
|
name: Create APKs (FDroid)
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.ref == 'refs/head/main' && format('build-release-main-{0}', github.sha) }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Use JDK 17
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
|
java-version: '17'
|
||||||
|
- name: Configure gradle
|
||||||
|
uses: gradle/actions/setup-gradle@v3
|
||||||
|
- name: Create APKs
|
||||||
|
env:
|
||||||
|
ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }}
|
||||||
|
ELEMENT_ANDROID_MAPTILER_LIGHT_MAP_ID: ${{ secrets.MAPTILER_LIGHT_MAP_ID }}
|
||||||
|
ELEMENT_ANDROID_MAPTILER_DARK_MAP_ID: ${{ secrets.MAPTILER_DARK_MAP_ID }}
|
||||||
|
run: ./gradlew assembleFdroidRelease $CI_GRADLE_ARG_PROPERTIES
|
||||||
|
- name: Upload apks as artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: elementx-app-fdroid-apks-unsigned
|
||||||
|
path: |
|
||||||
|
app/build/outputs/apk/fdroid/release/*.apk
|
||||||
|
|
|
||||||
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
|
|
@ -55,7 +55,7 @@ jobs:
|
||||||
run: ./gradlew verifyPaparazziDebug $CI_GRADLE_ARG_PROPERTIES
|
run: ./gradlew verifyPaparazziDebug $CI_GRADLE_ARG_PROPERTIES
|
||||||
|
|
||||||
- name: 📈Generate kover report and verify coverage
|
- name: 📈Generate kover report and verify coverage
|
||||||
run: ./gradlew :app:koverXmlReportGplayDebug :app:koverHtmlReportGplayDebug :app:koverVerifyGplayDebug $CI_GRADLE_ARG_PROPERTIES
|
run: ./gradlew :app:koverXmlReportGplayDebug :app:koverHtmlReportGplayDebug :app:koverVerifyAll $CI_GRADLE_ARG_PROPERTIES
|
||||||
|
|
||||||
- name: 🚫 Upload kover failed coverage reports
|
- name: 🚫 Upload kover failed coverage reports
|
||||||
if: failure()
|
if: failure()
|
||||||
|
|
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -45,6 +45,7 @@ captures/
|
||||||
.idea/assetWizardSettings.xml
|
.idea/assetWizardSettings.xml
|
||||||
.idea/compiler.xml
|
.idea/compiler.xml
|
||||||
.idea/deploymentTargetDropDown.xml
|
.idea/deploymentTargetDropDown.xml
|
||||||
|
.idea/deploymentTargetSelector.xml
|
||||||
.idea/gradle.xml
|
.idea/gradle.xml
|
||||||
.idea/jarRepositories.xml
|
.idea/jarRepositories.xml
|
||||||
.idea/misc.xml
|
.idea/misc.xml
|
||||||
|
|
|
||||||
2
.idea/kotlinc.xml
generated
2
.idea/kotlinc.xml
generated
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="KotlinJpsPluginSettings">
|
<component name="KotlinJpsPluginSettings">
|
||||||
<option name="version" value="1.9.23" />
|
<option name="version" value="1.9.24" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
|
|
@ -2,7 +2,7 @@ appId: ${MAESTRO_APP_ID}
|
||||||
---
|
---
|
||||||
- takeScreenshot: build/maestro/510-Timeline
|
- takeScreenshot: build/maestro/510-Timeline
|
||||||
- tapOn:
|
- tapOn:
|
||||||
id: "rich_text_editor"
|
id: "text_editor"
|
||||||
- inputText: "Hello world!"
|
- inputText: "Hello world!"
|
||||||
- tapOn: "Send"
|
- tapOn: "Send"
|
||||||
- hideKeyboard
|
- hideKeyboard
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ appId: ${MAESTRO_APP_ID}
|
||||||
|
|
||||||
- tapOn:
|
- tapOn:
|
||||||
text: "Advanced settings"
|
text: "Advanced settings"
|
||||||
- assertVisible: "Rich text editor"
|
- assertVisible: "View source"
|
||||||
- back
|
- back
|
||||||
|
|
||||||
- tapOn:
|
- tapOn:
|
||||||
|
|
|
||||||
23
CHANGES.md
23
CHANGES.md
|
|
@ -1,3 +1,26 @@
|
||||||
|
Changes in Element X v0.4.13 (2024-05-22)
|
||||||
|
=========================================
|
||||||
|
|
||||||
|
Features ✨
|
||||||
|
----------
|
||||||
|
- Add plain text editor based on Markdown input. ([#2840](https://github.com/element-hq/element-x-android/issues/2840))
|
||||||
|
|
||||||
|
Bugfixes 🐛
|
||||||
|
----------
|
||||||
|
- Use members display names for their membership state events. ([#2286](https://github.com/element-hq/element-x-android/issues/2286))
|
||||||
|
- Make sure explicit links in messages take priority over links found by linkification (urls, emails, phone numbers, etc.) ([#2291](https://github.com/element-hq/element-x-android/issues/2291))
|
||||||
|
- Fix modal contents overlapping screen lock pin. ([#2692](https://github.com/element-hq/element-x-android/issues/2692))
|
||||||
|
- Fix a crash when trying to create an `EncryptedFile` in Android 6. ([#2846](https://github.com/element-hq/element-x-android/issues/2846))
|
||||||
|
- Session falsely displayed as 'verified' with no internet connection. ([#2884](https://github.com/element-hq/element-x-android/issues/2884))
|
||||||
|
|
||||||
|
Other changes
|
||||||
|
-------------
|
||||||
|
- Allow configuring push notification provider ([#2340](https://github.com/element-hq/element-x-android/issues/2340))
|
||||||
|
- UX cleanup: reorder text composer actions to prioritise camera ones. ([#2803](https://github.com/element-hq/element-x-android/issues/2803))
|
||||||
|
- Translation added into Portuguese and Simplified Chinese ([#2834](https://github.com/element-hq/element-x-android/issues/2834))
|
||||||
|
- Use via parameters when joining a room from permalink. ([#2843](https://github.com/element-hq/element-x-android/issues/2843))
|
||||||
|
|
||||||
|
|
||||||
Changes in Element X v0.4.12 (2024-05-13)
|
Changes in Element X v0.4.12 (2024-05-13)
|
||||||
=========================================
|
=========================================
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,9 @@ import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import com.bumble.appyx.core.integration.NodeHost
|
import com.bumble.appyx.core.integration.NodeHost
|
||||||
import com.bumble.appyx.core.integrationpoint.NodeActivity
|
import com.bumble.appyx.core.integrationpoint.NodeActivity
|
||||||
import com.bumble.appyx.core.plugin.NodeReadyObserver
|
import com.bumble.appyx.core.plugin.NodeReadyObserver
|
||||||
|
|
@ -39,13 +42,16 @@ import io.element.android.compound.theme.ElementTheme
|
||||||
import io.element.android.compound.theme.Theme
|
import io.element.android.compound.theme.Theme
|
||||||
import io.element.android.compound.theme.isDark
|
import io.element.android.compound.theme.isDark
|
||||||
import io.element.android.compound.theme.mapToTheme
|
import io.element.android.compound.theme.mapToTheme
|
||||||
|
import io.element.android.features.lockscreen.api.LockScreenEntryPoint
|
||||||
|
import io.element.android.features.lockscreen.api.LockScreenLockState
|
||||||
|
import io.element.android.features.lockscreen.api.LockScreenService
|
||||||
import io.element.android.features.lockscreen.api.handleSecureFlag
|
import io.element.android.features.lockscreen.api.handleSecureFlag
|
||||||
import io.element.android.features.lockscreen.api.isLocked
|
|
||||||
import io.element.android.libraries.architecture.bindings
|
import io.element.android.libraries.architecture.bindings
|
||||||
import io.element.android.libraries.core.log.logger.LoggerTag
|
import io.element.android.libraries.core.log.logger.LoggerTag
|
||||||
import io.element.android.libraries.designsystem.utils.snackbar.LocalSnackbarDispatcher
|
import io.element.android.libraries.designsystem.utils.snackbar.LocalSnackbarDispatcher
|
||||||
import io.element.android.x.di.AppBindings
|
import io.element.android.x.di.AppBindings
|
||||||
import io.element.android.x.intent.SafeUriHandler
|
import io.element.android.x.intent.SafeUriHandler
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
private val loggerTag = LoggerTag("MainActivity")
|
private val loggerTag = LoggerTag("MainActivity")
|
||||||
|
|
@ -59,27 +65,13 @@ class MainActivity : NodeActivity() {
|
||||||
installSplashScreen()
|
installSplashScreen()
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
appBindings = bindings()
|
appBindings = bindings()
|
||||||
appBindings.lockScreenService().handleSecureFlag(this)
|
setupLockManagement(appBindings.lockScreenService(), appBindings.lockScreenEntryPoint())
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
setContent {
|
setContent {
|
||||||
MainContent(appBindings)
|
MainContent(appBindings)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated("")
|
|
||||||
override fun onBackPressed() {
|
|
||||||
// If the app is locked, we need to intercept onBackPressed before it goes to OnBackPressedDispatcher.
|
|
||||||
// Indeed, otherwise we would need to trick Appyx backstack management everywhere.
|
|
||||||
// Without this trick, we would get pop operations on the hidden backstack.
|
|
||||||
if (appBindings.lockScreenService().isLocked) {
|
|
||||||
// Do not kill the app in this case, just go to background.
|
|
||||||
moveTaskToBack(false)
|
|
||||||
} else {
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
super.onBackPressed()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun MainContent(appBindings: AppBindings) {
|
private fun MainContent(appBindings: AppBindings) {
|
||||||
val theme by remember {
|
val theme by remember {
|
||||||
|
|
@ -96,8 +88,8 @@ class MainActivity : NodeActivity() {
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(MaterialTheme.colorScheme.background),
|
.background(MaterialTheme.colorScheme.background),
|
||||||
) {
|
) {
|
||||||
if (migrationState.migrationAction.isSuccess()) {
|
if (migrationState.migrationAction.isSuccess()) {
|
||||||
MainNodeHost()
|
MainNodeHost()
|
||||||
|
|
@ -131,6 +123,22 @@ class MainActivity : NodeActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun setupLockManagement(
|
||||||
|
lockScreenService: LockScreenService,
|
||||||
|
lockScreenEntryPoint: LockScreenEntryPoint
|
||||||
|
) {
|
||||||
|
lockScreenService.handleSecureFlag(this)
|
||||||
|
lifecycleScope.launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||||
|
lockScreenService.lockState.collect { state ->
|
||||||
|
if (state == LockScreenLockState.Locked) {
|
||||||
|
startActivity(lockScreenEntryPoint.pinUnlockIntent(this@MainActivity))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when:
|
* Called when:
|
||||||
* - the launcher icon is clicked (if the app is already running);
|
* - the launcher icon is clicked (if the app is already running);
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ package io.element.android.x.di
|
||||||
|
|
||||||
import com.squareup.anvil.annotations.ContributesTo
|
import com.squareup.anvil.annotations.ContributesTo
|
||||||
import io.element.android.features.api.MigrationEntryPoint
|
import io.element.android.features.api.MigrationEntryPoint
|
||||||
|
import io.element.android.features.lockscreen.api.LockScreenEntryPoint
|
||||||
import io.element.android.features.lockscreen.api.LockScreenService
|
import io.element.android.features.lockscreen.api.LockScreenService
|
||||||
import io.element.android.features.preferences.api.store.AppPreferencesStore
|
import io.element.android.features.preferences.api.store.AppPreferencesStore
|
||||||
import io.element.android.features.rageshake.api.reporter.BugReporter
|
import io.element.android.features.rageshake.api.reporter.BugReporter
|
||||||
|
|
@ -38,4 +39,6 @@ interface AppBindings {
|
||||||
fun preferencesStore(): AppPreferencesStore
|
fun preferencesStore(): AppPreferencesStore
|
||||||
|
|
||||||
fun migrationEntryPoint(): MigrationEntryPoint
|
fun migrationEntryPoint(): MigrationEntryPoint
|
||||||
|
|
||||||
|
fun lockScreenEntryPoint(): LockScreenEntryPoint
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,13 @@
|
||||||
<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="ka"/>
|
||||||
|
<locale android:name="pt"/>
|
||||||
<locale android:name="ro"/>
|
<locale android:name="ro"/>
|
||||||
<locale android:name="ru"/>
|
<locale android:name="ru"/>
|
||||||
<locale android:name="sk"/>
|
<locale android:name="sk"/>
|
||||||
<locale android:name="sv"/>
|
<locale android:name="sv"/>
|
||||||
<locale android:name="uk"/>
|
<locale android:name="uk"/>
|
||||||
|
<locale android:name="zh-CN"/>
|
||||||
<locale android:name="zh-TW"/>
|
<locale android:name="zh-TW"/>
|
||||||
</locale-config>
|
</locale-config>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.appconfig
|
||||||
|
|
||||||
|
object MessageComposerConfig {
|
||||||
|
/**
|
||||||
|
* Enable the rich text editing in the composer.
|
||||||
|
*/
|
||||||
|
const val ENABLE_RICH_TEXT_EDITING = true
|
||||||
|
}
|
||||||
|
|
@ -37,6 +37,7 @@ import com.bumble.appyx.navmodel.backstack.operation.push
|
||||||
import com.bumble.appyx.navmodel.backstack.operation.replace
|
import com.bumble.appyx.navmodel.backstack.operation.replace
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
|
import im.vector.app.features.analytics.plan.JoinedRoom
|
||||||
import io.element.android.anvilannotations.ContributesNode
|
import io.element.android.anvilannotations.ContributesNode
|
||||||
import io.element.android.appnav.loggedin.LoggedInNode
|
import io.element.android.appnav.loggedin.LoggedInNode
|
||||||
import io.element.android.appnav.room.RoomFlowNode
|
import io.element.android.appnav.room.RoomFlowNode
|
||||||
|
|
@ -46,9 +47,6 @@ import io.element.android.features.createroom.api.CreateRoomEntryPoint
|
||||||
import io.element.android.features.ftue.api.FtueEntryPoint
|
import io.element.android.features.ftue.api.FtueEntryPoint
|
||||||
import io.element.android.features.ftue.api.state.FtueService
|
import io.element.android.features.ftue.api.state.FtueService
|
||||||
import io.element.android.features.ftue.api.state.FtueState
|
import io.element.android.features.ftue.api.state.FtueState
|
||||||
import io.element.android.features.lockscreen.api.LockScreenEntryPoint
|
|
||||||
import io.element.android.features.lockscreen.api.LockScreenLockState
|
|
||||||
import io.element.android.features.lockscreen.api.LockScreenService
|
|
||||||
import io.element.android.features.networkmonitor.api.NetworkMonitor
|
import io.element.android.features.networkmonitor.api.NetworkMonitor
|
||||||
import io.element.android.features.networkmonitor.api.NetworkStatus
|
import io.element.android.features.networkmonitor.api.NetworkStatus
|
||||||
import io.element.android.features.preferences.api.PreferencesEntryPoint
|
import io.element.android.features.preferences.api.PreferencesEntryPoint
|
||||||
|
|
@ -99,8 +97,6 @@ class LoggedInFlowNode @AssistedInject constructor(
|
||||||
private val coroutineScope: CoroutineScope,
|
private val coroutineScope: CoroutineScope,
|
||||||
private val networkMonitor: NetworkMonitor,
|
private val networkMonitor: NetworkMonitor,
|
||||||
private val ftueService: FtueService,
|
private val ftueService: FtueService,
|
||||||
private val lockScreenEntryPoint: LockScreenEntryPoint,
|
|
||||||
private val lockScreenStateService: LockScreenService,
|
|
||||||
private val roomDirectoryEntryPoint: RoomDirectoryEntryPoint,
|
private val roomDirectoryEntryPoint: RoomDirectoryEntryPoint,
|
||||||
private val matrixClient: MatrixClient,
|
private val matrixClient: MatrixClient,
|
||||||
snackbarDispatcher: SnackbarDispatcher,
|
snackbarDispatcher: SnackbarDispatcher,
|
||||||
|
|
@ -110,7 +106,7 @@ class LoggedInFlowNode @AssistedInject constructor(
|
||||||
savedStateMap = buildContext.savedStateMap,
|
savedStateMap = buildContext.savedStateMap,
|
||||||
),
|
),
|
||||||
permanentNavModel = PermanentNavModel(
|
permanentNavModel = PermanentNavModel(
|
||||||
navTargets = setOf(NavTarget.LoggedInPermanent, NavTarget.LockPermanent),
|
navTargets = setOf(NavTarget.LoggedInPermanent),
|
||||||
savedStateMap = buildContext.savedStateMap,
|
savedStateMap = buildContext.savedStateMap,
|
||||||
),
|
),
|
||||||
buildContext = buildContext,
|
buildContext = buildContext,
|
||||||
|
|
@ -188,15 +184,14 @@ class LoggedInFlowNode @AssistedInject constructor(
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data object LoggedInPermanent : NavTarget
|
data object LoggedInPermanent : NavTarget
|
||||||
|
|
||||||
@Parcelize
|
|
||||||
data object LockPermanent : NavTarget
|
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data object RoomList : NavTarget
|
data object RoomList : NavTarget
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class Room(
|
data class Room(
|
||||||
val roomIdOrAlias: RoomIdOrAlias,
|
val roomIdOrAlias: RoomIdOrAlias,
|
||||||
|
val serverNames: List<String> = emptyList(),
|
||||||
|
val trigger: JoinedRoom.Trigger? = null,
|
||||||
val roomDescription: RoomDescription? = null,
|
val roomDescription: RoomDescription? = null,
|
||||||
val initialElement: RoomNavigationTarget = RoomNavigationTarget.Messages()
|
val initialElement: RoomNavigationTarget = RoomNavigationTarget.Messages()
|
||||||
) : NavTarget
|
) : NavTarget
|
||||||
|
|
@ -232,11 +227,6 @@ class LoggedInFlowNode @AssistedInject constructor(
|
||||||
NavTarget.LoggedInPermanent -> {
|
NavTarget.LoggedInPermanent -> {
|
||||||
createNode<LoggedInNode>(buildContext)
|
createNode<LoggedInNode>(buildContext)
|
||||||
}
|
}
|
||||||
NavTarget.LockPermanent -> {
|
|
||||||
lockScreenEntryPoint.nodeBuilder(this, buildContext)
|
|
||||||
.target(LockScreenEntryPoint.Target.Unlock)
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
NavTarget.RoomList -> {
|
NavTarget.RoomList -> {
|
||||||
val callback = object : RoomListEntryPoint.Callback {
|
val callback = object : RoomListEntryPoint.Callback {
|
||||||
override fun onRoomClicked(roomId: RoomId) {
|
override fun onRoomClicked(roomId: RoomId) {
|
||||||
|
|
@ -292,8 +282,9 @@ class LoggedInFlowNode @AssistedInject constructor(
|
||||||
backstack.push(
|
backstack.push(
|
||||||
NavTarget.Room(
|
NavTarget.Room(
|
||||||
roomIdOrAlias = data.roomIdOrAlias,
|
roomIdOrAlias = data.roomIdOrAlias,
|
||||||
|
serverNames = data.viaParameters,
|
||||||
|
trigger = JoinedRoom.Trigger.Timeline,
|
||||||
initialElement = RoomNavigationTarget.Messages(data.eventId),
|
initialElement = RoomNavigationTarget.Messages(data.eventId),
|
||||||
// TODO Use the viaParameters
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -311,6 +302,8 @@ class LoggedInFlowNode @AssistedInject constructor(
|
||||||
val inputs = RoomFlowNode.Inputs(
|
val inputs = RoomFlowNode.Inputs(
|
||||||
roomIdOrAlias = navTarget.roomIdOrAlias,
|
roomIdOrAlias = navTarget.roomIdOrAlias,
|
||||||
roomDescription = Optional.ofNullable(navTarget.roomDescription),
|
roomDescription = Optional.ofNullable(navTarget.roomDescription),
|
||||||
|
serverNames = navTarget.serverNames,
|
||||||
|
trigger = Optional.ofNullable(navTarget.trigger),
|
||||||
initialElement = navTarget.initialElement
|
initialElement = navTarget.initialElement
|
||||||
)
|
)
|
||||||
createNode<RoomFlowNode>(buildContext, plugins = listOf(inputs, callback))
|
createNode<RoomFlowNode>(buildContext, plugins = listOf(inputs, callback))
|
||||||
|
|
@ -370,12 +363,14 @@ class LoggedInFlowNode @AssistedInject constructor(
|
||||||
NavTarget.RoomDirectorySearch -> {
|
NavTarget.RoomDirectorySearch -> {
|
||||||
roomDirectoryEntryPoint.nodeBuilder(this, buildContext)
|
roomDirectoryEntryPoint.nodeBuilder(this, buildContext)
|
||||||
.callback(object : RoomDirectoryEntryPoint.Callback {
|
.callback(object : RoomDirectoryEntryPoint.Callback {
|
||||||
override fun onRoomJoined(roomId: RoomId) {
|
|
||||||
backstack.push(NavTarget.Room(roomId.toRoomIdOrAlias()))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResultClicked(roomDescription: RoomDescription) {
|
override fun onResultClicked(roomDescription: RoomDescription) {
|
||||||
backstack.push(NavTarget.Room(roomDescription.roomId.toRoomIdOrAlias(), roomDescription))
|
backstack.push(
|
||||||
|
NavTarget.Room(
|
||||||
|
roomIdOrAlias = roomDescription.roomId.toRoomIdOrAlias(),
|
||||||
|
roomDescription = roomDescription,
|
||||||
|
trigger = JoinedRoom.Trigger.RoomDirectory,
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.build()
|
.build()
|
||||||
|
|
@ -383,7 +378,12 @@ class LoggedInFlowNode @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun attachRoom(roomIdOrAlias: RoomIdOrAlias, eventId: EventId? = null) {
|
suspend fun attachRoom(
|
||||||
|
roomIdOrAlias: RoomIdOrAlias,
|
||||||
|
serverNames: List<String> = emptyList(),
|
||||||
|
trigger: JoinedRoom.Trigger? = null,
|
||||||
|
eventId: EventId? = null,
|
||||||
|
) {
|
||||||
waitForNavTargetAttached { navTarget ->
|
waitForNavTargetAttached { navTarget ->
|
||||||
navTarget is NavTarget.RoomList
|
navTarget is NavTarget.RoomList
|
||||||
}
|
}
|
||||||
|
|
@ -391,6 +391,8 @@ class LoggedInFlowNode @AssistedInject constructor(
|
||||||
backstack.push(
|
backstack.push(
|
||||||
NavTarget.Room(
|
NavTarget.Room(
|
||||||
roomIdOrAlias = roomIdOrAlias,
|
roomIdOrAlias = roomIdOrAlias,
|
||||||
|
serverNames = serverNames,
|
||||||
|
trigger = trigger,
|
||||||
initialElement = RoomNavigationTarget.Messages(
|
initialElement = RoomNavigationTarget.Messages(
|
||||||
focusedEventId = eventId
|
focusedEventId = eventId
|
||||||
)
|
)
|
||||||
|
|
@ -415,15 +417,11 @@ class LoggedInFlowNode @AssistedInject constructor(
|
||||||
@Composable
|
@Composable
|
||||||
override fun View(modifier: Modifier) {
|
override fun View(modifier: Modifier) {
|
||||||
Box(modifier = modifier) {
|
Box(modifier = modifier) {
|
||||||
val lockScreenState by lockScreenStateService.lockState.collectAsState()
|
|
||||||
val ftueState by ftueService.state.collectAsState()
|
val ftueState by ftueService.state.collectAsState()
|
||||||
BackstackView()
|
BackstackView()
|
||||||
if (ftueState is FtueState.Complete) {
|
if (ftueState is FtueState.Complete) {
|
||||||
PermanentChild(permanentNavModel = permanentNavModel, navTarget = NavTarget.LoggedInPermanent)
|
PermanentChild(permanentNavModel = permanentNavModel, navTarget = NavTarget.LoggedInPermanent)
|
||||||
}
|
}
|
||||||
if (lockScreenState == LockScreenLockState.Locked) {
|
|
||||||
PermanentChild(permanentNavModel = permanentNavModel, navTarget = NavTarget.LockPermanent)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ import com.bumble.appyx.navmodel.backstack.operation.pop
|
||||||
import com.bumble.appyx.navmodel.backstack.operation.push
|
import com.bumble.appyx.navmodel.backstack.operation.push
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
|
import im.vector.app.features.analytics.plan.JoinedRoom
|
||||||
import io.element.android.anvilannotations.ContributesNode
|
import io.element.android.anvilannotations.ContributesNode
|
||||||
import io.element.android.appnav.di.MatrixClientsHolder
|
import io.element.android.appnav.di.MatrixClientsHolder
|
||||||
import io.element.android.appnav.intent.IntentResolver
|
import io.element.android.appnav.intent.IntentResolver
|
||||||
|
|
@ -295,6 +296,8 @@ class RootFlowNode @AssistedInject constructor(
|
||||||
is PermalinkData.RoomLink -> {
|
is PermalinkData.RoomLink -> {
|
||||||
attachRoom(
|
attachRoom(
|
||||||
roomIdOrAlias = permalinkData.roomIdOrAlias,
|
roomIdOrAlias = permalinkData.roomIdOrAlias,
|
||||||
|
trigger = JoinedRoom.Trigger.MobilePermalink,
|
||||||
|
serverNames = permalinkData.viaParameters,
|
||||||
eventId = permalinkData.eventId,
|
eventId = permalinkData.eventId,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatu
|
||||||
import io.element.android.libraries.push.api.PushService
|
import io.element.android.libraries.push.api.PushService
|
||||||
import io.element.android.services.analytics.api.AnalyticsService
|
import io.element.android.services.analytics.api.AnalyticsService
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class LoggedInPresenter @Inject constructor(
|
class LoggedInPresenter @Inject constructor(
|
||||||
|
|
@ -55,10 +56,26 @@ class LoggedInPresenter @Inject constructor(
|
||||||
LaunchedEffect(isVerified) {
|
LaunchedEffect(isVerified) {
|
||||||
if (isVerified) {
|
if (isVerified) {
|
||||||
// Ensure pusher is registered
|
// Ensure pusher is registered
|
||||||
// TODO Manually select push provider for now
|
val currentPushProvider = pushService.getCurrentPushProvider()
|
||||||
val pushProvider = pushService.getAvailablePushProviders().firstOrNull() ?: return@LaunchedEffect
|
val result = if (currentPushProvider == null) {
|
||||||
val distributor = pushProvider.getDistributors().firstOrNull() ?: return@LaunchedEffect
|
// Register with the first available push provider
|
||||||
pushService.registerWith(matrixClient, pushProvider, distributor)
|
val pushProvider = pushService.getAvailablePushProviders().firstOrNull() ?: return@LaunchedEffect
|
||||||
|
val distributor = pushProvider.getDistributors().firstOrNull() ?: return@LaunchedEffect
|
||||||
|
pushService.registerWith(matrixClient, pushProvider, distributor)
|
||||||
|
} else {
|
||||||
|
val currentPushDistributor = currentPushProvider.getCurrentDistributor(matrixClient)
|
||||||
|
if (currentPushDistributor == null) {
|
||||||
|
// Register with the first available distributor
|
||||||
|
val distributor = currentPushProvider.getDistributors().firstOrNull() ?: return@LaunchedEffect
|
||||||
|
pushService.registerWith(matrixClient, currentPushProvider, distributor)
|
||||||
|
} else {
|
||||||
|
// Re-register with the current distributor
|
||||||
|
pushService.registerWith(matrixClient, currentPushProvider, currentPushDistributor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.onFailure {
|
||||||
|
Timber.e(it, "Failed to register pusher")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ import com.bumble.appyx.navmodel.backstack.BackStack
|
||||||
import com.bumble.appyx.navmodel.backstack.operation.newRoot
|
import com.bumble.appyx.navmodel.backstack.operation.newRoot
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
|
import im.vector.app.features.analytics.plan.JoinedRoom
|
||||||
import io.element.android.anvilannotations.ContributesNode
|
import io.element.android.anvilannotations.ContributesNode
|
||||||
import io.element.android.appnav.room.joined.JoinedRoomFlowNode
|
import io.element.android.appnav.room.joined.JoinedRoomFlowNode
|
||||||
import io.element.android.appnav.room.joined.JoinedRoomLoadedFlowNode
|
import io.element.android.appnav.room.joined.JoinedRoomLoadedFlowNode
|
||||||
|
|
@ -54,6 +55,7 @@ import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
|
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
|
||||||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||||
|
import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
|
@ -83,6 +85,8 @@ class RoomFlowNode @AssistedInject constructor(
|
||||||
data class Inputs(
|
data class Inputs(
|
||||||
val roomIdOrAlias: RoomIdOrAlias,
|
val roomIdOrAlias: RoomIdOrAlias,
|
||||||
val roomDescription: Optional<RoomDescription>,
|
val roomDescription: Optional<RoomDescription>,
|
||||||
|
val serverNames: List<String>,
|
||||||
|
val trigger: Optional<JoinedRoom.Trigger>,
|
||||||
val initialElement: RoomNavigationTarget,
|
val initialElement: RoomNavigationTarget,
|
||||||
) : NodeInputs
|
) : NodeInputs
|
||||||
|
|
||||||
|
|
@ -96,7 +100,11 @@ class RoomFlowNode @AssistedInject constructor(
|
||||||
data class Resolving(val roomAlias: RoomAlias) : NavTarget
|
data class Resolving(val roomAlias: RoomAlias) : NavTarget
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class JoinRoom(val roomId: RoomId) : NavTarget
|
data class JoinRoom(
|
||||||
|
val roomId: RoomId,
|
||||||
|
val serverNames: List<String>,
|
||||||
|
val trigger: im.vector.app.features.analytics.plan.JoinedRoom.Trigger,
|
||||||
|
) : NavTarget
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class JoinedRoom(val roomId: RoomId) : NavTarget
|
data class JoinedRoom(val roomId: RoomId) : NavTarget
|
||||||
|
|
@ -114,13 +122,13 @@ class RoomFlowNode @AssistedInject constructor(
|
||||||
backstack.newRoot(NavTarget.Resolving(i.roomAlias))
|
backstack.newRoot(NavTarget.Resolving(i.roomAlias))
|
||||||
}
|
}
|
||||||
is RoomIdOrAlias.Id -> {
|
is RoomIdOrAlias.Id -> {
|
||||||
subscribeToRoomInfoFlow(i.roomId)
|
subscribeToRoomInfoFlow(i.roomId, inputs.serverNames)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun subscribeToRoomInfoFlow(roomId: RoomId) {
|
private fun subscribeToRoomInfoFlow(roomId: RoomId, serverNames: List<String>) {
|
||||||
val roomInfoFlow = client.getRoomInfoFlow(
|
val roomInfoFlow = client.getRoomInfoFlow(
|
||||||
roomId = roomId
|
roomId = roomId
|
||||||
).map { it.getOrNull() }
|
).map { it.getOrNull() }
|
||||||
|
|
@ -136,7 +144,13 @@ class RoomFlowNode @AssistedInject constructor(
|
||||||
// we can have a space here in case the space has just been joined.
|
// we can have a space here in case the space has just been joined.
|
||||||
// So navigate to the JoinRoom target for now, which will
|
// So navigate to the JoinRoom target for now, which will
|
||||||
// handle the space not supported screen
|
// handle the space not supported screen
|
||||||
backstack.newRoot(NavTarget.JoinRoom(roomId))
|
backstack.newRoot(
|
||||||
|
NavTarget.JoinRoom(
|
||||||
|
roomId = roomId,
|
||||||
|
serverNames = serverNames,
|
||||||
|
trigger = inputs.trigger.getOrNull() ?: JoinedRoom.Trigger.Invite,
|
||||||
|
)
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
backstack.newRoot(NavTarget.JoinedRoom(roomId))
|
backstack.newRoot(NavTarget.JoinedRoom(roomId))
|
||||||
}
|
}
|
||||||
|
|
@ -147,7 +161,13 @@ class RoomFlowNode @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
// Was invited or the room is not known, display the join room screen
|
// Was invited or the room is not known, display the join room screen
|
||||||
backstack.newRoot(NavTarget.JoinRoom(roomId))
|
backstack.newRoot(
|
||||||
|
NavTarget.JoinRoom(
|
||||||
|
roomId = roomId,
|
||||||
|
serverNames = serverNames,
|
||||||
|
trigger = inputs.trigger.getOrNull() ?: JoinedRoom.Trigger.Invite,
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.launchIn(lifecycleScope)
|
}.launchIn(lifecycleScope)
|
||||||
|
|
@ -158,8 +178,11 @@ class RoomFlowNode @AssistedInject constructor(
|
||||||
is NavTarget.Loading -> loadingNode(buildContext)
|
is NavTarget.Loading -> loadingNode(buildContext)
|
||||||
is NavTarget.Resolving -> {
|
is NavTarget.Resolving -> {
|
||||||
val callback = object : RoomAliasResolverEntryPoint.Callback {
|
val callback = object : RoomAliasResolverEntryPoint.Callback {
|
||||||
override fun onAliasResolved(roomId: RoomId) {
|
override fun onAliasResolved(data: ResolvedRoomAlias) {
|
||||||
subscribeToRoomInfoFlow(roomId)
|
subscribeToRoomInfoFlow(
|
||||||
|
roomId = data.roomId,
|
||||||
|
serverNames = data.servers,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val params = RoomAliasResolverEntryPoint.Params(navTarget.roomAlias)
|
val params = RoomAliasResolverEntryPoint.Params(navTarget.roomAlias)
|
||||||
|
|
@ -173,6 +196,8 @@ class RoomFlowNode @AssistedInject constructor(
|
||||||
roomId = navTarget.roomId,
|
roomId = navTarget.roomId,
|
||||||
roomIdOrAlias = inputs.roomIdOrAlias,
|
roomIdOrAlias = inputs.roomIdOrAlias,
|
||||||
roomDescription = inputs.roomDescription,
|
roomDescription = inputs.roomDescription,
|
||||||
|
serverNames = navTarget.serverNames,
|
||||||
|
trigger = navTarget.trigger,
|
||||||
)
|
)
|
||||||
joinRoomEntryPoint.createNode(this, buildContext, inputs)
|
joinRoomEntryPoint.createNode(this, buildContext, inputs)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
fastlane/metadata/android/en-US/changelogs/40004130.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/40004130.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
Main changes in this version: Add plain text editor based on Markdown input.
|
||||||
|
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>
|
||||||
|
|
@ -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">"Partilhe dados de utilização anónimos para nos ajudar a identificar problemas."</string>
|
||||||
|
<string name="screen_analytics_settings_read_terms">"Podes ler todos os nossos termos %1$s."</string>
|
||||||
|
<string name="screen_analytics_settings_read_terms_content_link">"aqui"</string>
|
||||||
|
<string name="screen_analytics_settings_share_data">"Partilhar dados de utilização"</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_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>
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -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">"Não recolheremos ou analisaremos quaisquer dados pessoais"</string>
|
||||||
|
<string name="screen_analytics_prompt_help_us_improve">"Partilhe dados de utilização anónimos para nos ajudar a identificar problemas."</string>
|
||||||
|
<string name="screen_analytics_prompt_read_terms">"Podes ler todos os nossos termos %1$s."</string>
|
||||||
|
<string name="screen_analytics_prompt_read_terms_content_link">"aqui"</string>
|
||||||
|
<string name="screen_analytics_prompt_settings">"Podes desligar qualquer momento"</string>
|
||||||
|
<string name="screen_analytics_prompt_third_party_sharing">"Não partilharemos os teus dados com terceiros"</string>
|
||||||
|
<string name="screen_analytics_prompt_title">"Ajude a melhorar a %1$s"</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>
|
||||||
6
features/call/src/main/res/values-ka/translations.xml
Normal file
6
features/call/src/main/res/values-ka/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="call_foreground_service_channel_title_android">"მიმდინარე ზარი"</string>
|
||||||
|
<string name="call_foreground_service_message_android">"დააწკაპუნეთ ზარში დასაბრუნებლად"</string>
|
||||||
|
<string name="call_foreground_service_title_android">"☎️ ზარი მიმდინარეობს"</string>
|
||||||
|
</resources>
|
||||||
6
features/call/src/main/res/values-pt/translations.xml
Normal file
6
features/call/src/main/res/values-pt/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="call_foreground_service_channel_title_android">"Chamada em curso"</string>
|
||||||
|
<string name="call_foreground_service_message_android">"Toca para voltar à chamada"</string>
|
||||||
|
<string name="call_foreground_service_title_android">"☎️ Chamada em curso"</string>
|
||||||
|
</resources>
|
||||||
6
features/call/src/main/res/values-zh/translations.xml
Normal file
6
features/call/src/main/res/values-zh/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="call_foreground_service_channel_title_android">"通话进行中"</string>
|
||||||
|
<string name="call_foreground_service_message_android">"点按即可返回通话"</string>
|
||||||
|
<string name="call_foreground_service_title_android">"☎️ 通话中"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -29,12 +29,10 @@ import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.selection.selectableGroup
|
import androidx.compose.foundation.selection.selectableGroup
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.ExperimentalMaterialApi
|
|
||||||
import androidx.compose.material.ModalBottomSheetValue
|
|
||||||
import androidx.compose.material.rememberModalBottomSheetState
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalFocusManager
|
import androidx.compose.ui.platform.LocalFocusManager
|
||||||
|
|
@ -63,9 +61,7 @@ import io.element.android.libraries.matrix.ui.components.SelectedUsersRowList
|
||||||
import io.element.android.libraries.matrix.ui.components.UnsavedAvatar
|
import io.element.android.libraries.matrix.ui.components.UnsavedAvatar
|
||||||
import io.element.android.libraries.permissions.api.PermissionsView
|
import io.element.android.libraries.permissions.api.PermissionsView
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterialApi::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ConfigureRoomView(
|
fun ConfigureRoomView(
|
||||||
state: ConfigureRoomState,
|
state: ConfigureRoomState,
|
||||||
|
|
@ -73,17 +69,12 @@ fun ConfigureRoomView(
|
||||||
onRoomCreated: (RoomId) -> Unit,
|
onRoomCreated: (RoomId) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val coroutineScope = rememberCoroutineScope()
|
|
||||||
val focusManager = LocalFocusManager.current
|
val focusManager = LocalFocusManager.current
|
||||||
val itemActionsBottomSheetState = rememberModalBottomSheetState(
|
val isAvatarActionsSheetVisible = remember { mutableStateOf(false) }
|
||||||
initialValue = ModalBottomSheetValue.Hidden,
|
|
||||||
)
|
|
||||||
|
|
||||||
fun onAvatarClicked() {
|
fun onAvatarClicked() {
|
||||||
focusManager.clearFocus()
|
focusManager.clearFocus()
|
||||||
coroutineScope.launch {
|
isAvatarActionsSheetVisible.value = true
|
||||||
itemActionsBottomSheetState.show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
|
|
@ -143,7 +134,8 @@ fun ConfigureRoomView(
|
||||||
|
|
||||||
AvatarActionBottomSheet(
|
AvatarActionBottomSheet(
|
||||||
actions = state.avatarActions,
|
actions = state.avatarActions,
|
||||||
modalBottomSheetState = itemActionsBottomSheetState,
|
isVisible = isAvatarActionsSheetVisible.value,
|
||||||
|
onDismiss = { isAvatarActionsSheetVisible.value = false },
|
||||||
onActionSelected = { state.eventSink(ConfigureRoomEvents.HandleAvatarAction(it)) }
|
onActionSelected = { state.eventSink(ConfigureRoomEvents.HandleAvatarAction(it)) }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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_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_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_title">"საჯარო ოთახი (ნებისმიერი)"</string>
|
||||||
|
<string name="screen_create_room_room_name_label">"ოთახის სახელი"</string>
|
||||||
|
<string name="screen_create_room_title">"ოთახის შექმნა"</string>
|
||||||
|
<string name="screen_create_room_topic_label">"თემა (სურვილისამებრ)"</string>
|
||||||
|
<string name="screen_start_chat_error_starting_chat">"ჩატის დაწყების მცდელობისას შეცდომა მოხდა"</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_create_room_action_create_room">"Nova sala"</string>
|
||||||
|
<string name="screen_create_room_add_people_title">"Convidar pessoas"</string>
|
||||||
|
<string name="screen_create_room_error_creating_room">"Ocorreu um erro ao criar a sala"</string>
|
||||||
|
<string name="screen_create_room_private_option_description">"As mensagens serão cifradas. Uma vez ativada, não é possível desativar a cifragem."</string>
|
||||||
|
<string name="screen_create_room_private_option_title">"Sala privada (entrada apenas por convite)"</string>
|
||||||
|
<string name="screen_create_room_public_option_description">"As mensagens não serão cifradas e qualquer um as poderá ler. É possível ativar a cifragem posteriormente."</string>
|
||||||
|
<string name="screen_create_room_public_option_title">"Sala pública (entrada livre)"</string>
|
||||||
|
<string name="screen_create_room_room_name_label">"Nome da sala"</string>
|
||||||
|
<string name="screen_create_room_title">"Criar uma sala"</string>
|
||||||
|
<string name="screen_create_room_topic_label">"Descrição (opcional)"</string>
|
||||||
|
<string name="screen_start_chat_error_starting_chat">"Ocorreu um erro ao tentar iniciar uma conversa"</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_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_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_title">"公共房间(任何人)"</string>
|
||||||
|
<string name="screen_create_room_room_name_label">"房间名称"</string>
|
||||||
|
<string name="screen_create_room_title">"创建房间"</string>
|
||||||
|
<string name="screen_create_room_topic_label">"主题(可选)"</string>
|
||||||
|
<string name="screen_start_chat_error_starting_chat">"在开始聊天时发生了错误"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -132,9 +132,8 @@ class FtueFlowNode @AssistedInject constructor(
|
||||||
lifecycleScope.launch { moveToNextStep() }
|
lifecycleScope.launch { moveToNextStep() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lockScreenEntryPoint.nodeBuilder(this, buildContext)
|
lockScreenEntryPoint.nodeBuilder(this, buildContext, LockScreenEntryPoint.Target.Setup)
|
||||||
.callback(callback)
|
.callback(callback)
|
||||||
.target(LockScreenEntryPoint.Target.Setup)
|
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,12 @@
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_title">"Злучэнне небяспечнае"</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_title">"Злучэнне небяспечнае"</string>
|
||||||
<string name="screen_qr_code_login_device_code_subtitle">"Вам будзе прапанавана ўвесці дзве лічбы, паказаныя на гэтай прыладзе."</string>
|
<string name="screen_qr_code_login_device_code_subtitle">"Вам будзе прапанавана ўвесці дзве лічбы, паказаныя на гэтай прыладзе."</string>
|
||||||
<string name="screen_qr_code_login_device_code_title">"Увядзіце наступны нумар на іншай прыладзе."</string>
|
<string name="screen_qr_code_login_device_code_title">"Увядзіце наступны нумар на іншай прыладзе."</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_button_title">"Гатовы да сканавання"</string>
|
||||||
<string name="screen_qr_code_login_initial_state_item_1">"Адкрыйце %1$s на настольнай прыладзе"</string>
|
<string name="screen_qr_code_login_initial_state_item_1">"Адкрыйце %1$s на настольнай прыладзе"</string>
|
||||||
<string name="screen_qr_code_login_initial_state_item_2">"Націсніце на свой аватар"</string>
|
<string name="screen_qr_code_login_initial_state_item_2">"Націсніце на свой аватар"</string>
|
||||||
<string name="screen_qr_code_login_initial_state_item_3">"Выберыце %1$s"</string>
|
<string name="screen_qr_code_login_initial_state_item_3">"Выберыце %1$s"</string>
|
||||||
<string name="screen_qr_code_login_initial_state_item_3_action">"“Звязаць новую прыладу”"</string>
|
<string name="screen_qr_code_login_initial_state_item_3_action">"“Звязаць новую прыладу”"</string>
|
||||||
<string name="screen_qr_code_login_initial_state_item_4">"Выконвайце паказаныя інструкцыі"</string>
|
<string name="screen_qr_code_login_initial_state_item_4">"Адсканіруйце QR-код з дапамогай гэтай прылады"</string>
|
||||||
<string name="screen_qr_code_login_initial_state_title">"Адкрыйце %1$s на іншай прыладзе, каб атрымаць QR-код"</string>
|
<string name="screen_qr_code_login_initial_state_title">"Адкрыйце %1$s на іншай прыладзе, каб атрымаць QR-код"</string>
|
||||||
<string name="screen_qr_code_login_invalid_scan_state_description">"Выкарыстоўвайце QR-код, паказаны на іншай прыладзе."</string>
|
<string name="screen_qr_code_login_invalid_scan_state_description">"Выкарыстоўвайце QR-код, паказаны на іншай прыладзе."</string>
|
||||||
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Паўтарыць спробу"</string>
|
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Паўтарыць спробу"</string>
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,12 @@
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_title">"Připojení není zabezpečené"</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_title">"Připojení není zabezpečené"</string>
|
||||||
<string name="screen_qr_code_login_device_code_subtitle">"Budete požádáni o zadání dvou níže uvedených číslic."</string>
|
<string name="screen_qr_code_login_device_code_subtitle">"Budete požádáni o zadání dvou níže uvedených číslic."</string>
|
||||||
<string name="screen_qr_code_login_device_code_title">"Zadejte níže uvedené číslo na svém dalším zařízení"</string>
|
<string name="screen_qr_code_login_device_code_title">"Zadejte níže uvedené číslo na svém dalším zařízení"</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_button_title">"Připraveno ke skenování"</string>
|
||||||
<string name="screen_qr_code_login_initial_state_item_1">"Otevřete %1$s na stolním počítači"</string>
|
<string name="screen_qr_code_login_initial_state_item_1">"Otevřete %1$s na stolním počítači"</string>
|
||||||
<string name="screen_qr_code_login_initial_state_item_2">"Klikněte na svůj avatar"</string>
|
<string name="screen_qr_code_login_initial_state_item_2">"Klikněte na svůj avatar"</string>
|
||||||
<string name="screen_qr_code_login_initial_state_item_3">"Vybrat %1$s"</string>
|
<string name="screen_qr_code_login_initial_state_item_3">"Vybrat %1$s"</string>
|
||||||
<string name="screen_qr_code_login_initial_state_item_3_action">"\"Připojit nové zařízení\""</string>
|
<string name="screen_qr_code_login_initial_state_item_3_action">"\"Připojit nové zařízení\""</string>
|
||||||
<string name="screen_qr_code_login_initial_state_item_4">"Postupujte podle uvedených pokynů"</string>
|
<string name="screen_qr_code_login_initial_state_item_4">"Naskenujte QR kód pomocí tohoto zařízení"</string>
|
||||||
<string name="screen_qr_code_login_initial_state_title">"Otevřete %1$s na jiném zařízení pro získání QR kódu"</string>
|
<string name="screen_qr_code_login_initial_state_title">"Otevřete %1$s na jiném zařízení pro získání QR kódu"</string>
|
||||||
<string name="screen_qr_code_login_invalid_scan_state_description">"Použijte QR kód zobrazený na druhém zařízení."</string>
|
<string name="screen_qr_code_login_invalid_scan_state_description">"Použijte QR kód zobrazený na druhém zařízení."</string>
|
||||||
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Zkusit znovu"</string>
|
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Zkusit znovu"</string>
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,12 @@
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_title">"Die Verbindung ist nicht sicher"</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_title">"Die Verbindung ist nicht sicher"</string>
|
||||||
<string name="screen_qr_code_login_device_code_subtitle">"Du wirst aufgefordert, die beiden unten abgebildeten Ziffern einzugeben."</string>
|
<string name="screen_qr_code_login_device_code_subtitle">"Du wirst aufgefordert, die beiden unten abgebildeten Ziffern einzugeben."</string>
|
||||||
<string name="screen_qr_code_login_device_code_title">"Trage die unten angezeigte Zahl auf einem anderen Device ein"</string>
|
<string name="screen_qr_code_login_device_code_title">"Trage die unten angezeigte Zahl auf einem anderen Device ein"</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_button_title">"Bereit zum Scannen"</string>
|
||||||
<string name="screen_qr_code_login_initial_state_item_1">"%1$s auf einem Desktop-Gerät öffnen"</string>
|
<string name="screen_qr_code_login_initial_state_item_1">"%1$s auf einem Desktop-Gerät öffnen"</string>
|
||||||
<string name="screen_qr_code_login_initial_state_item_2">"Klick auf deinen Avatar"</string>
|
<string name="screen_qr_code_login_initial_state_item_2">"Klick auf deinen Avatar"</string>
|
||||||
<string name="screen_qr_code_login_initial_state_item_3">"Wähle %1$s"</string>
|
<string name="screen_qr_code_login_initial_state_item_3">"Wähle %1$s"</string>
|
||||||
<string name="screen_qr_code_login_initial_state_item_3_action">"\"Neues Gerät verknüpfen\""</string>
|
<string name="screen_qr_code_login_initial_state_item_3_action">"\"Neues Gerät verknüpfen\""</string>
|
||||||
<string name="screen_qr_code_login_initial_state_item_4">"Befolge die angezeigten Anweisungen"</string>
|
<string name="screen_qr_code_login_initial_state_item_4">"Scanne den QR-Code mit diesem Gerät"</string>
|
||||||
<string name="screen_qr_code_login_initial_state_title">"Öffne %1$s auf einem anderen Gerät, um den QR-Code zu erhalten"</string>
|
<string name="screen_qr_code_login_initial_state_title">"Öffne %1$s auf einem anderen Gerät, um den QR-Code zu erhalten"</string>
|
||||||
<string name="screen_qr_code_login_invalid_scan_state_description">"Verwende den QR-Code, der auf dem anderen Gerät angezeigt wird."</string>
|
<string name="screen_qr_code_login_invalid_scan_state_description">"Verwende den QR-Code, der auf dem anderen Gerät angezeigt wird."</string>
|
||||||
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Erneut versuchen"</string>
|
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Erneut versuchen"</string>
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,12 @@
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_title">"A kapcsolat nem biztonságos"</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_title">"A kapcsolat nem biztonságos"</string>
|
||||||
<string name="screen_qr_code_login_device_code_subtitle">"A rendszer kérni fogja, hogy adja meg az alábbi két számjegyet az eszközén."</string>
|
<string name="screen_qr_code_login_device_code_subtitle">"A rendszer kérni fogja, hogy adja meg az alábbi két számjegyet az eszközén."</string>
|
||||||
<string name="screen_qr_code_login_device_code_title">"Adja meg az alábbi számot a másik eszközén"</string>
|
<string name="screen_qr_code_login_device_code_title">"Adja meg az alábbi számot a másik eszközén"</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_button_title">"Készen áll a beolvasásra"</string>
|
||||||
<string name="screen_qr_code_login_initial_state_item_1">"Nyissa meg az %1$set egy asztali eszközön"</string>
|
<string name="screen_qr_code_login_initial_state_item_1">"Nyissa meg az %1$set egy asztali eszközön"</string>
|
||||||
<string name="screen_qr_code_login_initial_state_item_2">"Kattintson a profilképére"</string>
|
<string name="screen_qr_code_login_initial_state_item_2">"Kattintson a profilképére"</string>
|
||||||
<string name="screen_qr_code_login_initial_state_item_3">"Válassza ezt: %1$s"</string>
|
<string name="screen_qr_code_login_initial_state_item_3">"Válassza ezt: %1$s"</string>
|
||||||
<string name="screen_qr_code_login_initial_state_item_3_action">"„Új eszköz összekapcsolása”"</string>
|
<string name="screen_qr_code_login_initial_state_item_3_action">"„Új eszköz összekapcsolása”"</string>
|
||||||
<string name="screen_qr_code_login_initial_state_item_4">"Kövesse a látható utasításokat"</string>
|
<string name="screen_qr_code_login_initial_state_item_4">"Olvassa be a QR-kódot ezzel az eszközzel"</string>
|
||||||
<string name="screen_qr_code_login_initial_state_title">"Nyissa meg az %1$set egy másik eszközön a QR-kód lekéréséhez."</string>
|
<string name="screen_qr_code_login_initial_state_title">"Nyissa meg az %1$set egy másik eszközön a QR-kód lekéréséhez."</string>
|
||||||
<string name="screen_qr_code_login_invalid_scan_state_description">"Használja a másik eszközön látható QR-kódot."</string>
|
<string name="screen_qr_code_login_invalid_scan_state_description">"Használja a másik eszközön látható QR-kódot."</string>
|
||||||
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Próbálja újra"</string>
|
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Próbálja újra"</string>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,33 @@
|
||||||
<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_notification_optin_subtitle">"Potrai modificare le tue impostazioni in seguito."</string>
|
<string name="screen_notification_optin_subtitle">"Potrai modificare le tue impostazioni in seguito."</string>
|
||||||
<string name="screen_notification_optin_title">"Consenti le notifiche e non perdere mai un messaggio"</string>
|
<string name="screen_notification_optin_title">"Consenti le notifiche e non perdere mai un messaggio"</string>
|
||||||
|
<string name="screen_qr_code_login_connecting_subtitle">"Stabilendo la connessione"</string>
|
||||||
|
<string name="screen_qr_code_login_connection_note_secure_state_description">"Non è stato possibile stabilire una connessione sicura con il nuovo dispositivo. I tuoi dispositivi esistenti sono ancora al sicuro e non devi preoccuparti di loro."</string>
|
||||||
|
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"E adesso?"</string>
|
||||||
|
<string name="screen_qr_code_login_connection_note_secure_state_list_item_1">"Prova ad accedere di nuovo con un codice QR nel caso si sia verificato un problema di rete."</string>
|
||||||
|
<string name="screen_qr_code_login_connection_note_secure_state_list_item_2">"Se riscontri lo stesso problema, prova con un altra rete wifi o usa i dati mobili al posto del wifi."</string>
|
||||||
|
<string name="screen_qr_code_login_connection_note_secure_state_list_item_3">"Se il problema persiste, accedi manualmente"</string>
|
||||||
|
<string name="screen_qr_code_login_connection_note_secure_state_title">"La connessione non è sicura"</string>
|
||||||
|
<string name="screen_qr_code_login_device_code_subtitle">"Ti verrà chiesto di inserire le due cifre mostrate su questo dispositivo."</string>
|
||||||
|
<string name="screen_qr_code_login_device_code_title">"Inserisci il numero qui sotto sull\'altro dispositivo"</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_item_1">"Apri %1$s su un dispositivo desktop"</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_item_2">"Clicca sul tuo avatar"</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_item_3">"Seleziona %1$s"</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_item_3_action">"\"Collega un nuovo dispositivo\""</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_item_4">"Segui le istruzioni mostrate"</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_title">"Apri %1$s su un altro dispositivo per ottenere il codice QR"</string>
|
||||||
|
<string name="screen_qr_code_login_invalid_scan_state_description">"Usa il codice QR mostrato sull\'altro dispositivo."</string>
|
||||||
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Riprova"</string>
|
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Riprova"</string>
|
||||||
|
<string name="screen_qr_code_login_invalid_scan_state_subtitle">"Codice QR sbagliato"</string>
|
||||||
|
<string name="screen_qr_code_login_no_camera_permission_button">"Vai alle impostazioni della fotocamera"</string>
|
||||||
|
<string name="screen_qr_code_login_no_camera_permission_state_description">"Per continuare, è necessario fornire l\'autorizzazione a %1$s per utilizzare la fotocamera del dispositivo."</string>
|
||||||
|
<string name="screen_qr_code_login_no_camera_permission_state_title">"Consenti l\'accesso alla fotocamera per la scansione del codice QR"</string>
|
||||||
|
<string name="screen_qr_code_login_scanning_state_title">"Scansiona il codice QR"</string>
|
||||||
|
<string name="screen_qr_code_login_start_over_button">"Ricomincia"</string>
|
||||||
|
<string name="screen_qr_code_login_unknown_error_description">"Si è verificato un errore inatteso. Riprova."</string>
|
||||||
|
<string name="screen_qr_code_login_verify_code_loading">"In attesa dell\'altro dispositivo"</string>
|
||||||
|
<string name="screen_qr_code_login_verify_code_subtitle">"Il fornitore dell\'account potrebbe richiedere il seguente codice per verificare l\'accesso."</string>
|
||||||
|
<string name="screen_qr_code_login_verify_code_title">"Il tuo codice di verifica"</string>
|
||||||
<string name="screen_welcome_bullet_1">"Chiamate, sondaggi, ricerche e altro ancora saranno aggiunti nel corso dell\'anno."</string>
|
<string name="screen_welcome_bullet_1">"Chiamate, sondaggi, ricerche e altro ancora saranno aggiunti nel corso dell\'anno."</string>
|
||||||
<string name="screen_welcome_bullet_2">"La cronologia dei messaggi per le stanze crittografate non è ancora disponibile."</string>
|
<string name="screen_welcome_bullet_2">"La cronologia dei messaggi per le stanze crittografate non è ancora disponibile."</string>
|
||||||
<string name="screen_welcome_bullet_3">"Ci piacerebbe sentire il tuo parere, facci sapere cosa ne pensi tramite la pagina delle impostazioni."</string>
|
<string name="screen_welcome_bullet_3">"Ci piacerebbe sentire il tuo parere, facci sapere cosa ne pensi tramite la pagina delle impostazioni."</string>
|
||||||
|
|
|
||||||
11
features/ftue/impl/src/main/res/values-ka/translations.xml
Normal file
11
features/ftue/impl/src/main/res/values-ka/translations.xml
Normal file
|
|
@ -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_notification_optin_subtitle">"თქვენ შეგიძლიათ შეცვალოთ თქვენი პარამეტრები მოგვიანებით."</string>
|
||||||
|
<string name="screen_notification_optin_title">"ყველა შეტყობინებაზე შეტყობინებების მიღება"</string>
|
||||||
|
<string name="screen_welcome_bullet_1">"ზარები, გამოკითხვები, ძიება და სხვა დაემატება ამ წლის ბოლოს."</string>
|
||||||
|
<string name="screen_welcome_bullet_2">"დაშიფრული ოთახებისთვის შეტყობინებების ისტორია ჯერ არ არის ხელმისაწვდომი."</string>
|
||||||
|
<string name="screen_welcome_bullet_3">"ჩვენ სიამოვნებით მოვისმინოთ თქვენგან, შეგვატყობინეთ რას ფიქრობთ პარამეტრების გვერდზე."</string>
|
||||||
|
<string name="screen_welcome_button">"დავიწყოთ!"</string>
|
||||||
|
<string name="screen_welcome_subtitle">"აი, რა უნდა იცოდეთ:"</string>
|
||||||
|
<string name="screen_welcome_title">"კეთილი იყოს თქვენი მობრძანება %1$s-ში!"</string>
|
||||||
|
</resources>
|
||||||
39
features/ftue/impl/src/main/res/values-pt/translations.xml
Normal file
39
features/ftue/impl/src/main/res/values-pt/translations.xml
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_notification_optin_subtitle">"Podes alterar as tuas definições mais tarde."</string>
|
||||||
|
<string name="screen_notification_optin_title">"Permite as notificações e nunca percas uma mensagem"</string>
|
||||||
|
<string name="screen_qr_code_login_connecting_subtitle">"A estabelecer uma ligação segura"</string>
|
||||||
|
<string name="screen_qr_code_login_connection_note_secure_state_description">"Não foi possível estabelecer uma ligação segura com o novo dispositivo. Os teus outros dispositivos continuam seguros, não precisas de te preocupar com eles."</string>
|
||||||
|
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"E agora?"</string>
|
||||||
|
<string name="screen_qr_code_login_connection_note_secure_state_list_item_1">"Tenta iniciar sessão novamente com um código QR, caso se trate de um problema de rede"</string>
|
||||||
|
<string name="screen_qr_code_login_connection_note_secure_state_list_item_2">"Se tiveres o mesmo problema, experimenta uma rede Wi-Fi diferente ou utiliza os teus dados móveis."</string>
|
||||||
|
<string name="screen_qr_code_login_connection_note_secure_state_list_item_3">"Se isso não funcionar, inicia sessão manualmente"</string>
|
||||||
|
<string name="screen_qr_code_login_connection_note_secure_state_title">"Ligação insegura"</string>
|
||||||
|
<string name="screen_qr_code_login_device_code_subtitle">"Ser-te-á pedido que insiras os dois dígitos indicados neste dispositivo."</string>
|
||||||
|
<string name="screen_qr_code_login_device_code_title">"Insere o número abaixo no teu dispositivo"</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_button_title">"Pronto para ler"</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_item_1">"Abre a %1$s num computador"</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_item_2">"Carrega no teu avatar"</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_item_3">"Seleciona %1$s"</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_item_3_action">"“Ligar novo dispositivo”"</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_item_4">"Lê o código QR com este dispositivo"</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_title">"Abre a %1$s noutro dispositivo para obteres o código QR"</string>
|
||||||
|
<string name="screen_qr_code_login_invalid_scan_state_description">"Lê o código QR apresentado no outro dispositivo."</string>
|
||||||
|
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Tentar novamente"</string>
|
||||||
|
<string name="screen_qr_code_login_invalid_scan_state_subtitle">"Código QR inválido"</string>
|
||||||
|
<string name="screen_qr_code_login_no_camera_permission_button">"Ir para as configurações da câmara"</string>
|
||||||
|
<string name="screen_qr_code_login_no_camera_permission_state_description">"Para continuar, tens que dar permissão à %1$s para aceder à câmara do teu dispositivo."</string>
|
||||||
|
<string name="screen_qr_code_login_no_camera_permission_state_title">"Permitir o acesso à câmara para ler o código QR"</string>
|
||||||
|
<string name="screen_qr_code_login_scanning_state_title">"Ler o código QR"</string>
|
||||||
|
<string name="screen_qr_code_login_start_over_button">"Começar de novo"</string>
|
||||||
|
<string name="screen_qr_code_login_unknown_error_description">"Ocorreu um erro inesperado. Tenta novamente."</string>
|
||||||
|
<string name="screen_qr_code_login_verify_code_loading">"À espera do teu outro dispositivo"</string>
|
||||||
|
<string name="screen_qr_code_login_verify_code_subtitle">"O teu fornecedor de conta pode pedir o seguinte código para verificar o início de sessão."</string>
|
||||||
|
<string name="screen_qr_code_login_verify_code_title">"O teu código de verificação"</string>
|
||||||
|
<string name="screen_welcome_bullet_1">"Chamadas, sondagens, pesquisa e mais funcionalidades vão ser adicionadas ao longo do ano."</string>
|
||||||
|
<string name="screen_welcome_bullet_2">"O histórico de mensagens em salas cifradas ainda não está disponível."</string>
|
||||||
|
<string name="screen_welcome_bullet_3">"Gostaríamos de ouvir a tua opinião, diz-nos o que pensas através da página de configurações."</string>
|
||||||
|
<string name="screen_welcome_button">"Vamos lá!"</string>
|
||||||
|
<string name="screen_welcome_subtitle">"Eis o que tens de saber:"</string>
|
||||||
|
<string name="screen_welcome_title">"Bem-vindo à %1$s!"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -2,7 +2,34 @@
|
||||||
<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_notification_optin_subtitle">"Puteți modifica setările mai târziu."</string>
|
<string name="screen_notification_optin_subtitle">"Puteți modifica setările mai târziu."</string>
|
||||||
<string name="screen_notification_optin_title">"Permiteți notificările și nu pierdeți niciodată un mesaj"</string>
|
<string name="screen_notification_optin_title">"Permiteți notificările și nu pierdeți niciodată un mesaj"</string>
|
||||||
|
<string name="screen_qr_code_login_connecting_subtitle">"Se stabilește o conexiune securizată"</string>
|
||||||
|
<string name="screen_qr_code_login_connection_note_secure_state_description">"Nu a putut fi făcută o conexiune sigură la noul dispozitiv. Dispozitivele existente sunt încă în siguranță și nu trebuie să vă faceți griji cu privire la ele."</string>
|
||||||
|
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Și acum?"</string>
|
||||||
|
<string name="screen_qr_code_login_connection_note_secure_state_list_item_1">"Încercați să vă conectați din nou cu un cod QR în cazul în care a fost o problemă de rețea."</string>
|
||||||
|
<string name="screen_qr_code_login_connection_note_secure_state_list_item_2">"Dacă întâmpinați aceeași problemă, încercați o altă rețea Wi-Fi sau utilizați datele mobile în loc de Wi-Fi."</string>
|
||||||
|
<string name="screen_qr_code_login_connection_note_secure_state_list_item_3">"Dacă nu funcționează, conectați-vă manual"</string>
|
||||||
|
<string name="screen_qr_code_login_connection_note_secure_state_title">"Conexiunea nu este sigură"</string>
|
||||||
|
<string name="screen_qr_code_login_device_code_subtitle">"Vi se va cere să introduceți cele două cifre afișate pe acest dispozitiv."</string>
|
||||||
|
<string name="screen_qr_code_login_device_code_title">"Introduceți numărul de mai jos pe celălalt dispozitiv"</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_button_title">"Gata de scanare"</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_item_1">"Deschideți %1$s pe un dispozitiv desktop"</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_item_2">"Faceți clic pe avatarul dumneavoastră"</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_item_3">"Selectați %1$s"</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_item_3_action">"„Conectați un dispozitiv nou”"</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_item_4">"Scanați codul QR cu acest dispozitiv"</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_title">"Deschideți %1$s pe un alt dispozitiv pentru a obține codul QR"</string>
|
||||||
|
<string name="screen_qr_code_login_invalid_scan_state_description">"Utilizați codul QR afișat pe celălalt dispozitiv."</string>
|
||||||
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Încercați din nou"</string>
|
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Încercați din nou"</string>
|
||||||
|
<string name="screen_qr_code_login_invalid_scan_state_subtitle">"Cod QR greșit"</string>
|
||||||
|
<string name="screen_qr_code_login_no_camera_permission_button">"Mergeți la setările camerei"</string>
|
||||||
|
<string name="screen_qr_code_login_no_camera_permission_state_description">"Trebuie să acordați permisiunea ca %1$s să folosească camera dispozitivului pentru a continua."</string>
|
||||||
|
<string name="screen_qr_code_login_no_camera_permission_state_title">"Permiteți accesul la cameră pentru a scana codul QR"</string>
|
||||||
|
<string name="screen_qr_code_login_scanning_state_title">"Scanați codul QR"</string>
|
||||||
|
<string name="screen_qr_code_login_start_over_button">"Începeți din nou"</string>
|
||||||
|
<string name="screen_qr_code_login_unknown_error_description">"A apărut o eroare neașteptată. Vă rugăm să încercați din nou."</string>
|
||||||
|
<string name="screen_qr_code_login_verify_code_loading">"În așteptarea celuilalt dispozitiv"</string>
|
||||||
|
<string name="screen_qr_code_login_verify_code_subtitle">"Furnizorul dumneavoastră de cont poate solicita următorul cod pentru a verifica conectarea."</string>
|
||||||
|
<string name="screen_qr_code_login_verify_code_title">"Codul dumneavoastră de verificare"</string>
|
||||||
<string name="screen_welcome_bullet_1">"Apelurile, sondajele, căutare și multe altele vor fi adăugate în cursul acestui an."</string>
|
<string name="screen_welcome_bullet_1">"Apelurile, sondajele, căutare și multe altele vor fi adăugate în cursul acestui an."</string>
|
||||||
<string name="screen_welcome_bullet_2">"Istoricul mesajelor pentru camerele criptate nu va fi disponibil în această actualizare."</string>
|
<string name="screen_welcome_bullet_2">"Istoricul mesajelor pentru camerele criptate nu va fi disponibil în această actualizare."</string>
|
||||||
<string name="screen_welcome_bullet_3">"Ne-ar plăcea să auzim de la dumneavoastră, spuneți-ne ce părere aveți prin intermediul paginii de setări."</string>
|
<string name="screen_welcome_bullet_3">"Ne-ar plăcea să auzim de la dumneavoastră, spuneți-ne ce părere aveți prin intermediul paginii de setări."</string>
|
||||||
|
|
|
||||||
|
|
@ -2,19 +2,20 @@
|
||||||
<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_notification_optin_subtitle">"Вы можете изменить настройки позже."</string>
|
<string name="screen_notification_optin_subtitle">"Вы можете изменить настройки позже."</string>
|
||||||
<string name="screen_notification_optin_title">"Разрешите уведомления и никогда не пропустите сообщение"</string>
|
<string name="screen_notification_optin_title">"Разрешите уведомления и никогда не пропустите сообщение"</string>
|
||||||
<string name="screen_qr_code_login_connecting_subtitle">"Установление соединения"</string>
|
<string name="screen_qr_code_login_connecting_subtitle">"Установление безопасного соединения"</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_description">"Не удалось установить безопасное соединение с новым устройством. Существующие устройства по-прежнему в безопасности, и вам не нужно беспокоиться о них."</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_description">"Не удалось установить безопасное соединение с новым устройством. Существующие устройства по-прежнему в безопасности, и вам не нужно беспокоиться о них."</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Что теперь?"</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Что теперь?"</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_list_item_1">"Попробуйте снова войти в систему с помощью QR-кода, если это была сетевая проблема"</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_list_item_1">"Попробуйте снова войти в систему с помощью QR-кода, если это была сетевая проблема"</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_list_item_2">"Если вы столкнулись с той же проблемой, попробуйте сменить точку доступа Wi-Fi или используйте мобильные данные"</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_list_item_2">"Если вы столкнулись с той же проблемой, попробуйте сменить точку доступа Wi-Fi или используйте мобильные данные"</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_list_item_3">"Если это не помогло, войдите вручную"</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_list_item_3">"Если это не помогло, войдите вручную"</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_title">"Соединение не защищено"</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_title">"Соединение не защищено"</string>
|
||||||
<string name="screen_qr_code_login_device_code_subtitle">"Вам будет предложено ввести две цифры, показанные ниже."</string>
|
<string name="screen_qr_code_login_device_code_subtitle">"Вам нужно будет ввести две цифры, показанные на этом устройстве."</string>
|
||||||
<string name="screen_qr_code_login_device_code_title">"Введите номер на своем устройстве"</string>
|
<string name="screen_qr_code_login_device_code_title">"Введите показанный номер на своем другом устройстве"</string>
|
||||||
<string name="screen_qr_code_login_initial_state_item_1">"Откройте %1$s на настольном устройстве"</string>
|
<string name="screen_qr_code_login_initial_state_item_1">"Откройте %1$s на настольном устройстве"</string>
|
||||||
<string name="screen_qr_code_login_initial_state_item_2">"Нажмите на свое изображение"</string>
|
<string name="screen_qr_code_login_initial_state_item_2">"Нажмите на свое изображение"</string>
|
||||||
<string name="screen_qr_code_login_initial_state_item_3">"Выбрать %1$s"</string>
|
<string name="screen_qr_code_login_initial_state_item_3">"Выбрать %1$s"</string>
|
||||||
<string name="screen_qr_code_login_initial_state_item_3_action">"\"Привязать новое устройство\""</string>
|
<string name="screen_qr_code_login_initial_state_item_3_action">"\"Привязать новое устройство\""</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_item_4">"Соблюдайте показанную инструкцию"</string>
|
||||||
<string name="screen_qr_code_login_initial_state_title">"Откройте %1$s на другом устройстве, чтобы получить QR-код"</string>
|
<string name="screen_qr_code_login_initial_state_title">"Откройте %1$s на другом устройстве, чтобы получить QR-код"</string>
|
||||||
<string name="screen_qr_code_login_invalid_scan_state_description">"Используйте QR-код, показанный на другом устройстве."</string>
|
<string name="screen_qr_code_login_invalid_scan_state_description">"Используйте QR-код, показанный на другом устройстве."</string>
|
||||||
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Повторить попытку"</string>
|
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Повторить попытку"</string>
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,12 @@
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_title">"Pripojenie nie je bezpečené"</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_title">"Pripojenie nie je bezpečené"</string>
|
||||||
<string name="screen_qr_code_login_device_code_subtitle">"Budete požiadaní o zadanie dvoch číslic zobrazených na tomto zariadení."</string>
|
<string name="screen_qr_code_login_device_code_subtitle">"Budete požiadaní o zadanie dvoch číslic zobrazených na tomto zariadení."</string>
|
||||||
<string name="screen_qr_code_login_device_code_title">"Zadajte nižšie uvedené číslo na vašom druhom zariadení"</string>
|
<string name="screen_qr_code_login_device_code_title">"Zadajte nižšie uvedené číslo na vašom druhom zariadení"</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_button_title">"Pripravené na skenovanie"</string>
|
||||||
<string name="screen_qr_code_login_initial_state_item_1">"Otvorte %1$s na stolnom zariadení"</string>
|
<string name="screen_qr_code_login_initial_state_item_1">"Otvorte %1$s na stolnom zariadení"</string>
|
||||||
<string name="screen_qr_code_login_initial_state_item_2">"Kliknite na svoj obrázok"</string>
|
<string name="screen_qr_code_login_initial_state_item_2">"Kliknite na svoj obrázok"</string>
|
||||||
<string name="screen_qr_code_login_initial_state_item_3">"Vyberte %1$s"</string>
|
<string name="screen_qr_code_login_initial_state_item_3">"Vyberte %1$s"</string>
|
||||||
<string name="screen_qr_code_login_initial_state_item_3_action">"„Prepojiť nové zariadenie“"</string>
|
<string name="screen_qr_code_login_initial_state_item_3_action">"„Prepojiť nové zariadenie“"</string>
|
||||||
<string name="screen_qr_code_login_initial_state_item_4">"Postupujte podľa zobrazených pokynov"</string>
|
<string name="screen_qr_code_login_initial_state_item_4">"Naskenujte QR kód pomocou tohto zariadenia"</string>
|
||||||
<string name="screen_qr_code_login_initial_state_title">"Ak chcete získať QR kód, otvorte %1$s na inom zariadení"</string>
|
<string name="screen_qr_code_login_initial_state_title">"Ak chcete získať QR kód, otvorte %1$s na inom zariadení"</string>
|
||||||
<string name="screen_qr_code_login_invalid_scan_state_description">"Použite QR kód zobrazený na druhom zariadení."</string>
|
<string name="screen_qr_code_login_invalid_scan_state_description">"Použite QR kód zobrazený na druhom zariadení."</string>
|
||||||
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Skúste to znova"</string>
|
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Skúste to znova"</string>
|
||||||
|
|
|
||||||
38
features/ftue/impl/src/main/res/values-zh/translations.xml
Normal file
38
features/ftue/impl/src/main/res/values-zh/translations.xml
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_notification_optin_subtitle">"您可以稍后更改设置。"</string>
|
||||||
|
<string name="screen_notification_optin_title">"允许通知,绝不错过任何消息"</string>
|
||||||
|
<string name="screen_qr_code_login_connecting_subtitle">"建立安全连接"</string>
|
||||||
|
<string name="screen_qr_code_login_connection_note_secure_state_description">"无法与新设备建立安全连接。您现有的设备仍然安全,无需担心。"</string>
|
||||||
|
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"现在怎么办?"</string>
|
||||||
|
<string name="screen_qr_code_login_connection_note_secure_state_list_item_1">"如果这是网络问题,请尝试使用二维码再次登录"</string>
|
||||||
|
<string name="screen_qr_code_login_connection_note_secure_state_list_item_2">"如果你遇到同样的问题,请尝试使用不同的 WiFi 网络或使用你的移动数据代替 WiFi"</string>
|
||||||
|
<string name="screen_qr_code_login_connection_note_secure_state_list_item_3">"如果不起作用,请手动登录"</string>
|
||||||
|
<string name="screen_qr_code_login_connection_note_secure_state_title">"连接不安全"</string>
|
||||||
|
<string name="screen_qr_code_login_device_code_subtitle">"您会被要求输入此设备上显示的两位数。"</string>
|
||||||
|
<string name="screen_qr_code_login_device_code_title">"在您的其他设备上输入下面的数字"</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_item_1">"在桌面设备上打开 %1$s"</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_item_2">"点击你的头像"</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_item_3">"选择 %1$s"</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_item_3_action">"「连接新设备」"</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_item_4">"按照说明进行操作"</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_title">"在另一台设备上打开 %1$s 以获取二维码"</string>
|
||||||
|
<string name="screen_qr_code_login_invalid_scan_state_description">"使用其他设备上显示的二维码。"</string>
|
||||||
|
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"再试一次"</string>
|
||||||
|
<string name="screen_qr_code_login_invalid_scan_state_subtitle">"二维码错误"</string>
|
||||||
|
<string name="screen_qr_code_login_no_camera_permission_button">"转到摄像头设置"</string>
|
||||||
|
<string name="screen_qr_code_login_no_camera_permission_state_description">"您需要授予 %1$s 使用设备摄像头的权限才能继续。"</string>
|
||||||
|
<string name="screen_qr_code_login_no_camera_permission_state_title">"允许摄像头权限以扫描 QR 码"</string>
|
||||||
|
<string name="screen_qr_code_login_scanning_state_title">"扫描二维码"</string>
|
||||||
|
<string name="screen_qr_code_login_start_over_button">"重新开始"</string>
|
||||||
|
<string name="screen_qr_code_login_unknown_error_description">"发生了意外错误。请再试一次。"</string>
|
||||||
|
<string name="screen_qr_code_login_verify_code_loading">"等着您的其他设备"</string>
|
||||||
|
<string name="screen_qr_code_login_verify_code_subtitle">"您的账户提供商可能会要求您提供以下代码来验证登录。"</string>
|
||||||
|
<string name="screen_qr_code_login_verify_code_title">"您的验证码"</string>
|
||||||
|
<string name="screen_welcome_bullet_1">"今年晚些时候将增加通话、投票、搜索等功能。"</string>
|
||||||
|
<string name="screen_welcome_bullet_2">"加密房间的消息历史记录尚不可用。"</string>
|
||||||
|
<string name="screen_welcome_bullet_3">"我们很乐意听取您的意见,请通过设置页面告诉我们您的想法。"</string>
|
||||||
|
<string name="screen_welcome_button">"开始吧!"</string>
|
||||||
|
<string name="screen_welcome_subtitle">"以下是您需要了解的内容:"</string>
|
||||||
|
<string name="screen_welcome_title">"欢迎使用 %1$s"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -11,11 +11,12 @@
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_title">"Connection not secure"</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_title">"Connection not secure"</string>
|
||||||
<string name="screen_qr_code_login_device_code_subtitle">"You’ll be asked to enter the two digits shown on this device."</string>
|
<string name="screen_qr_code_login_device_code_subtitle">"You’ll be asked to enter the two digits shown on this device."</string>
|
||||||
<string name="screen_qr_code_login_device_code_title">"Enter the number below on your other device"</string>
|
<string name="screen_qr_code_login_device_code_title">"Enter the number below on your other device"</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_button_title">"Ready to scan"</string>
|
||||||
<string name="screen_qr_code_login_initial_state_item_1">"Open %1$s on a desktop device"</string>
|
<string name="screen_qr_code_login_initial_state_item_1">"Open %1$s on a desktop device"</string>
|
||||||
<string name="screen_qr_code_login_initial_state_item_2">"Click on your avatar"</string>
|
<string name="screen_qr_code_login_initial_state_item_2">"Click on your avatar"</string>
|
||||||
<string name="screen_qr_code_login_initial_state_item_3">"Select %1$s"</string>
|
<string name="screen_qr_code_login_initial_state_item_3">"Select %1$s"</string>
|
||||||
<string name="screen_qr_code_login_initial_state_item_3_action">"“Link new device”"</string>
|
<string name="screen_qr_code_login_initial_state_item_3_action">"“Link new device”"</string>
|
||||||
<string name="screen_qr_code_login_initial_state_item_4">"Follow the instructions shown"</string>
|
<string name="screen_qr_code_login_initial_state_item_4">"Scan the QR code with this device"</string>
|
||||||
<string name="screen_qr_code_login_initial_state_title">"Open %1$s on another device to get the QR code"</string>
|
<string name="screen_qr_code_login_initial_state_title">"Open %1$s on another device to get the QR code"</string>
|
||||||
<string name="screen_qr_code_login_invalid_scan_state_description">"Use the QR code shown on the other device."</string>
|
<string name="screen_qr_code_login_invalid_scan_state_description">"Use the QR code shown on the other device."</string>
|
||||||
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Try again"</string>
|
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Try again"</string>
|
||||||
|
|
|
||||||
|
|
@ -25,4 +25,5 @@ android {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(projects.libraries.architecture)
|
implementation(projects.libraries.architecture)
|
||||||
implementation(projects.libraries.matrix.api)
|
implementation(projects.libraries.matrix.api)
|
||||||
|
implementation(projects.services.analytics.api)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,9 +33,8 @@ import io.element.android.libraries.architecture.runCatchingUpdatingState
|
||||||
import io.element.android.libraries.architecture.runUpdatingState
|
import io.element.android.libraries.architecture.runUpdatingState
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
import io.element.android.libraries.matrix.api.room.join.JoinRoom
|
||||||
import io.element.android.libraries.push.api.notifications.NotificationDrawerManager
|
import io.element.android.libraries.push.api.notifications.NotificationDrawerManager
|
||||||
import io.element.android.services.analytics.api.AnalyticsService
|
|
||||||
import io.element.android.services.analytics.api.extensions.toAnalyticsJoinedRoom
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.util.Optional
|
import java.util.Optional
|
||||||
|
|
@ -44,7 +43,7 @@ import kotlin.jvm.optionals.getOrNull
|
||||||
|
|
||||||
class AcceptDeclineInvitePresenter @Inject constructor(
|
class AcceptDeclineInvitePresenter @Inject constructor(
|
||||||
private val client: MatrixClient,
|
private val client: MatrixClient,
|
||||||
private val analyticsService: AnalyticsService,
|
private val joinRoom: JoinRoom,
|
||||||
private val notificationDrawerManager: NotificationDrawerManager,
|
private val notificationDrawerManager: NotificationDrawerManager,
|
||||||
) : Presenter<AcceptDeclineInviteState> {
|
) : Presenter<AcceptDeclineInviteState> {
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -59,9 +58,11 @@ class AcceptDeclineInvitePresenter @Inject constructor(
|
||||||
fun handleEvents(event: AcceptDeclineInviteEvents) {
|
fun handleEvents(event: AcceptDeclineInviteEvents) {
|
||||||
when (event) {
|
when (event) {
|
||||||
is AcceptDeclineInviteEvents.AcceptInvite -> {
|
is AcceptDeclineInviteEvents.AcceptInvite -> {
|
||||||
currentInvite = Optional.of(event.invite)
|
// currentInvite is used to render the decline confirmation dialog
|
||||||
localCoroutineScope.acceptInvite(event.invite.roomId, acceptedAction)
|
// and to reuse the roomId when the user confirm the rejection of the invitation.
|
||||||
|
// Just set it to empty here.
|
||||||
currentInvite = Optional.empty()
|
currentInvite = Optional.empty()
|
||||||
|
localCoroutineScope.acceptInvite(event.invite.roomId, acceptedAction)
|
||||||
}
|
}
|
||||||
|
|
||||||
is AcceptDeclineInviteEvents.DeclineInvite -> {
|
is AcceptDeclineInviteEvents.DeclineInvite -> {
|
||||||
|
|
@ -100,14 +101,18 @@ class AcceptDeclineInvitePresenter @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun CoroutineScope.acceptInvite(roomId: RoomId, acceptedAction: MutableState<AsyncAction<RoomId>>) = launch {
|
private fun CoroutineScope.acceptInvite(
|
||||||
|
roomId: RoomId,
|
||||||
|
acceptedAction: MutableState<AsyncAction<RoomId>>,
|
||||||
|
) = launch {
|
||||||
acceptedAction.runUpdatingState {
|
acceptedAction.runUpdatingState {
|
||||||
client.joinRoom(roomId)
|
joinRoom(
|
||||||
|
roomId = roomId,
|
||||||
|
serverNames = emptyList(),
|
||||||
|
trigger = JoinedRoom.Trigger.Invite,
|
||||||
|
)
|
||||||
.onSuccess {
|
.onSuccess {
|
||||||
notificationDrawerManager.clearMembershipNotificationForRoom(client.sessionId, roomId, doRender = true)
|
notificationDrawerManager.clearMembershipNotificationForRoom(client.sessionId, roomId, doRender = true)
|
||||||
client.getRoom(roomId)?.use { room ->
|
|
||||||
analyticsService.capture(room.toAnalyticsJoinedRoom(JoinedRoom.Trigger.Invite))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.map { roomId }
|
.map { roomId }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_invites_decline_chat_message">"დარწმუნებული ხართ, რომ გსურთ, უარი თქვათ მოწვევაზე %1$s-ში?"</string>
|
||||||
|
<string name="screen_invites_decline_chat_title">"მოწვევაზე უარის თქმა"</string>
|
||||||
|
<string name="screen_invites_decline_direct_chat_message">"დარწმუნებული ხართ, რომ გსურთ, უარი თქვათ ჩატზე %1$s-თან?"</string>
|
||||||
|
<string name="screen_invites_decline_direct_chat_title">"ჩატზე უარის თქვა"</string>
|
||||||
|
<string name="screen_invites_empty_list">"მოწვევები არ არის"</string>
|
||||||
|
<string name="screen_invites_invited_you">"%1$s (%2$s) მოგიწვიათ"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_invites_decline_chat_message">"Tens a certeza que queres rejeitar o convite para %1$s?"</string>
|
||||||
|
<string name="screen_invites_decline_chat_title">"Rejeitar conite"</string>
|
||||||
|
<string name="screen_invites_decline_direct_chat_message">"Tens a certeza que queres rejeitar esta conversa privada com %1$s?"</string>
|
||||||
|
<string name="screen_invites_decline_direct_chat_title">"Rejeitar conversa"</string>
|
||||||
|
<string name="screen_invites_empty_list">"Sem convites"</string>
|
||||||
|
<string name="screen_invites_invited_you">"%1$s (%2$s) convidou-te"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_invites_decline_chat_message">"您确定要拒绝加入 %1$s 的邀请吗?"</string>
|
||||||
|
<string name="screen_invites_decline_chat_title">"拒绝邀请"</string>
|
||||||
|
<string name="screen_invites_decline_direct_chat_message">"您确定要拒绝与 %1$s 开始私聊吗?"</string>
|
||||||
|
<string name="screen_invites_decline_direct_chat_title">"拒绝聊天"</string>
|
||||||
|
<string name="screen_invites_empty_list">"没有邀请"</string>
|
||||||
|
<string name="screen_invites_invited_you">"%1$s (%2$s)邀请了你"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package io.element.android.features.invite.impl.response
|
package io.element.android.features.invite.impl.response
|
||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import im.vector.app.features.analytics.plan.JoinedRoom
|
||||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents
|
import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents
|
||||||
import io.element.android.features.invite.api.response.InviteData
|
import io.element.android.features.invite.api.response.InviteData
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
|
|
@ -26,13 +27,13 @@ import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||||
import io.element.android.libraries.matrix.test.A_ROOM_NAME
|
import io.element.android.libraries.matrix.test.A_ROOM_NAME
|
||||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||||
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
||||||
|
import io.element.android.libraries.matrix.test.room.join.FakeJoinRoom
|
||||||
import io.element.android.libraries.push.api.notifications.NotificationDrawerManager
|
import io.element.android.libraries.push.api.notifications.NotificationDrawerManager
|
||||||
import io.element.android.libraries.push.test.notifications.FakeNotificationDrawerManager
|
import io.element.android.libraries.push.test.notifications.FakeNotificationDrawerManager
|
||||||
import io.element.android.services.analytics.api.AnalyticsService
|
|
||||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
|
||||||
import io.element.android.tests.testutils.WarmUpRule
|
import io.element.android.tests.testutils.WarmUpRule
|
||||||
import io.element.android.tests.testutils.lambda.assert
|
import io.element.android.tests.testutils.lambda.assert
|
||||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||||
|
import io.element.android.tests.testutils.lambda.value
|
||||||
import io.element.android.tests.testutils.test
|
import io.element.android.tests.testutils.test
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
|
|
@ -163,13 +164,10 @@ class AcceptDeclineInvitePresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - accepting invite error flow`() = runTest {
|
fun `present - accepting invite error flow`() = runTest {
|
||||||
val joinRoomFailure = lambdaRecorder { roomId: RoomId ->
|
val joinRoomFailure = lambdaRecorder { roomId: RoomId, _: List<String>, _: JoinedRoom.Trigger ->
|
||||||
Result.failure<Unit>(RuntimeException("Failed to join room $roomId"))
|
Result.failure<Unit>(RuntimeException("Failed to join room $roomId"))
|
||||||
}
|
}
|
||||||
val client = FakeMatrixClient().apply {
|
val presenter = createAcceptDeclineInvitePresenter(joinRoomLambda = joinRoomFailure)
|
||||||
joinRoomLambda = joinRoomFailure
|
|
||||||
}
|
|
||||||
val presenter = createAcceptDeclineInvitePresenter(client = client)
|
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val inviteData = anInviteData()
|
val inviteData = anInviteData()
|
||||||
awaitItem().also { state ->
|
awaitItem().also { state ->
|
||||||
|
|
@ -177,33 +175,35 @@ class AcceptDeclineInvitePresenterTest {
|
||||||
AcceptDeclineInviteEvents.AcceptInvite(inviteData)
|
AcceptDeclineInviteEvents.AcceptInvite(inviteData)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
skipItems(1)
|
|
||||||
awaitItem().also { state ->
|
awaitItem().also { state ->
|
||||||
assertThat(state.invite).isEqualTo(Optional.of(inviteData))
|
assertThat(state.invite).isEqualTo(Optional.empty<InviteData>())
|
||||||
|
assertThat(state.acceptAction).isEqualTo(AsyncAction.Loading)
|
||||||
|
}
|
||||||
|
awaitItem().also { state ->
|
||||||
assertThat(state.acceptAction).isInstanceOf(AsyncAction.Failure::class.java)
|
assertThat(state.acceptAction).isInstanceOf(AsyncAction.Failure::class.java)
|
||||||
state.eventSink(
|
state.eventSink(
|
||||||
InternalAcceptDeclineInviteEvents.DismissAcceptError
|
InternalAcceptDeclineInviteEvents.DismissAcceptError
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
skipItems(1)
|
|
||||||
awaitItem().also { state ->
|
awaitItem().also { state ->
|
||||||
assertThat(state.invite).isEqualTo(Optional.empty<InviteData>())
|
assertThat(state.invite).isEqualTo(Optional.empty<InviteData>())
|
||||||
assertThat(state.acceptAction).isInstanceOf(AsyncAction.Uninitialized::class.java)
|
assertThat(state.acceptAction).isInstanceOf(AsyncAction.Uninitialized::class.java)
|
||||||
}
|
}
|
||||||
cancelAndConsumeRemainingEvents()
|
cancelAndConsumeRemainingEvents()
|
||||||
}
|
}
|
||||||
assert(joinRoomFailure).isCalledOnce()
|
assert(joinRoomFailure)
|
||||||
|
.isCalledExactly(1)
|
||||||
|
.withSequence(
|
||||||
|
listOf(value(A_ROOM_ID), value(emptyList<String>()), value(JoinedRoom.Trigger.Invite))
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - accepting invite success flow`() = runTest {
|
fun `present - accepting invite success flow`() = runTest {
|
||||||
val joinRoomSuccess = lambdaRecorder { _: RoomId ->
|
val joinRoomSuccess = lambdaRecorder { _: RoomId, _: List<String>, _: JoinedRoom.Trigger ->
|
||||||
Result.success(Unit)
|
Result.success(Unit)
|
||||||
}
|
}
|
||||||
val client = FakeMatrixClient().apply {
|
val presenter = createAcceptDeclineInvitePresenter(joinRoomLambda = joinRoomSuccess)
|
||||||
joinRoomLambda = joinRoomSuccess
|
|
||||||
}
|
|
||||||
val presenter = createAcceptDeclineInvitePresenter(client = client)
|
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val inviteData = anInviteData()
|
val inviteData = anInviteData()
|
||||||
awaitItem().also { state ->
|
awaitItem().also { state ->
|
||||||
|
|
@ -211,14 +211,20 @@ class AcceptDeclineInvitePresenterTest {
|
||||||
AcceptDeclineInviteEvents.AcceptInvite(inviteData)
|
AcceptDeclineInviteEvents.AcceptInvite(inviteData)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
skipItems(1)
|
|
||||||
awaitItem().also { state ->
|
awaitItem().also { state ->
|
||||||
assertThat(state.invite).isEqualTo(Optional.of(inviteData))
|
assertThat(state.invite).isEqualTo(Optional.empty<InviteData>())
|
||||||
|
assertThat(state.acceptAction).isEqualTo(AsyncAction.Loading)
|
||||||
|
}
|
||||||
|
awaitItem().also { state ->
|
||||||
assertThat(state.acceptAction).isInstanceOf(AsyncAction.Success::class.java)
|
assertThat(state.acceptAction).isInstanceOf(AsyncAction.Success::class.java)
|
||||||
}
|
}
|
||||||
cancelAndConsumeRemainingEvents()
|
cancelAndConsumeRemainingEvents()
|
||||||
}
|
}
|
||||||
assert(joinRoomSuccess).isCalledOnce()
|
assert(joinRoomSuccess)
|
||||||
|
.isCalledExactly(1)
|
||||||
|
.withSequence(
|
||||||
|
listOf(value(A_ROOM_ID), value(emptyList<String>()), value(JoinedRoom.Trigger.Invite))
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun anInviteData(
|
private fun anInviteData(
|
||||||
|
|
@ -235,12 +241,14 @@ class AcceptDeclineInvitePresenterTest {
|
||||||
|
|
||||||
private fun createAcceptDeclineInvitePresenter(
|
private fun createAcceptDeclineInvitePresenter(
|
||||||
client: MatrixClient = FakeMatrixClient(),
|
client: MatrixClient = FakeMatrixClient(),
|
||||||
analyticsService: AnalyticsService = FakeAnalyticsService(),
|
joinRoomLambda: (RoomId, List<String>, JoinedRoom.Trigger) -> Result<Unit> = { _, _, _ ->
|
||||||
|
Result.success(Unit)
|
||||||
|
},
|
||||||
notificationDrawerManager: NotificationDrawerManager = FakeNotificationDrawerManager(),
|
notificationDrawerManager: NotificationDrawerManager = FakeNotificationDrawerManager(),
|
||||||
): AcceptDeclineInvitePresenter {
|
): AcceptDeclineInvitePresenter {
|
||||||
return AcceptDeclineInvitePresenter(
|
return AcceptDeclineInvitePresenter(
|
||||||
client = client,
|
client = client,
|
||||||
analyticsService = analyticsService,
|
joinRoom = FakeJoinRoom(joinRoomLambda),
|
||||||
notificationDrawerManager = notificationDrawerManager,
|
notificationDrawerManager = notificationDrawerManager,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,4 +26,5 @@ dependencies {
|
||||||
implementation(projects.libraries.architecture)
|
implementation(projects.libraries.architecture)
|
||||||
implementation(projects.libraries.matrix.api)
|
implementation(projects.libraries.matrix.api)
|
||||||
implementation(projects.features.roomdirectory.api)
|
implementation(projects.features.roomdirectory.api)
|
||||||
|
implementation(projects.services.analytics.api)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ package io.element.android.features.joinroom.api
|
||||||
|
|
||||||
import com.bumble.appyx.core.modality.BuildContext
|
import com.bumble.appyx.core.modality.BuildContext
|
||||||
import com.bumble.appyx.core.node.Node
|
import com.bumble.appyx.core.node.Node
|
||||||
|
import im.vector.app.features.analytics.plan.JoinedRoom
|
||||||
import io.element.android.features.roomdirectory.api.RoomDescription
|
import io.element.android.features.roomdirectory.api.RoomDescription
|
||||||
import io.element.android.libraries.architecture.FeatureEntryPoint
|
import io.element.android.libraries.architecture.FeatureEntryPoint
|
||||||
import io.element.android.libraries.architecture.NodeInputs
|
import io.element.android.libraries.architecture.NodeInputs
|
||||||
|
|
@ -32,5 +33,7 @@ interface JoinRoomEntryPoint : FeatureEntryPoint {
|
||||||
val roomId: RoomId,
|
val roomId: RoomId,
|
||||||
val roomIdOrAlias: RoomIdOrAlias,
|
val roomIdOrAlias: RoomIdOrAlias,
|
||||||
val roomDescription: Optional<RoomDescription>,
|
val roomDescription: Optional<RoomDescription>,
|
||||||
|
val serverNames: List<String>,
|
||||||
|
val trigger: JoinedRoom.Trigger,
|
||||||
) : NodeInputs
|
) : NodeInputs
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,9 +44,10 @@ dependencies {
|
||||||
implementation(projects.libraries.matrix.api)
|
implementation(projects.libraries.matrix.api)
|
||||||
implementation(projects.libraries.matrixui)
|
implementation(projects.libraries.matrixui)
|
||||||
implementation(projects.libraries.designsystem)
|
implementation(projects.libraries.designsystem)
|
||||||
|
implementation(projects.libraries.uiStrings)
|
||||||
implementation(projects.features.invite.api)
|
implementation(projects.features.invite.api)
|
||||||
implementation(projects.features.roomdirectory.api)
|
implementation(projects.features.roomdirectory.api)
|
||||||
implementation(projects.libraries.uiStrings)
|
implementation(projects.services.analytics.api)
|
||||||
|
|
||||||
testImplementation(libs.test.junit)
|
testImplementation(libs.test.junit)
|
||||||
testImplementation(libs.coroutines.test)
|
testImplementation(libs.coroutines.test)
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,8 @@ class JoinRoomNode @AssistedInject constructor(
|
||||||
inputs.roomId,
|
inputs.roomId,
|
||||||
inputs.roomIdOrAlias,
|
inputs.roomIdOrAlias,
|
||||||
inputs.roomDescription,
|
inputs.roomDescription,
|
||||||
|
inputs.serverNames,
|
||||||
|
inputs.trigger,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -49,6 +51,7 @@ class JoinRoomNode @AssistedInject constructor(
|
||||||
JoinRoomView(
|
JoinRoomView(
|
||||||
state = state,
|
state = state,
|
||||||
onBackPressed = ::navigateUp,
|
onBackPressed = ::navigateUp,
|
||||||
|
onJoinSuccess = ::navigateUp,
|
||||||
onKnockSuccess = ::navigateUp,
|
onKnockSuccess = ::navigateUp,
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
|
import im.vector.app.features.analytics.plan.JoinedRoom
|
||||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents
|
import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents
|
||||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
|
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
|
||||||
import io.element.android.features.invite.api.response.InviteData
|
import io.element.android.features.invite.api.response.InviteData
|
||||||
|
|
@ -41,10 +42,10 @@ import io.element.android.libraries.core.meta.BuildMeta
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
|
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
|
||||||
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
|
|
||||||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||||
import io.element.android.libraries.matrix.api.room.MatrixRoomInfo
|
import io.element.android.libraries.matrix.api.room.MatrixRoomInfo
|
||||||
import io.element.android.libraries.matrix.api.room.RoomType
|
import io.element.android.libraries.matrix.api.room.RoomType
|
||||||
|
import io.element.android.libraries.matrix.api.room.join.JoinRoom
|
||||||
import io.element.android.libraries.matrix.api.room.preview.RoomPreview
|
import io.element.android.libraries.matrix.api.room.preview.RoomPreview
|
||||||
import io.element.android.libraries.matrix.ui.model.toInviteSender
|
import io.element.android.libraries.matrix.ui.model.toInviteSender
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
|
@ -55,7 +56,10 @@ class JoinRoomPresenter @AssistedInject constructor(
|
||||||
@Assisted private val roomId: RoomId,
|
@Assisted private val roomId: RoomId,
|
||||||
@Assisted private val roomIdOrAlias: RoomIdOrAlias,
|
@Assisted private val roomIdOrAlias: RoomIdOrAlias,
|
||||||
@Assisted private val roomDescription: Optional<RoomDescription>,
|
@Assisted private val roomDescription: Optional<RoomDescription>,
|
||||||
|
@Assisted private val serverNames: List<String>,
|
||||||
|
@Assisted private val trigger: JoinedRoom.Trigger,
|
||||||
private val matrixClient: MatrixClient,
|
private val matrixClient: MatrixClient,
|
||||||
|
private val joinRoom: JoinRoom,
|
||||||
private val knockRoom: KnockRoom,
|
private val knockRoom: KnockRoom,
|
||||||
private val acceptDeclineInvitePresenter: Presenter<AcceptDeclineInviteState>,
|
private val acceptDeclineInvitePresenter: Presenter<AcceptDeclineInviteState>,
|
||||||
private val buildMeta: BuildMeta,
|
private val buildMeta: BuildMeta,
|
||||||
|
|
@ -65,6 +69,8 @@ class JoinRoomPresenter @AssistedInject constructor(
|
||||||
roomId: RoomId,
|
roomId: RoomId,
|
||||||
roomIdOrAlias: RoomIdOrAlias,
|
roomIdOrAlias: RoomIdOrAlias,
|
||||||
roomDescription: Optional<RoomDescription>,
|
roomDescription: Optional<RoomDescription>,
|
||||||
|
serverNames: List<String>,
|
||||||
|
trigger: JoinedRoom.Trigger,
|
||||||
): JoinRoomPresenter
|
): JoinRoomPresenter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -73,6 +79,7 @@ class JoinRoomPresenter @AssistedInject constructor(
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
var retryCount by remember { mutableIntStateOf(0) }
|
var retryCount by remember { mutableIntStateOf(0) }
|
||||||
val roomInfo by matrixClient.getRoomInfoFlow(roomId).collectAsState(initial = Optional.empty())
|
val roomInfo by matrixClient.getRoomInfoFlow(roomId).collectAsState(initial = Optional.empty())
|
||||||
|
val joinAction: MutableState<AsyncAction<Unit>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
|
||||||
val knockAction: MutableState<AsyncAction<Unit>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
|
val knockAction: MutableState<AsyncAction<Unit>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
|
||||||
val contentState by produceState<ContentState>(
|
val contentState by produceState<ContentState>(
|
||||||
initialValue = ContentState.Loading(roomIdOrAlias),
|
initialValue = ContentState.Loading(roomIdOrAlias),
|
||||||
|
|
@ -88,7 +95,7 @@ class JoinRoomPresenter @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
value = ContentState.Loading(roomIdOrAlias)
|
value = ContentState.Loading(roomIdOrAlias)
|
||||||
val result = matrixClient.getRoomPreview(roomId.toRoomIdOrAlias())
|
val result = matrixClient.getRoomPreviewFromRoomId(roomId, serverNames)
|
||||||
value = result.fold(
|
value = result.fold(
|
||||||
onSuccess = { roomPreview ->
|
onSuccess = { roomPreview ->
|
||||||
roomPreview.toContentState()
|
roomPreview.toContentState()
|
||||||
|
|
@ -108,16 +115,14 @@ class JoinRoomPresenter @AssistedInject constructor(
|
||||||
|
|
||||||
fun handleEvents(event: JoinRoomEvents) {
|
fun handleEvents(event: JoinRoomEvents) {
|
||||||
when (event) {
|
when (event) {
|
||||||
JoinRoomEvents.AcceptInvite,
|
JoinRoomEvents.JoinRoom -> coroutineScope.joinRoom(joinAction)
|
||||||
JoinRoomEvents.JoinRoom -> {
|
JoinRoomEvents.KnockRoom -> coroutineScope.knockRoom(knockAction)
|
||||||
|
JoinRoomEvents.AcceptInvite -> {
|
||||||
val inviteData = contentState.toInviteData() ?: return
|
val inviteData = contentState.toInviteData() ?: return
|
||||||
acceptDeclineInviteState.eventSink(
|
acceptDeclineInviteState.eventSink(
|
||||||
AcceptDeclineInviteEvents.AcceptInvite(inviteData)
|
AcceptDeclineInviteEvents.AcceptInvite(inviteData)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
JoinRoomEvents.KnockRoom -> {
|
|
||||||
coroutineScope.knockRoom(roomId, knockAction)
|
|
||||||
}
|
|
||||||
JoinRoomEvents.DeclineInvite -> {
|
JoinRoomEvents.DeclineInvite -> {
|
||||||
val inviteData = contentState.toInviteData() ?: return
|
val inviteData = contentState.toInviteData() ?: return
|
||||||
acceptDeclineInviteState.eventSink(
|
acceptDeclineInviteState.eventSink(
|
||||||
|
|
@ -129,6 +134,7 @@ class JoinRoomPresenter @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
JoinRoomEvents.ClearError -> {
|
JoinRoomEvents.ClearError -> {
|
||||||
knockAction.value = AsyncAction.Uninitialized
|
knockAction.value = AsyncAction.Uninitialized
|
||||||
|
joinAction.value = AsyncAction.Uninitialized
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -136,13 +142,24 @@ class JoinRoomPresenter @AssistedInject constructor(
|
||||||
return JoinRoomState(
|
return JoinRoomState(
|
||||||
contentState = contentState,
|
contentState = contentState,
|
||||||
acceptDeclineInviteState = acceptDeclineInviteState,
|
acceptDeclineInviteState = acceptDeclineInviteState,
|
||||||
|
joinAction = joinAction.value,
|
||||||
knockAction = knockAction.value,
|
knockAction = knockAction.value,
|
||||||
applicationName = buildMeta.applicationName,
|
applicationName = buildMeta.applicationName,
|
||||||
eventSink = ::handleEvents
|
eventSink = ::handleEvents
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun CoroutineScope.knockRoom(roomId: RoomId, knockAction: MutableState<AsyncAction<Unit>>) = launch {
|
private fun CoroutineScope.joinRoom(joinAction: MutableState<AsyncAction<Unit>>) = launch {
|
||||||
|
joinAction.runUpdatingState {
|
||||||
|
joinRoom.invoke(
|
||||||
|
roomId = roomId,
|
||||||
|
serverNames = serverNames,
|
||||||
|
trigger = trigger
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun CoroutineScope.knockRoom(knockAction: MutableState<AsyncAction<Unit>>) = launch {
|
||||||
knockAction.runUpdatingState {
|
knockAction.runUpdatingState {
|
||||||
knockRoom(roomId)
|
knockRoom(roomId)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ import io.element.android.libraries.matrix.ui.model.InviteSender
|
||||||
data class JoinRoomState(
|
data class JoinRoomState(
|
||||||
val contentState: ContentState,
|
val contentState: ContentState,
|
||||||
val acceptDeclineInviteState: AcceptDeclineInviteState,
|
val acceptDeclineInviteState: AcceptDeclineInviteState,
|
||||||
|
val joinAction: AsyncAction<Unit>,
|
||||||
val knockAction: AsyncAction<Unit>,
|
val knockAction: AsyncAction<Unit>,
|
||||||
val applicationName: String,
|
val applicationName: String,
|
||||||
val eventSink: (JoinRoomEvents) -> Unit
|
val eventSink: (JoinRoomEvents) -> Unit
|
||||||
|
|
|
||||||
|
|
@ -125,11 +125,13 @@ fun aLoadedContentState(
|
||||||
fun aJoinRoomState(
|
fun aJoinRoomState(
|
||||||
contentState: ContentState = aLoadedContentState(),
|
contentState: ContentState = aLoadedContentState(),
|
||||||
acceptDeclineInviteState: AcceptDeclineInviteState = anAcceptDeclineInviteState(),
|
acceptDeclineInviteState: AcceptDeclineInviteState = anAcceptDeclineInviteState(),
|
||||||
|
joinAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||||
knockAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
knockAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||||
eventSink: (JoinRoomEvents) -> Unit = {}
|
eventSink: (JoinRoomEvents) -> Unit = {}
|
||||||
) = JoinRoomState(
|
) = JoinRoomState(
|
||||||
contentState = contentState,
|
contentState = contentState,
|
||||||
acceptDeclineInviteState = acceptDeclineInviteState,
|
acceptDeclineInviteState = acceptDeclineInviteState,
|
||||||
|
joinAction = joinAction,
|
||||||
knockAction = knockAction,
|
knockAction = knockAction,
|
||||||
applicationName = "AppName",
|
applicationName = "AppName",
|
||||||
eventSink = eventSink
|
eventSink = eventSink
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,7 @@ import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
fun JoinRoomView(
|
fun JoinRoomView(
|
||||||
state: JoinRoomState,
|
state: JoinRoomState,
|
||||||
onBackPressed: () -> Unit,
|
onBackPressed: () -> Unit,
|
||||||
|
onJoinSuccess: () -> Unit,
|
||||||
onKnockSuccess: () -> Unit,
|
onKnockSuccess: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
|
|
@ -108,7 +109,11 @@ fun JoinRoomView(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
AsyncActionView(
|
||||||
|
async = state.joinAction,
|
||||||
|
onSuccess = { onJoinSuccess() },
|
||||||
|
onErrorDismiss = { state.eventSink(JoinRoomEvents.ClearError) },
|
||||||
|
)
|
||||||
AsyncActionView(
|
AsyncActionView(
|
||||||
async = state.knockAction,
|
async = state.knockAction,
|
||||||
onSuccess = { onKnockSuccess() },
|
onSuccess = { onKnockSuccess() },
|
||||||
|
|
@ -323,6 +328,7 @@ internal fun JoinRoomViewPreview(@PreviewParameter(JoinRoomStateProvider::class)
|
||||||
JoinRoomView(
|
JoinRoomView(
|
||||||
state = state,
|
state = state,
|
||||||
onBackPressed = { },
|
onBackPressed = { },
|
||||||
|
onJoinSuccess = { },
|
||||||
onKnockSuccess = { },
|
onKnockSuccess = { },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ package io.element.android.features.joinroom.impl.di
|
||||||
import com.squareup.anvil.annotations.ContributesTo
|
import com.squareup.anvil.annotations.ContributesTo
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
|
import im.vector.app.features.analytics.plan.JoinedRoom
|
||||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
|
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
|
||||||
import io.element.android.features.joinroom.impl.JoinRoomPresenter
|
import io.element.android.features.joinroom.impl.JoinRoomPresenter
|
||||||
import io.element.android.features.roomdirectory.api.RoomDescription
|
import io.element.android.features.roomdirectory.api.RoomDescription
|
||||||
|
|
@ -28,6 +29,7 @@ import io.element.android.libraries.di.SessionScope
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
|
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
|
||||||
|
import io.element.android.libraries.matrix.api.room.join.JoinRoom
|
||||||
import java.util.Optional
|
import java.util.Optional
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
|
|
@ -36,6 +38,7 @@ object JoinRoomModule {
|
||||||
@Provides
|
@Provides
|
||||||
fun providesJoinRoomPresenterFactory(
|
fun providesJoinRoomPresenterFactory(
|
||||||
client: MatrixClient,
|
client: MatrixClient,
|
||||||
|
joinRoom: JoinRoom,
|
||||||
knockRoom: KnockRoom,
|
knockRoom: KnockRoom,
|
||||||
acceptDeclineInvitePresenter: Presenter<AcceptDeclineInviteState>,
|
acceptDeclineInvitePresenter: Presenter<AcceptDeclineInviteState>,
|
||||||
buildMeta: BuildMeta,
|
buildMeta: BuildMeta,
|
||||||
|
|
@ -45,12 +48,17 @@ object JoinRoomModule {
|
||||||
roomId: RoomId,
|
roomId: RoomId,
|
||||||
roomIdOrAlias: RoomIdOrAlias,
|
roomIdOrAlias: RoomIdOrAlias,
|
||||||
roomDescription: Optional<RoomDescription>,
|
roomDescription: Optional<RoomDescription>,
|
||||||
|
serverNames: List<String>,
|
||||||
|
trigger: JoinedRoom.Trigger,
|
||||||
): JoinRoomPresenter {
|
): JoinRoomPresenter {
|
||||||
return JoinRoomPresenter(
|
return JoinRoomPresenter(
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
roomIdOrAlias = roomIdOrAlias,
|
roomIdOrAlias = roomIdOrAlias,
|
||||||
roomDescription = roomDescription,
|
roomDescription = roomDescription,
|
||||||
|
serverNames = serverNames,
|
||||||
|
trigger = trigger,
|
||||||
matrixClient = client,
|
matrixClient = client,
|
||||||
|
joinRoom = joinRoom,
|
||||||
knockRoom = knockRoom,
|
knockRoom = knockRoom,
|
||||||
acceptDeclineInvitePresenter = acceptDeclineInvitePresenter,
|
acceptDeclineInvitePresenter = acceptDeclineInvitePresenter,
|
||||||
buildMeta = buildMeta,
|
buildMeta = buildMeta,
|
||||||
|
|
|
||||||
|
|
@ -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_join_room_join_action">"Entra nella stanza"</string>
|
||||||
|
<string name="screen_join_room_knock_action">"Bussa per partecipare"</string>
|
||||||
|
<string name="screen_join_room_space_not_supported_description">"%1$s non supporta ancora gli spazi. Puoi accedere agli spazi sul web."</string>
|
||||||
|
<string name="screen_join_room_space_not_supported_title">"Gli spazi non sono ancora supportati"</string>
|
||||||
|
<string name="screen_join_room_subtitle_knock">"Clicca sul pulsante qui sotto e un amministratore della stanza riceverà una notifica. Potrai partecipare alla conversazione una volta approvato."</string>
|
||||||
|
<string name="screen_join_room_subtitle_no_preview">"Per visualizzare la cronologia dei messaggi devi essere un membro di questa stanza."</string>
|
||||||
|
<string name="screen_join_room_title_knock">"Vuoi entrare in questa stanza?"</string>
|
||||||
|
<string name="screen_join_room_title_no_preview">"L\'anteprima non è disponibile"</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_join_room_join_action">"Entrar na sala"</string>
|
||||||
|
<string name="screen_join_room_knock_action">"Bater à porta"</string>
|
||||||
|
<string name="screen_join_room_space_not_supported_description">"A %1$s ainda não funciona com espaços. Podes usá-los na aplicação web."</string>
|
||||||
|
<string name="screen_join_room_space_not_supported_title">"Os espaços ainda não estão implementados"</string>
|
||||||
|
<string name="screen_join_room_subtitle_knock">"Carrega no botão abaixo para notificar um administrador da sala. Poderás entrar quando te aprovarem."</string>
|
||||||
|
<string name="screen_join_room_subtitle_no_preview">"Apenas os participantes podem ver o histórico de mensagens."</string>
|
||||||
|
<string name="screen_join_room_title_knock">"Queres entrar nesta sala?"</string>
|
||||||
|
<string name="screen_join_room_title_no_preview">"Pré-visualização indisponível"</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_join_room_join_action">"Alăturați-vă camerei"</string>
|
||||||
|
<string name="screen_join_room_knock_action">"Bateți pentru a vă alătura"</string>
|
||||||
|
<string name="screen_join_room_space_not_supported_description">"%1$s nu suporta încă spații. Puteți accesa spațiile pe web."</string>
|
||||||
|
<string name="screen_join_room_space_not_supported_title">"Spațiile nu sunt încă suportate"</string>
|
||||||
|
<string name="screen_join_room_subtitle_knock">"Faceți clic pe butonul de mai jos și un administrator de cameră va fi notificat. Veți putea să vă alăturați conversației odată aprobată."</string>
|
||||||
|
<string name="screen_join_room_subtitle_no_preview">"Trebuie să fiți membru al acestei camere pentru a vizualiza istoricul mesajelor."</string>
|
||||||
|
<string name="screen_join_room_title_knock">"Doriți să vă alăturați acestei camere?"</string>
|
||||||
|
<string name="screen_join_room_title_no_preview">"Previzualizare indisponibilă"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -2,6 +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_join_room_join_action">"Присоединиться к комнате"</string>
|
<string name="screen_join_room_join_action">"Присоединиться к комнате"</string>
|
||||||
<string name="screen_join_room_knock_action">"Постучите, чтобы присоединиться"</string>
|
<string name="screen_join_room_knock_action">"Постучите, чтобы присоединиться"</string>
|
||||||
|
<string name="screen_join_room_space_not_supported_description">"%1$s еще не поддерживает пространства. Вы можете получить к ним доступ в веб-версии."</string>
|
||||||
|
<string name="screen_join_room_space_not_supported_title">"Пространства пока не поддерживаются"</string>
|
||||||
<string name="screen_join_room_subtitle_knock">"Нажмите кнопку ниже и администратор комнаты получит уведомление. После одобрения вы сможете присоединиться к обсуждению."</string>
|
<string name="screen_join_room_subtitle_knock">"Нажмите кнопку ниже и администратор комнаты получит уведомление. После одобрения вы сможете присоединиться к обсуждению."</string>
|
||||||
<string name="screen_join_room_subtitle_no_preview">"Вы должны быть участником этой комнаты, чтобы просмотреть историю сообщений."</string>
|
<string name="screen_join_room_subtitle_no_preview">"Вы должны быть участником этой комнаты, чтобы просмотреть историю сообщений."</string>
|
||||||
<string name="screen_join_room_title_knock">"Хотите присоединиться к этой комнате?"</string>
|
<string name="screen_join_room_title_knock">"Хотите присоединиться к этой комнате?"</string>
|
||||||
|
|
|
||||||
|
|
@ -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_join_room_join_action">"加入聊天室"</string>
|
||||||
|
<string name="screen_join_room_knock_action">"加入房间"</string>
|
||||||
|
<string name="screen_join_room_space_not_supported_description">"%1$s 尚不支持空间。您可以通过 Web 端访问空间"</string>
|
||||||
|
<string name="screen_join_room_space_not_supported_title">"空间尚不支持"</string>
|
||||||
|
<string name="screen_join_room_subtitle_knock">"点击下面的按钮,系统将通知房间管理员。获得批准后,您将能够加入对话。"</string>
|
||||||
|
<string name="screen_join_room_subtitle_no_preview">"只有聊天室成员才能查看消息历史记录。"</string>
|
||||||
|
<string name="screen_join_room_title_knock">"想加入这个房间吗?"</string>
|
||||||
|
<string name="screen_join_room_title_no_preview">"预览不可用"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package io.element.android.features.joinroom.impl
|
package io.element.android.features.joinroom.impl
|
||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import im.vector.app.features.analytics.plan.JoinedRoom
|
||||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents
|
import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents
|
||||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
|
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
|
||||||
import io.element.android.features.invite.api.response.anAcceptDeclineInviteState
|
import io.element.android.features.invite.api.response.anAcceptDeclineInviteState
|
||||||
|
|
@ -36,10 +37,12 @@ import io.element.android.libraries.matrix.api.room.preview.RoomPreview
|
||||||
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_ROOM_ID
|
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||||
import io.element.android.libraries.matrix.test.A_ROOM_NAME
|
import io.element.android.libraries.matrix.test.A_ROOM_NAME
|
||||||
|
import io.element.android.libraries.matrix.test.A_SERVER_LIST
|
||||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||||
import io.element.android.libraries.matrix.test.room.aRoomInfo
|
import io.element.android.libraries.matrix.test.room.aRoomInfo
|
||||||
import io.element.android.libraries.matrix.test.room.aRoomMember
|
import io.element.android.libraries.matrix.test.room.aRoomMember
|
||||||
|
import io.element.android.libraries.matrix.test.room.join.FakeJoinRoom
|
||||||
import io.element.android.libraries.matrix.ui.model.toInviteSender
|
import io.element.android.libraries.matrix.ui.model.toInviteSender
|
||||||
import io.element.android.tests.testutils.WarmUpRule
|
import io.element.android.tests.testutils.WarmUpRule
|
||||||
import io.element.android.tests.testutils.lambda.assert
|
import io.element.android.tests.testutils.lambda.assert
|
||||||
|
|
@ -174,6 +177,59 @@ class JoinRoomPresenterTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - when room is joined with success, all the parameters are provided`() = runTest {
|
||||||
|
val aTrigger = JoinedRoom.Trigger.MobilePermalink
|
||||||
|
val joinRoomLambda = lambdaRecorder { _: RoomId, _: List<String>, _: JoinedRoom.Trigger ->
|
||||||
|
Result.success(Unit)
|
||||||
|
}
|
||||||
|
val presenter = createJoinRoomPresenter(
|
||||||
|
trigger = aTrigger,
|
||||||
|
serverNames = A_SERVER_LIST,
|
||||||
|
joinRoomLambda = joinRoomLambda,
|
||||||
|
)
|
||||||
|
presenter.test {
|
||||||
|
skipItems(1)
|
||||||
|
awaitItem().also { state ->
|
||||||
|
state.eventSink(JoinRoomEvents.JoinRoom)
|
||||||
|
}
|
||||||
|
awaitItem().also { state ->
|
||||||
|
assertThat(state.joinAction).isEqualTo(AsyncAction.Loading)
|
||||||
|
}
|
||||||
|
awaitItem().also { state ->
|
||||||
|
assertThat(state.joinAction).isEqualTo(AsyncAction.Success(Unit))
|
||||||
|
}
|
||||||
|
joinRoomLambda.assertions()
|
||||||
|
.isCalledOnce()
|
||||||
|
.with(value(A_ROOM_ID), value(A_SERVER_LIST), value(aTrigger))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - when room is joined with error, it is possible to clear the error`() = runTest {
|
||||||
|
val presenter = createJoinRoomPresenter(
|
||||||
|
joinRoomLambda = { _, _, _ ->
|
||||||
|
Result.failure(AN_EXCEPTION)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
presenter.test {
|
||||||
|
skipItems(1)
|
||||||
|
awaitItem().also { state ->
|
||||||
|
state.eventSink(JoinRoomEvents.JoinRoom)
|
||||||
|
}
|
||||||
|
awaitItem().also { state ->
|
||||||
|
assertThat(state.joinAction).isEqualTo(AsyncAction.Loading)
|
||||||
|
}
|
||||||
|
awaitItem().also { state ->
|
||||||
|
assertThat(state.joinAction).isEqualTo(AsyncAction.Failure(AN_EXCEPTION))
|
||||||
|
state.eventSink(JoinRoomEvents.ClearError)
|
||||||
|
}
|
||||||
|
awaitItem().also { state ->
|
||||||
|
assertThat(state.joinAction).isEqualTo(AsyncAction.Uninitialized)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - when room is left and public then join authorization is equal to canJoin`() = runTest {
|
fun `present - when room is left and public then join authorization is equal to canJoin`() = runTest {
|
||||||
val roomInfo = aRoomInfo(currentUserMembership = CurrentUserMembership.LEFT, isPublic = true)
|
val roomInfo = aRoomInfo(currentUserMembership = CurrentUserMembership.LEFT, isPublic = true)
|
||||||
|
|
@ -310,7 +366,7 @@ class JoinRoomPresenterTest {
|
||||||
@Test
|
@Test
|
||||||
fun `present - when room is not known RoomPreview is loaded`() = runTest {
|
fun `present - when room is not known RoomPreview is loaded`() = runTest {
|
||||||
val client = FakeMatrixClient(
|
val client = FakeMatrixClient(
|
||||||
getRoomPreviewResult = {
|
getRoomPreviewFromRoomIdResult = { _, _ ->
|
||||||
Result.success(
|
Result.success(
|
||||||
RoomPreview(
|
RoomPreview(
|
||||||
roomId = A_ROOM_ID,
|
roomId = A_ROOM_ID,
|
||||||
|
|
@ -355,7 +411,7 @@ class JoinRoomPresenterTest {
|
||||||
@Test
|
@Test
|
||||||
fun `present - when room is not known RoomPreview is loaded with error`() = runTest {
|
fun `present - when room is not known RoomPreview is loaded with error`() = runTest {
|
||||||
val client = FakeMatrixClient(
|
val client = FakeMatrixClient(
|
||||||
getRoomPreviewResult = {
|
getRoomPreviewFromRoomIdResult = { _, _ ->
|
||||||
Result.failure(AN_EXCEPTION)
|
Result.failure(AN_EXCEPTION)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
@ -393,7 +449,7 @@ class JoinRoomPresenterTest {
|
||||||
@Test
|
@Test
|
||||||
fun `present - when room is not known RoomPreview is loaded with error 403`() = runTest {
|
fun `present - when room is not known RoomPreview is loaded with error 403`() = runTest {
|
||||||
val client = FakeMatrixClient(
|
val client = FakeMatrixClient(
|
||||||
getRoomPreviewResult = {
|
getRoomPreviewFromRoomIdResult = { _, _ ->
|
||||||
Result.failure(Exception("403"))
|
Result.failure(Exception("403"))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
@ -415,7 +471,12 @@ class JoinRoomPresenterTest {
|
||||||
private fun createJoinRoomPresenter(
|
private fun createJoinRoomPresenter(
|
||||||
roomId: RoomId = A_ROOM_ID,
|
roomId: RoomId = A_ROOM_ID,
|
||||||
roomDescription: Optional<RoomDescription> = Optional.empty(),
|
roomDescription: Optional<RoomDescription> = Optional.empty(),
|
||||||
|
serverNames: List<String> = emptyList(),
|
||||||
|
trigger: JoinedRoom.Trigger = JoinedRoom.Trigger.Invite,
|
||||||
matrixClient: MatrixClient = FakeMatrixClient(),
|
matrixClient: MatrixClient = FakeMatrixClient(),
|
||||||
|
joinRoomLambda: (RoomId, List<String>, JoinedRoom.Trigger) -> Result<Unit> = { _, _, _ ->
|
||||||
|
Result.success(Unit)
|
||||||
|
},
|
||||||
knockRoom: KnockRoom = FakeKnockRoom(),
|
knockRoom: KnockRoom = FakeKnockRoom(),
|
||||||
buildMeta: BuildMeta = aBuildMeta(applicationName = "AppName"),
|
buildMeta: BuildMeta = aBuildMeta(applicationName = "AppName"),
|
||||||
acceptDeclineInvitePresenter: Presenter<AcceptDeclineInviteState> = Presenter { anAcceptDeclineInviteState() }
|
acceptDeclineInvitePresenter: Presenter<AcceptDeclineInviteState> = Presenter { anAcceptDeclineInviteState() }
|
||||||
|
|
@ -424,7 +485,10 @@ class JoinRoomPresenterTest {
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
roomIdOrAlias = roomId.toRoomIdOrAlias(),
|
roomIdOrAlias = roomId.toRoomIdOrAlias(),
|
||||||
roomDescription = roomDescription,
|
roomDescription = roomDescription,
|
||||||
|
serverNames = serverNames,
|
||||||
|
trigger = trigger,
|
||||||
matrixClient = matrixClient,
|
matrixClient = matrixClient,
|
||||||
|
joinRoom = FakeJoinRoom(joinRoomLambda),
|
||||||
knockRoom = knockRoom,
|
knockRoom = knockRoom,
|
||||||
buildMeta = buildMeta,
|
buildMeta = buildMeta,
|
||||||
acceptDeclineInvitePresenter = acceptDeclineInvitePresenter
|
acceptDeclineInvitePresenter = acceptDeclineInvitePresenter
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,34 @@ class JoinRoomViewTest {
|
||||||
eventsRecorder.assertSingle(JoinRoomEvents.ClearError)
|
eventsRecorder.assertSingle(JoinRoomEvents.ClearError)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `clicking on closing Join error emits the expected Event`() {
|
||||||
|
val eventsRecorder = EventsRecorder<JoinRoomEvents>()
|
||||||
|
rule.setJoinRoomView(
|
||||||
|
aJoinRoomState(
|
||||||
|
contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.CanKnock),
|
||||||
|
joinAction = AsyncAction.Failure(Exception("Error")),
|
||||||
|
eventSink = eventsRecorder,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
rule.clickOn(CommonStrings.action_ok)
|
||||||
|
eventsRecorder.assertSingle(JoinRoomEvents.ClearError)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when joining room is successful, the expected callback is invoked`() {
|
||||||
|
val eventsRecorder = EventsRecorder<JoinRoomEvents>(expectEvents = false)
|
||||||
|
ensureCalledOnce {
|
||||||
|
rule.setJoinRoomView(
|
||||||
|
aJoinRoomState(
|
||||||
|
joinAction = AsyncAction.Success(Unit),
|
||||||
|
eventSink = eventsRecorder,
|
||||||
|
),
|
||||||
|
onJoinSuccess = it
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on Accept invitation IsInvited room emits the expected Event`() {
|
fun `clicking on Accept invitation IsInvited room emits the expected Event`() {
|
||||||
val eventsRecorder = EventsRecorder<JoinRoomEvents>()
|
val eventsRecorder = EventsRecorder<JoinRoomEvents>()
|
||||||
|
|
@ -149,12 +177,14 @@ class JoinRoomViewTest {
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setJoinRoomView(
|
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setJoinRoomView(
|
||||||
state: JoinRoomState,
|
state: JoinRoomState,
|
||||||
onBackPressed: () -> Unit = EnsureNeverCalled(),
|
onBackPressed: () -> Unit = EnsureNeverCalled(),
|
||||||
|
onJoinSuccess: () -> Unit = EnsureNeverCalled(),
|
||||||
onKnockSuccess: () -> Unit = EnsureNeverCalled(),
|
onKnockSuccess: () -> Unit = EnsureNeverCalled(),
|
||||||
) {
|
) {
|
||||||
setContent {
|
setContent {
|
||||||
JoinRoomView(
|
JoinRoomView(
|
||||||
state = state,
|
state = state,
|
||||||
onBackPressed = onBackPressed,
|
onBackPressed = onBackPressed,
|
||||||
|
onJoinSuccess = onJoinSuccess,
|
||||||
onKnockSuccess = onKnockSuccess,
|
onKnockSuccess = onKnockSuccess,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
<?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="leave_conversation_alert_subtitle">"¿Estás seguro de que quieres salir de esta conversación? Esta conversación no es pública y no podrás volver a unirte sin una invitación."</string>
|
||||||
<string name="leave_room_alert_empty_subtitle">"¿Estás seguro de que quieres salir de esta sala? Eres la única persona aquí. Si te vas, nadie podrá unirse en el futuro, ni siquiera tú."</string>
|
<string name="leave_room_alert_empty_subtitle">"¿Estás seguro de que quieres salir de esta sala? Eres la única persona aquí. Si te vas, nadie podrá unirse en el futuro, ni siquiera tú."</string>
|
||||||
<string name="leave_room_alert_private_subtitle">"¿Estás seguro de que quieres abandonar esta sala? Esta sala no es pública y no podrás volver a entrar sin una invitación."</string>
|
<string name="leave_room_alert_private_subtitle">"¿Estás seguro de que quieres abandonar esta sala? Esta sala no es pública y no podrás volver a entrar sin una invitación."</string>
|
||||||
<string name="leave_room_alert_subtitle">"¿Seguro que quieres salir de la habitación?"</string>
|
<string name="leave_room_alert_subtitle">"¿Seguro que quieres salir de la habitación?"</string>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="leave_room_alert_empty_subtitle">"დარწმუნებული ბრძანდებით, რომ ამ ოთახის დატოვება გსურთ? თქვენ აქ მარტო ხართ და ჩატის დატოვებისას აქ თქვენს ჩათვლით ვერავინ ვერ გაწევრიანდება."</string>
|
||||||
|
<string name="leave_room_alert_private_subtitle">"დარწმუნებული ბრძანდებით, რომ ამ ოთახის დატოვება გსურთ? ეს ოთახი არ არის საჯარო და მოწვევის გარეშე ვერ შეძლებთ ხელახლა გაწევრიანებას."</string>
|
||||||
|
<string name="leave_room_alert_subtitle">"დარწმუნებული ბრძანდებით, რომ ოთახის დატოვება გსურთ?"</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="leave_conversation_alert_subtitle">"Tens a certeza que queres sair desta conversa? Não é pública, logo não poderás voltar a participar sem um convite."</string>
|
||||||
|
<string name="leave_room_alert_empty_subtitle">"Tens a certeza que queres sair desta sala? És o único participante. Se saíres, ninguém mais poderá entrar, incluindo tu."</string>
|
||||||
|
<string name="leave_room_alert_private_subtitle">"Tens a certeza que queres sair desta sala? Atenta que não é pública e portanto não poderás voltar a entrar sem um novo convite."</string>
|
||||||
|
<string name="leave_room_alert_subtitle">"Tens a certeza que queres sair da sala?"</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="leave_conversation_alert_subtitle">"您确定要离开此对话吗?此对话不公开,未经邀请您将无法重新加入。"</string>
|
||||||
|
<string name="leave_room_alert_empty_subtitle">"你确定要离开这个房间吗?这里只有你一个人,如果你走了,包括你在内的所有人都无法进入此房间。"</string>
|
||||||
|
<string name="leave_room_alert_private_subtitle">"你确定要离开这个房间吗?这个房间不是公开的,如果没有邀请,你将无法重新加入。"</string>
|
||||||
|
<string name="leave_room_alert_subtitle">"你确定要离开房间吗?"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -16,17 +16,19 @@
|
||||||
|
|
||||||
package io.element.android.features.lockscreen.api
|
package io.element.android.features.lockscreen.api
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
import com.bumble.appyx.core.modality.BuildContext
|
import com.bumble.appyx.core.modality.BuildContext
|
||||||
import com.bumble.appyx.core.node.Node
|
import com.bumble.appyx.core.node.Node
|
||||||
import com.bumble.appyx.core.plugin.Plugin
|
import com.bumble.appyx.core.plugin.Plugin
|
||||||
import io.element.android.libraries.architecture.FeatureEntryPoint
|
import io.element.android.libraries.architecture.FeatureEntryPoint
|
||||||
|
|
||||||
interface LockScreenEntryPoint : FeatureEntryPoint {
|
interface LockScreenEntryPoint : FeatureEntryPoint {
|
||||||
fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder
|
fun nodeBuilder(parentNode: Node, buildContext: BuildContext, navTarget: Target): NodeBuilder
|
||||||
|
fun pinUnlockIntent(context: Context): Intent
|
||||||
|
|
||||||
interface NodeBuilder {
|
interface NodeBuilder {
|
||||||
fun callback(callback: Callback): NodeBuilder
|
fun callback(callback: Callback): NodeBuilder
|
||||||
fun target(target: Target): NodeBuilder
|
|
||||||
fun build(): Node
|
fun build(): Node
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -37,6 +39,5 @@ interface LockScreenEntryPoint : FeatureEntryPoint {
|
||||||
enum class Target {
|
enum class Target {
|
||||||
Settings,
|
Settings,
|
||||||
Setup,
|
Setup,
|
||||||
Unlock
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
25
features/lockscreen/impl/src/main/AndroidManifest.xml
Normal file
25
features/lockscreen/impl/src/main/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
<!--
|
||||||
|
~ Copyright (c) 2024 New Vector Ltd
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<application>
|
||||||
|
<activity
|
||||||
|
android:name="io.element.android.features.lockscreen.impl.unlock.activity.PinUnlockActivity"
|
||||||
|
android:configChanges="screenSize|screenLayout|orientation|keyboardHidden|keyboard|navigation|uiMode"/>
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
||||||
|
|
@ -16,18 +16,20 @@
|
||||||
|
|
||||||
package io.element.android.features.lockscreen.impl
|
package io.element.android.features.lockscreen.impl
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
import com.bumble.appyx.core.modality.BuildContext
|
import com.bumble.appyx.core.modality.BuildContext
|
||||||
import com.bumble.appyx.core.node.Node
|
import com.bumble.appyx.core.node.Node
|
||||||
import com.squareup.anvil.annotations.ContributesBinding
|
import com.squareup.anvil.annotations.ContributesBinding
|
||||||
import io.element.android.features.lockscreen.api.LockScreenEntryPoint
|
import io.element.android.features.lockscreen.api.LockScreenEntryPoint
|
||||||
|
import io.element.android.features.lockscreen.impl.unlock.activity.PinUnlockActivity
|
||||||
import io.element.android.libraries.architecture.createNode
|
import io.element.android.libraries.architecture.createNode
|
||||||
import io.element.android.libraries.di.AppScope
|
import io.element.android.libraries.di.AppScope
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ContributesBinding(AppScope::class)
|
@ContributesBinding(AppScope::class)
|
||||||
class DefaultLockScreenEntryPoint @Inject constructor() : LockScreenEntryPoint {
|
class DefaultLockScreenEntryPoint @Inject constructor() : LockScreenEntryPoint {
|
||||||
override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): LockScreenEntryPoint.NodeBuilder {
|
override fun nodeBuilder(parentNode: Node, buildContext: BuildContext, navTarget: LockScreenEntryPoint.Target): LockScreenEntryPoint.NodeBuilder {
|
||||||
var innerTarget: LockScreenEntryPoint.Target = LockScreenEntryPoint.Target.Unlock
|
|
||||||
val callbacks = mutableListOf<LockScreenEntryPoint.Callback>()
|
val callbacks = mutableListOf<LockScreenEntryPoint.Callback>()
|
||||||
|
|
||||||
return object : LockScreenEntryPoint.NodeBuilder {
|
return object : LockScreenEntryPoint.NodeBuilder {
|
||||||
|
|
@ -36,15 +38,9 @@ class DefaultLockScreenEntryPoint @Inject constructor() : LockScreenEntryPoint {
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun target(target: LockScreenEntryPoint.Target): LockScreenEntryPoint.NodeBuilder {
|
|
||||||
innerTarget = target
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun build(): Node {
|
override fun build(): Node {
|
||||||
val inputs = LockScreenFlowNode.Inputs(
|
val inputs = LockScreenFlowNode.Inputs(
|
||||||
when (innerTarget) {
|
when (navTarget) {
|
||||||
LockScreenEntryPoint.Target.Unlock -> LockScreenFlowNode.NavTarget.Unlock
|
|
||||||
LockScreenEntryPoint.Target.Setup -> LockScreenFlowNode.NavTarget.Setup
|
LockScreenEntryPoint.Target.Setup -> LockScreenFlowNode.NavTarget.Setup
|
||||||
LockScreenEntryPoint.Target.Settings -> LockScreenFlowNode.NavTarget.Settings
|
LockScreenEntryPoint.Target.Settings -> LockScreenFlowNode.NavTarget.Settings
|
||||||
}
|
}
|
||||||
|
|
@ -54,4 +50,8 @@ class DefaultLockScreenEntryPoint @Inject constructor() : LockScreenEntryPoint {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun pinUnlockIntent(context: Context): Intent {
|
||||||
|
return PinUnlockActivity.newIntent(context)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,6 @@ import io.element.android.anvilannotations.ContributesNode
|
||||||
import io.element.android.features.lockscreen.api.LockScreenEntryPoint
|
import io.element.android.features.lockscreen.api.LockScreenEntryPoint
|
||||||
import io.element.android.features.lockscreen.impl.settings.LockScreenSettingsFlowNode
|
import io.element.android.features.lockscreen.impl.settings.LockScreenSettingsFlowNode
|
||||||
import io.element.android.features.lockscreen.impl.setup.LockScreenSetupFlowNode
|
import io.element.android.features.lockscreen.impl.setup.LockScreenSetupFlowNode
|
||||||
import io.element.android.features.lockscreen.impl.unlock.PinUnlockNode
|
|
||||||
import io.element.android.libraries.architecture.BackstackView
|
import io.element.android.libraries.architecture.BackstackView
|
||||||
import io.element.android.libraries.architecture.BaseFlowNode
|
import io.element.android.libraries.architecture.BaseFlowNode
|
||||||
import io.element.android.libraries.architecture.NodeInputs
|
import io.element.android.libraries.architecture.NodeInputs
|
||||||
|
|
@ -44,20 +43,17 @@ class LockScreenFlowNode @AssistedInject constructor(
|
||||||
@Assisted plugins: List<Plugin>,
|
@Assisted plugins: List<Plugin>,
|
||||||
) : BaseFlowNode<LockScreenFlowNode.NavTarget>(
|
) : BaseFlowNode<LockScreenFlowNode.NavTarget>(
|
||||||
backstack = BackStack(
|
backstack = BackStack(
|
||||||
initialElement = plugins.filterIsInstance(Inputs::class.java).first().initialNavTarget,
|
initialElement = plugins.filterIsInstance<Inputs>().first().initialNavTarget,
|
||||||
savedStateMap = buildContext.savedStateMap,
|
savedStateMap = buildContext.savedStateMap,
|
||||||
),
|
),
|
||||||
buildContext = buildContext,
|
buildContext = buildContext,
|
||||||
plugins = plugins,
|
plugins = plugins,
|
||||||
) {
|
) {
|
||||||
data class Inputs(
|
data class Inputs(
|
||||||
val initialNavTarget: NavTarget = NavTarget.Unlock,
|
val initialNavTarget: NavTarget,
|
||||||
) : NodeInputs
|
) : NodeInputs
|
||||||
|
|
||||||
sealed interface NavTarget : Parcelable {
|
sealed interface NavTarget : Parcelable {
|
||||||
@Parcelize
|
|
||||||
data object Unlock : NavTarget
|
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data object Setup : NavTarget
|
data object Setup : NavTarget
|
||||||
|
|
||||||
|
|
@ -75,10 +71,6 @@ class LockScreenFlowNode @AssistedInject constructor(
|
||||||
|
|
||||||
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
||||||
return when (navTarget) {
|
return when (navTarget) {
|
||||||
NavTarget.Unlock -> {
|
|
||||||
val inputs = PinUnlockNode.Inputs(isInAppUnlock = false)
|
|
||||||
createNode<PinUnlockNode>(buildContext, plugins = listOf(inputs))
|
|
||||||
}
|
|
||||||
NavTarget.Setup -> {
|
NavTarget.Setup -> {
|
||||||
val callback = OnSetupDoneCallback(plugins())
|
val callback = OnSetupDoneCallback(plugins())
|
||||||
createNode<LockScreenSetupFlowNode>(buildContext, plugins = listOf(callback))
|
createNode<LockScreenSetupFlowNode>(buildContext, plugins = listOf(callback))
|
||||||
|
|
|
||||||
|
|
@ -103,13 +103,12 @@ class LockScreenSettingsFlowNode @AssistedInject constructor(
|
||||||
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
||||||
return when (navTarget) {
|
return when (navTarget) {
|
||||||
NavTarget.Unlock -> {
|
NavTarget.Unlock -> {
|
||||||
val inputs = PinUnlockNode.Inputs(isInAppUnlock = true)
|
|
||||||
val callback = object : PinUnlockNode.Callback {
|
val callback = object : PinUnlockNode.Callback {
|
||||||
override fun onUnlock() {
|
override fun onUnlock() {
|
||||||
backstack.newRoot(NavTarget.Settings)
|
backstack.newRoot(NavTarget.Settings)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
createNode<PinUnlockNode>(buildContext, plugins = listOf(inputs, callback))
|
createNode<PinUnlockNode>(buildContext, plugins = listOf(callback))
|
||||||
}
|
}
|
||||||
NavTarget.SetupPin -> {
|
NavTarget.SetupPin -> {
|
||||||
createNode<SetupPinNode>(buildContext)
|
createNode<SetupPinNode>(buildContext)
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,6 @@ import com.bumble.appyx.core.plugin.plugins
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import io.element.android.anvilannotations.ContributesNode
|
import io.element.android.anvilannotations.ContributesNode
|
||||||
import io.element.android.libraries.architecture.NodeInputs
|
|
||||||
import io.element.android.libraries.architecture.inputs
|
|
||||||
import io.element.android.libraries.di.SessionScope
|
import io.element.android.libraries.di.SessionScope
|
||||||
|
|
||||||
@ContributesNode(SessionScope::class)
|
@ContributesNode(SessionScope::class)
|
||||||
|
|
@ -40,12 +38,6 @@ class PinUnlockNode @AssistedInject constructor(
|
||||||
fun onUnlock()
|
fun onUnlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Inputs(
|
|
||||||
val isInAppUnlock: Boolean
|
|
||||||
) : NodeInputs
|
|
||||||
|
|
||||||
private val inputs: Inputs = inputs()
|
|
||||||
|
|
||||||
private fun onUnlock() {
|
private fun onUnlock() {
|
||||||
plugins<Callback>().forEach {
|
plugins<Callback>().forEach {
|
||||||
it.onUnlock()
|
it.onUnlock()
|
||||||
|
|
@ -62,7 +54,9 @@ class PinUnlockNode @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
PinUnlockView(
|
PinUnlockView(
|
||||||
state = state,
|
state = state,
|
||||||
isInAppUnlock = inputs.isInAppUnlock,
|
// UnlockNode is only used for in-app unlock, so we can safely set isInAppUnlock to true.
|
||||||
|
// It's set to false in PinUnlockActivity.
|
||||||
|
isInAppUnlock = true,
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,11 +29,11 @@ import io.element.android.features.lockscreen.impl.biometric.BiometricUnlockMana
|
||||||
import io.element.android.features.lockscreen.impl.pin.PinCodeManager
|
import io.element.android.features.lockscreen.impl.pin.PinCodeManager
|
||||||
import io.element.android.features.lockscreen.impl.pin.model.PinEntry
|
import io.element.android.features.lockscreen.impl.pin.model.PinEntry
|
||||||
import io.element.android.features.lockscreen.impl.unlock.keypad.PinKeypadModel
|
import io.element.android.features.lockscreen.impl.unlock.keypad.PinKeypadModel
|
||||||
|
import io.element.android.features.lockscreen.impl.unlock.signout.SignOut
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
||||||
import io.element.android.libraries.core.bool.orFalse
|
import io.element.android.libraries.core.bool.orFalse
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
@ -41,7 +41,7 @@ import javax.inject.Inject
|
||||||
class PinUnlockPresenter @Inject constructor(
|
class PinUnlockPresenter @Inject constructor(
|
||||||
private val pinCodeManager: PinCodeManager,
|
private val pinCodeManager: PinCodeManager,
|
||||||
private val biometricUnlockManager: BiometricUnlockManager,
|
private val biometricUnlockManager: BiometricUnlockManager,
|
||||||
private val matrixClient: MatrixClient,
|
private val signOut: SignOut,
|
||||||
private val coroutineScope: CoroutineScope,
|
private val coroutineScope: CoroutineScope,
|
||||||
private val pinUnlockHelper: PinUnlockHelper,
|
private val pinUnlockHelper: PinUnlockHelper,
|
||||||
) : Presenter<PinUnlockState> {
|
) : Presenter<PinUnlockState> {
|
||||||
|
|
@ -179,7 +179,7 @@ class PinUnlockPresenter @Inject constructor(
|
||||||
|
|
||||||
private fun CoroutineScope.signOut(signOutAction: MutableState<AsyncData<String?>>) = launch {
|
private fun CoroutineScope.signOut(signOutAction: MutableState<AsyncData<String?>>) = launch {
|
||||||
suspend {
|
suspend {
|
||||||
matrixClient.logout(ignoreSdkError = true)
|
signOut()
|
||||||
}.runCatchingUpdatingState(signOutAction)
|
}.runCatchingUpdatingState(signOutAction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.lockscreen.impl.unlock.activity
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.activity.OnBackPressedCallback
|
||||||
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import io.element.android.compound.theme.ElementTheme
|
||||||
|
import io.element.android.features.lockscreen.api.LockScreenLockState
|
||||||
|
import io.element.android.features.lockscreen.api.LockScreenService
|
||||||
|
import io.element.android.features.lockscreen.impl.unlock.PinUnlockPresenter
|
||||||
|
import io.element.android.features.lockscreen.impl.unlock.PinUnlockView
|
||||||
|
import io.element.android.features.lockscreen.impl.unlock.di.PinUnlockBindings
|
||||||
|
import io.element.android.libraries.architecture.bindings
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class PinUnlockActivity : AppCompatActivity() {
|
||||||
|
internal companion object {
|
||||||
|
fun newIntent(context: Context): Intent {
|
||||||
|
return Intent(context, PinUnlockActivity::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject lateinit var presenter: PinUnlockPresenter
|
||||||
|
@Inject lateinit var lockScreenService: LockScreenService
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
enableEdgeToEdge()
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
bindings<PinUnlockBindings>().inject(this)
|
||||||
|
setContent {
|
||||||
|
ElementTheme {
|
||||||
|
val state = presenter.present()
|
||||||
|
PinUnlockView(state = state, isInAppUnlock = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lifecycleScope.launch {
|
||||||
|
lockScreenService.lockState.collect { state ->
|
||||||
|
if (state == LockScreenLockState.Unlocked) {
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val onBackPressedCallback = object : OnBackPressedCallback(true) {
|
||||||
|
override fun handleOnBackPressed() {
|
||||||
|
moveTaskToBack(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.lockscreen.impl.unlock.di
|
||||||
|
|
||||||
|
import com.squareup.anvil.annotations.ContributesTo
|
||||||
|
import io.element.android.features.lockscreen.impl.unlock.activity.PinUnlockActivity
|
||||||
|
import io.element.android.libraries.di.AppScope
|
||||||
|
|
||||||
|
@ContributesTo(AppScope::class)
|
||||||
|
interface PinUnlockBindings {
|
||||||
|
fun inject(activity: PinUnlockActivity)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.lockscreen.impl.unlock.signout
|
||||||
|
|
||||||
|
import com.squareup.anvil.annotations.ContributesBinding
|
||||||
|
import io.element.android.libraries.di.AppScope
|
||||||
|
import io.element.android.libraries.matrix.api.MatrixClientProvider
|
||||||
|
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ContributesBinding(AppScope::class)
|
||||||
|
class DefaultSignOut @Inject constructor(
|
||||||
|
private val authenticationService: MatrixAuthenticationService,
|
||||||
|
private val matrixClientProvider: MatrixClientProvider,
|
||||||
|
) : SignOut {
|
||||||
|
override suspend fun invoke(): String? {
|
||||||
|
val currentSession = authenticationService.getLatestSessionId()
|
||||||
|
return if (currentSession != null) {
|
||||||
|
matrixClientProvider.getOrRestore(currentSession)
|
||||||
|
.getOrThrow()
|
||||||
|
.logout(ignoreSdkError = true)
|
||||||
|
} else {
|
||||||
|
error("No session to sign out")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.lockscreen.impl.unlock.signout
|
||||||
|
|
||||||
|
interface SignOut {
|
||||||
|
suspend operator fun invoke(): String?
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<?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_app_lock_biometric_authentication">"authentification biométrique"</string>
|
<string name="screen_app_lock_biometric_authentication">"l’authentification biométrique"</string>
|
||||||
<string name="screen_app_lock_biometric_unlock">"déverrouillage biométrique"</string>
|
<string name="screen_app_lock_biometric_unlock">"déverrouillage biométrique"</string>
|
||||||
<string name="screen_app_lock_biometric_unlock_title_android">"Déverrouiller avec la biométrie"</string>
|
<string name="screen_app_lock_biometric_unlock_title_android">"Déverrouiller avec la biométrie"</string>
|
||||||
<string name="screen_app_lock_forgot_pin">"Code PIN oublié?"</string>
|
<string name="screen_app_lock_forgot_pin">"Code PIN oublié?"</string>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_app_lock_forgot_pin">"დაგავიწყდათ PIN?"</string>
|
||||||
|
<string name="screen_app_lock_settings_change_pin">"PIN კოდის შეცვლა"</string>
|
||||||
|
<string name="screen_app_lock_settings_enable_biometric_unlock">"ბიომეტრიული განბლოკვის დაშვება"</string>
|
||||||
|
<string name="screen_app_lock_settings_remove_pin">"პინ კოდის წაშლა"</string>
|
||||||
|
<string name="screen_app_lock_settings_remove_pin_alert_message">"დარწმუნებული ხართ, რომ გსურთ PIN-ის წაშლა?"</string>
|
||||||
|
<string name="screen_app_lock_settings_remove_pin_alert_title">"გსურთ PIN-ის წაშლა?"</string>
|
||||||
|
<string name="screen_app_lock_setup_confirm_pin">"დაადასტურეთ PIN"</string>
|
||||||
|
<string name="screen_app_lock_signout_alert_message">"გასაგრძელებლად საჭიროა ხელახლა შესვლა და ახალი PIN-ის შექმნა"</string>
|
||||||
|
<string name="screen_app_lock_signout_alert_title">"თქვენ ახლა გადიხართ…"</string>
|
||||||
|
<plurals name="screen_app_lock_subtitle">
|
||||||
|
<item quantity="one">"თქვენ გაქვთ %1$d მცდელობა განსაბლოკად"</item>
|
||||||
|
<item quantity="other">"თქვენ გაქვთ %1$d მცდელობა განსაბლოკად"</item>
|
||||||
|
</plurals>
|
||||||
|
<plurals name="screen_app_lock_subtitle_wrong_pin">
|
||||||
|
<item quantity="one">"არასწორი PIN. თქვენ %1$d შანსი დაგრჩათ"</item>
|
||||||
|
<item quantity="other">"არასწორი PIN. თქვენ %1$d შანსი დაგრჩათ"</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="screen_signout_in_progress_dialog_content">"გასვლა…"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_app_lock_biometric_authentication">"autenticação biométrica"</string>
|
||||||
|
<string name="screen_app_lock_biometric_unlock">"desbloqueio biométrico"</string>
|
||||||
|
<string name="screen_app_lock_biometric_unlock_title_android">"Desbloquear com biometria"</string>
|
||||||
|
<string name="screen_app_lock_forgot_pin">"Esqueceste-te do PIN?"</string>
|
||||||
|
<string name="screen_app_lock_settings_change_pin">"Altera o código PIN"</string>
|
||||||
|
<string name="screen_app_lock_settings_enable_biometric_unlock">"Permitir o desbloqueio biométrico"</string>
|
||||||
|
<string name="screen_app_lock_settings_remove_pin">"Remover PIN"</string>
|
||||||
|
<string name="screen_app_lock_settings_remove_pin_alert_message">"Tens a certeza que queres remover o PIN?"</string>
|
||||||
|
<string name="screen_app_lock_settings_remove_pin_alert_title">"Remover o PIN?"</string>
|
||||||
|
<string name="screen_app_lock_setup_biometric_unlock_allow_title">"Permitir %1$s"</string>
|
||||||
|
<string name="screen_app_lock_setup_biometric_unlock_skip">"Prefiro usar o PIN"</string>
|
||||||
|
<string name="screen_app_lock_setup_biometric_unlock_subtitle">"Poupa tempo e utiliza %1$s para desbloquear a aplicação"</string>
|
||||||
|
<string name="screen_app_lock_setup_choose_pin">"Escolher PIN"</string>
|
||||||
|
<string name="screen_app_lock_setup_confirm_pin">"Confirmar PIN"</string>
|
||||||
|
<string name="screen_app_lock_setup_pin_blacklisted_dialog_content">"Não podes escolher este código PIN por razões de segurança"</string>
|
||||||
|
<string name="screen_app_lock_setup_pin_blacklisted_dialog_title">"Escolhe um PIN diferente"</string>
|
||||||
|
<string name="screen_app_lock_setup_pin_context">"Bloqueia a %1$s para dar mais segurança às tuas conversas.
|
||||||
|
|
||||||
|
Escolhe algo memorável. Se te esqueceres deste PIN, a tua sessão será terminada."</string>
|
||||||
|
<string name="screen_app_lock_setup_pin_mismatch_dialog_content">"Insere o mesmo PIN duas vezes"</string>
|
||||||
|
<string name="screen_app_lock_setup_pin_mismatch_dialog_title">"Os PINs não coincidem"</string>
|
||||||
|
<string name="screen_app_lock_signout_alert_message">"Terás de voltar a iniciar sessão e criar um novo PIN para continuar"</string>
|
||||||
|
<string name="screen_app_lock_signout_alert_title">"Estás a terminar a sessão"</string>
|
||||||
|
<plurals name="screen_app_lock_subtitle">
|
||||||
|
<item quantity="one">"Tens %1$d tentativa de desbloqueio"</item>
|
||||||
|
<item quantity="other">"Tens %1$d tentativas de desbloqueio"</item>
|
||||||
|
</plurals>
|
||||||
|
<plurals name="screen_app_lock_subtitle_wrong_pin">
|
||||||
|
<item quantity="one">"PIN incorreto. Tens mais %1$d tentativa"</item>
|
||||||
|
<item quantity="other">"PIN incorreto. Tens mais %1$d tentativas"</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="screen_app_lock_use_biometric_android">"Utilizar biometria"</string>
|
||||||
|
<string name="screen_app_lock_use_pin_android">"Utilizar PIN"</string>
|
||||||
|
<string name="screen_signout_in_progress_dialog_content">"A terminar sessão…"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_app_lock_biometric_authentication">"生物识别认证"</string>
|
||||||
|
<string name="screen_app_lock_biometric_unlock">"生物识别解锁"</string>
|
||||||
|
<string name="screen_app_lock_biometric_unlock_title_android">"使用生物识别解锁"</string>
|
||||||
|
<string name="screen_app_lock_forgot_pin">"忘记 PIN 码?"</string>
|
||||||
|
<string name="screen_app_lock_settings_change_pin">"更改 PIN 码"</string>
|
||||||
|
<string name="screen_app_lock_settings_enable_biometric_unlock">"允许生物识别解锁"</string>
|
||||||
|
<string name="screen_app_lock_settings_remove_pin">"移除 PIN 码"</string>
|
||||||
|
<string name="screen_app_lock_settings_remove_pin_alert_message">"您确定要删除 PIN 码吗?"</string>
|
||||||
|
<string name="screen_app_lock_settings_remove_pin_alert_title">"移除 PIN 码?"</string>
|
||||||
|
<string name="screen_app_lock_setup_biometric_unlock_allow_title">"允许 %1$s"</string>
|
||||||
|
<string name="screen_app_lock_setup_biometric_unlock_skip">"我宁愿使用 PIN 码"</string>
|
||||||
|
<string name="screen_app_lock_setup_biometric_unlock_subtitle">"节省时间,用 %1$s 来解锁应用程序"</string>
|
||||||
|
<string name="screen_app_lock_setup_choose_pin">"选择 PIN 码"</string>
|
||||||
|
<string name="screen_app_lock_setup_confirm_pin">"确认 PIN 码"</string>
|
||||||
|
<string name="screen_app_lock_setup_pin_blacklisted_dialog_content">"出于安全原因,您不能选择这个 PIN 码"</string>
|
||||||
|
<string name="screen_app_lock_setup_pin_blacklisted_dialog_title">"选择不同的 PIN 码"</string>
|
||||||
|
<string name="screen_app_lock_setup_pin_context">"锁定 %1$s 以为聊天增加安全性。
|
||||||
|
|
||||||
|
选择好记的 PIN 码。如果忘掉了这个 PIN 码,就不得不登出应用。"</string>
|
||||||
|
<string name="screen_app_lock_setup_pin_mismatch_dialog_content">"请输入两次相同的 PIN 码"</string>
|
||||||
|
<string name="screen_app_lock_setup_pin_mismatch_dialog_title">"PIN 码不匹配"</string>
|
||||||
|
<string name="screen_app_lock_signout_alert_message">"您需要重新登录并创建新的 PIN 才能继续"</string>
|
||||||
|
<string name="screen_app_lock_signout_alert_title">"您正在登出"</string>
|
||||||
|
<plurals name="screen_app_lock_subtitle">
|
||||||
|
<item quantity="other">"还剩 %1$d 次解锁机会"</item>
|
||||||
|
</plurals>
|
||||||
|
<plurals name="screen_app_lock_subtitle_wrong_pin">
|
||||||
|
<item quantity="other">"PIN 码错误。还剩 %1$d 次机会"</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="screen_app_lock_use_biometric_android">"使用生物识别"</string>
|
||||||
|
<string name="screen_app_lock_use_pin_android">"使用 PIN 码"</string>
|
||||||
|
<string name="screen_signout_in_progress_dialog_content">"正在登出…"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.lockscreen.impl.unlock
|
||||||
|
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import io.element.android.features.lockscreen.impl.unlock.signout.DefaultSignOut
|
||||||
|
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||||
|
import io.element.android.libraries.matrix.test.FakeMatrixClientProvider
|
||||||
|
import io.element.android.libraries.matrix.test.auth.FakeAuthenticationService
|
||||||
|
import io.element.android.tests.testutils.lambda.assert
|
||||||
|
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class DefaultSignOutTest {
|
||||||
|
private val matrixClient = FakeMatrixClient()
|
||||||
|
private val authenticationService = FakeAuthenticationService()
|
||||||
|
private val matrixClientProvider = FakeMatrixClientProvider(getClient = { Result.success(matrixClient) })
|
||||||
|
private val sut = DefaultSignOut(authenticationService, matrixClientProvider)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when no active session then it throws`() = runTest {
|
||||||
|
authenticationService.getLatestSessionIdLambda = { null }
|
||||||
|
val result = runCatching { sut.invoke() }
|
||||||
|
assertThat(result.isFailure).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `with one active session and successful logout on client`() = runTest {
|
||||||
|
val logoutLambda = lambdaRecorder<Boolean, String?> { _: Boolean -> null }
|
||||||
|
authenticationService.getLatestSessionIdLambda = { matrixClient.sessionId }
|
||||||
|
matrixClient.logoutLambda = logoutLambda
|
||||||
|
val result = runCatching { sut.invoke() }
|
||||||
|
assertThat(result.isSuccess).isTrue()
|
||||||
|
assert(logoutLambda).isCalledOnce()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `with one active session and and failed logout on client`() = runTest {
|
||||||
|
val logoutLambda = lambdaRecorder<Boolean, String?> { _: Boolean -> error("Failed to logout") }
|
||||||
|
authenticationService.getLatestSessionIdLambda = { matrixClient.sessionId }
|
||||||
|
matrixClient.logoutLambda = logoutLambda
|
||||||
|
val result = runCatching { sut.invoke() }
|
||||||
|
assertThat(result.isFailure).isTrue()
|
||||||
|
assert(logoutLambda).isCalledOnce()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.lockscreen.impl.unlock
|
||||||
|
|
||||||
|
import io.element.android.features.lockscreen.impl.unlock.signout.SignOut
|
||||||
|
import io.element.android.tests.testutils.simulateLongTask
|
||||||
|
|
||||||
|
class FakeSignOut(
|
||||||
|
var lambda: () -> String? = { null }
|
||||||
|
) : SignOut {
|
||||||
|
override suspend fun invoke(): String? = simulateLongTask {
|
||||||
|
lambda()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -28,8 +28,10 @@ import io.element.android.features.lockscreen.impl.pin.PinCodeManager
|
||||||
import io.element.android.features.lockscreen.impl.pin.model.PinEntry
|
import io.element.android.features.lockscreen.impl.pin.model.PinEntry
|
||||||
import io.element.android.features.lockscreen.impl.pin.model.assertText
|
import io.element.android.features.lockscreen.impl.pin.model.assertText
|
||||||
import io.element.android.features.lockscreen.impl.unlock.keypad.PinKeypadModel
|
import io.element.android.features.lockscreen.impl.unlock.keypad.PinKeypadModel
|
||||||
|
import io.element.android.features.lockscreen.impl.unlock.signout.SignOut
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
import io.element.android.tests.testutils.lambda.assert
|
||||||
|
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
@ -104,7 +106,9 @@ class PinUnlockPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - forgot pin flow`() = runTest {
|
fun `present - forgot pin flow`() = runTest {
|
||||||
val presenter = createPinUnlockPresenter(this)
|
val signOutLambda = lambdaRecorder<String?> { null }
|
||||||
|
val signOut = FakeSignOut(signOutLambda)
|
||||||
|
val presenter = createPinUnlockPresenter(this, signOut = signOut)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
|
|
@ -131,6 +135,7 @@ class PinUnlockPresenterTest {
|
||||||
awaitItem().also { state ->
|
awaitItem().also { state ->
|
||||||
assertThat(state.signOutAction).isInstanceOf(AsyncData.Success::class.java)
|
assertThat(state.signOutAction).isInstanceOf(AsyncData.Success::class.java)
|
||||||
}
|
}
|
||||||
|
assert(signOutLambda).isCalledOnce().withNoParameter()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -142,6 +147,7 @@ class PinUnlockPresenterTest {
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
biometricUnlockManager: BiometricUnlockManager = FakeBiometricUnlockManager(),
|
biometricUnlockManager: BiometricUnlockManager = FakeBiometricUnlockManager(),
|
||||||
callback: PinCodeManager.Callback = DefaultPinCodeManagerCallback(),
|
callback: PinCodeManager.Callback = DefaultPinCodeManagerCallback(),
|
||||||
|
signOut: SignOut = FakeSignOut(),
|
||||||
): PinUnlockPresenter {
|
): PinUnlockPresenter {
|
||||||
val pinCodeManager = aPinCodeManager().apply {
|
val pinCodeManager = aPinCodeManager().apply {
|
||||||
addCallback(callback)
|
addCallback(callback)
|
||||||
|
|
@ -150,7 +156,7 @@ class PinUnlockPresenterTest {
|
||||||
return PinUnlockPresenter(
|
return PinUnlockPresenter(
|
||||||
pinCodeManager = pinCodeManager,
|
pinCodeManager = pinCodeManager,
|
||||||
biometricUnlockManager = biometricUnlockManager,
|
biometricUnlockManager = biometricUnlockManager,
|
||||||
matrixClient = FakeMatrixClient(),
|
signOut = signOut,
|
||||||
coroutineScope = scope,
|
coroutineScope = scope,
|
||||||
pinUnlockHelper = PinUnlockHelper(biometricUnlockManager, pinCodeManager),
|
pinUnlockHelper = PinUnlockHelper(biometricUnlockManager, pinCodeManager),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@
|
||||||
<string name="screen_change_account_provider_subtitle">"Usa un proveedor de cuenta diferente, como tu propio servidor privado o una cuenta de trabajo."</string>
|
<string name="screen_change_account_provider_subtitle">"Usa un proveedor de cuenta diferente, como tu propio servidor privado o una cuenta de trabajo."</string>
|
||||||
<string name="screen_change_account_provider_title">"Cambiar el proveedor de la cuenta"</string>
|
<string name="screen_change_account_provider_title">"Cambiar el proveedor de la cuenta"</string>
|
||||||
<string name="screen_change_server_error_invalid_homeserver">"No hemos podido acceder a este servidor. Comprueba que has introducido correctamente la dirección del servidor. Si la dirección es correcta, ponte en contacto con el administrador del servidor para obtener más ayuda."</string>
|
<string name="screen_change_server_error_invalid_homeserver">"No hemos podido acceder a este servidor. Comprueba que has introducido correctamente la dirección del servidor. Si la dirección es correcta, ponte en contacto con el administrador del servidor para obtener más ayuda."</string>
|
||||||
|
<string name="screen_change_server_error_invalid_well_known">"Sliding sync no está disponible debido a un problema en el archivo well-known:
|
||||||
|
%1$s"</string>
|
||||||
<string name="screen_change_server_error_no_sliding_sync_message">"Este servidor no soporta sliding sync."</string>
|
<string name="screen_change_server_error_no_sliding_sync_message">"Este servidor no soporta sliding sync."</string>
|
||||||
<string name="screen_change_server_form_header">"Dirección del homeserver"</string>
|
<string name="screen_change_server_form_header">"Dirección del homeserver"</string>
|
||||||
<string name="screen_change_server_form_notice">"Solo puedes conectarte a un servidor que soporte sliding sync. El administrador de tu servidor tendrá que configurarlo. %1$s"</string>
|
<string name="screen_change_server_form_notice">"Solo puedes conectarte a un servidor que soporte sliding sync. El administrador de tu servidor tendrá que configurarlo. %1$s"</string>
|
||||||
|
|
@ -22,6 +24,7 @@
|
||||||
<string name="screen_login_error_deactivated_account">"Esta cuenta ha sido desactivada."</string>
|
<string name="screen_login_error_deactivated_account">"Esta cuenta ha sido desactivada."</string>
|
||||||
<string name="screen_login_error_invalid_credentials">"Usuario y/o contraseña incorrectos"</string>
|
<string name="screen_login_error_invalid_credentials">"Usuario y/o contraseña incorrectos"</string>
|
||||||
<string name="screen_login_error_invalid_user_id">"Este no es un id de usuario válido. Formato esperado: \'@user:homeserver.org\'"</string>
|
<string name="screen_login_error_invalid_user_id">"Este no es un id de usuario válido. Formato esperado: \'@user:homeserver.org\'"</string>
|
||||||
|
<string name="screen_login_error_refresh_tokens">"Este servidor está configurado para utilizar tokens de actualización. Estos no son compatibles cuando se utiliza el inicio de sesión basado en contraseña."</string>
|
||||||
<string name="screen_login_error_unsupported_authentication">"El servidor seleccionado no admite contraseñas ni inicio de sesión OIDC. Póngase en contacto con su administrador o elija otro homeserver."</string>
|
<string name="screen_login_error_unsupported_authentication">"El servidor seleccionado no admite contraseñas ni inicio de sesión OIDC. Póngase en contacto con su administrador o elija otro homeserver."</string>
|
||||||
<string name="screen_login_form_header">"Introduce tus datos"</string>
|
<string name="screen_login_form_header">"Introduce tus datos"</string>
|
||||||
<string name="screen_login_subtitle">"Matrix es una red abierta para una comunicación segura y descentralizada."</string>
|
<string name="screen_login_subtitle">"Matrix es una red abierta para una comunicación segura y descentralizada."</string>
|
||||||
|
|
|
||||||
42
features/login/impl/src/main/res/values-ka/translations.xml
Normal file
42
features/login/impl/src/main/res/values-ka/translations.xml
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_account_provider_change">"ანგარიშის მიმწოდებლის შეცვლა"</string>
|
||||||
|
<string name="screen_account_provider_form_hint">"სახლის სერვერის მისამართი"</string>
|
||||||
|
<string name="screen_account_provider_form_notice">"შეიყვანეთ საძიებო სიტყვა ან დომენის მისამართი."</string>
|
||||||
|
<string name="screen_account_provider_form_subtitle">"მოძებნეთ კომპანია, საზოგადოება ან კერძო სერვერი."</string>
|
||||||
|
<string name="screen_account_provider_form_title">"ანგარიშის მომწოდებლის მოძებნა"</string>
|
||||||
|
<string name="screen_account_provider_signin_subtitle">"აქ იქნება თქვენი საუბრები - ისევე, როგორც თქვენ ელ. ფოსტაში ინახება თქვენი ელ.წერილები."</string>
|
||||||
|
<string name="screen_account_provider_signin_title">"თქვენ აპირებთ შესვლას %s-ში"</string>
|
||||||
|
<string name="screen_account_provider_signup_subtitle">"აქ იქნება თქვენი საუბრები - ისევე, როგორც თქვენ ელ. ფოსტაში ინახება თქვენი ელ.წერილები."</string>
|
||||||
|
<string name="screen_account_provider_signup_title">"თქვენ აპირებთ ანგარიშის შექმნას %s-ში"</string>
|
||||||
|
<string name="screen_change_account_provider_matrix_org_subtitle">"Matrix.org არის დიდი, უფასო სერვერი საჯარო Matrix ქსელში უსაფრთხო, დეცენტრალიზებული კომუნიკაციისთვის, რომელსაც მართავს Matrix.org ფონდი."</string>
|
||||||
|
<string name="screen_change_account_provider_other">"სხვა"</string>
|
||||||
|
<string name="screen_change_account_provider_subtitle">"გამოიყენეთ სხვა ანგარიშის პროვაიდერი, როგორიცაა თქვენი პირადი სერვერი ან სამუშაო ანგარიში."</string>
|
||||||
|
<string name="screen_change_account_provider_title">"შეცვალეთ ანგარიშის მომწოდებელი"</string>
|
||||||
|
<string name="screen_change_server_error_invalid_homeserver">"ჩვენ ვერ მივაღწიეთ ამ სახლის სერვერს. გთხოვთ, შეამოწმოთ, რომ სწორად შეიყვანეთ სახლის სერვერის URL. თუ URL სწორია, დაუკავშირდით თქვენი სახლის სერვერის ადმინისტრატორს დამატებითი დახმარებისთვის."</string>
|
||||||
|
<string name="screen_change_server_error_no_sliding_sync_message">"ამჟამად ეს სერვერი მხარს არ უჭერს \"sliding sync\"-ს."</string>
|
||||||
|
<string name="screen_change_server_form_header">"სახლის სერვერის URL"</string>
|
||||||
|
<string name="screen_change_server_form_notice">"თქვენ შეგიძლიათ დაუკავშირდეთ მხოლოდ იმ სერვერს, რომელიც მხარს უჭერს \"sliding sync\"-ს. თქვენი სახლის სერვერის ადმინისტრატორს დასჭირდება მისი კონფიგურაცია.%1$s"</string>
|
||||||
|
<string name="screen_change_server_subtitle">"რა არის თქვენი სერვერის მისამართი?"</string>
|
||||||
|
<string name="screen_change_server_title">"აირჩიეთ თქვენი სერვერი"</string>
|
||||||
|
<string name="screen_login_error_deactivated_account">"ეს ანგარიში დეაქტივირებულია."</string>
|
||||||
|
<string name="screen_login_error_invalid_credentials">"არასწორი მომხმარებლის სახელი და/ან პაროლი"</string>
|
||||||
|
<string name="screen_login_error_invalid_user_id">"მოცემული მომხმარებლის იდენტიფიკატორი არასწორია. დასაშვები ფორმატი: ‘@user:homeserver.org’"</string>
|
||||||
|
<string name="screen_login_error_unsupported_authentication">"მოცემული სახლის სერვერი მხარს არ უჭერს პაროლით ან OIDC-ით შესვლას. გთხოვთ, დაუკავშირდეთ თქვენს ადმინისტრატორს ან აარჩიეთ სხვა სახლის სერვერი."</string>
|
||||||
|
<string name="screen_login_form_header">"შეიყვანეთ თქვენი დეტალები"</string>
|
||||||
|
<string name="screen_login_subtitle">"Matrix არის ღია ქსელი უსაფრთხო, დეცენტრალიზებული კომუნიკაციისთვის."</string>
|
||||||
|
<string name="screen_login_title">"კეთილი იყოს თქვენი მობრძანება!"</string>
|
||||||
|
<string name="screen_login_title_with_homeserver">"შესვლა %1$s-ში"</string>
|
||||||
|
<string name="screen_server_confirmation_change_server">"შეცვალეთ ანგარიშის მომწოდებელი"</string>
|
||||||
|
<string name="screen_server_confirmation_message_login_element_dot_io">"კერძო სერვერი Element-ის თანამშრომლებისთვის."</string>
|
||||||
|
<string name="screen_server_confirmation_message_login_matrix_dot_org">"Matrix არის ღია ქსელი უსაფრთხო, დეცენტრალიზებული კომუნიკაციისთვის."</string>
|
||||||
|
<string name="screen_server_confirmation_message_register">"აქ იქნება თქვენი საუბრები - ისევე, როგორც თქვენ ელ. ფოსტაში ინახება თქვენი ელ.წერილები."</string>
|
||||||
|
<string name="screen_server_confirmation_title_login">"თქვენ აპირებთ შესვლას %1$s-ში"</string>
|
||||||
|
<string name="screen_server_confirmation_title_register">"თქვენ აპირებთ ანგარიშის შექმნას %1$s-ში"</string>
|
||||||
|
<string name="screen_waitlist_message">"ახლა დიდი მოთხოვნაა %1$s-ზე %2$s-ში. დაბრუნდით რამდენიმე დღეში და სცადეთ ერთხელაც.
|
||||||
|
|
||||||
|
მადლობა მოთმენისათვის!"</string>
|
||||||
|
<string name="screen_waitlist_message_success">"კეთილი იყოს თქვენი მობრძანება %1$s-ში!"</string>
|
||||||
|
<string name="screen_waitlist_title">"თითქმის მზადაა."</string>
|
||||||
|
<string name="screen_waitlist_title_success">"თქვენ შეხვედით."</string>
|
||||||
|
</resources>
|
||||||
45
features/login/impl/src/main/res/values-pt/translations.xml
Normal file
45
features/login/impl/src/main/res/values-pt/translations.xml
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_account_provider_change">"Alterar operador de conta"</string>
|
||||||
|
<string name="screen_account_provider_form_hint">"Endereço do servidor"</string>
|
||||||
|
<string name="screen_account_provider_form_notice">"Insira um termo para pesquisa ou um endereço."</string>
|
||||||
|
<string name="screen_account_provider_form_subtitle">"Pesquisar por uma empresa, comunidade ou servidor privado."</string>
|
||||||
|
<string name="screen_account_provider_form_title">"Encontrar um operador de conta"</string>
|
||||||
|
<string name="screen_account_provider_signin_subtitle">"É aqui que as tuas conversas vão ficar — tal como num serviço de e-mail."</string>
|
||||||
|
<string name="screen_account_provider_signin_title">"Irás iniciar sessão em %s"</string>
|
||||||
|
<string name="screen_account_provider_signup_subtitle">"É aqui que as tuas conversas vão ficar — tal como num serviço de e-mail."</string>
|
||||||
|
<string name="screen_account_provider_signup_title">"Irás criar uma conta em %s"</string>
|
||||||
|
<string name="screen_change_account_provider_matrix_org_subtitle">"O Matrix.org é um servidor grande e gratuito na rede pública Matrix para comunicação segura e descentralizada, gerido pela Fundação Matrix.org."</string>
|
||||||
|
<string name="screen_change_account_provider_other">"Outro"</string>
|
||||||
|
<string name="screen_change_account_provider_subtitle">"Utiliza um operador de conta diferente, como o teu próprio servidor privado ou uma conta de trabalho."</string>
|
||||||
|
<string name="screen_change_account_provider_title">"Alterar operador de conta"</string>
|
||||||
|
<string name="screen_change_server_error_invalid_homeserver">"Não foi possível comunicar com este servidor. Por favor, verifica se introduziste o seu URL corretamente. Se sim, contacta o administrador para obteres mais ajuda."</string>
|
||||||
|
<string name="screen_change_server_error_invalid_well_known">"A sincronização deslizante (sliding sync) não está disponível devido a um problema no ficheiro \"well-known\":
|
||||||
|
%1$s"</string>
|
||||||
|
<string name="screen_change_server_error_no_sliding_sync_message">"Este servidor não suporta sincronização deslizante (sliding sync)."</string>
|
||||||
|
<string name="screen_change_server_form_header">"URL do servidor"</string>
|
||||||
|
<string name="screen_change_server_form_notice">"Só te podes ligar a um servidor existente que suporte a sincronização deslizante (sliding sync). O administrador do teu servidor terá de a configurar. %1$s"</string>
|
||||||
|
<string name="screen_change_server_subtitle">"Qual é o endereço do teu servidor?"</string>
|
||||||
|
<string name="screen_change_server_title">"Seleciona o teu servidor"</string>
|
||||||
|
<string name="screen_login_error_deactivated_account">"Esta conta foi desativada."</string>
|
||||||
|
<string name="screen_login_error_invalid_credentials">"Nome de utilizador ou senha incorretos"</string>
|
||||||
|
<string name="screen_login_error_invalid_user_id">"Identificador de utilizador inválido. Formato esperado: ‘@utilizador:servidor.org’"</string>
|
||||||
|
<string name="screen_login_error_refresh_tokens">"Este servidor está configurado para utilizar \"tokens\" de atualização. Estes não são suportados quando utilizas o início de sessão por senha."</string>
|
||||||
|
<string name="screen_login_error_unsupported_authentication">"O servidor selecionado não suporta início de sessão por senha nem por OIDC. Por favor, contacta o teu administrador ou escolhe outro servidor."</string>
|
||||||
|
<string name="screen_login_form_header">"Insere o teus detalhes"</string>
|
||||||
|
<string name="screen_login_subtitle">"A Matrix é uma rede aberta de comunicação descentralizada e segura."</string>
|
||||||
|
<string name="screen_login_title">"Bem-vindo(a) de volta!"</string>
|
||||||
|
<string name="screen_login_title_with_homeserver">"Iniciar sessão em %1$s"</string>
|
||||||
|
<string name="screen_server_confirmation_change_server">"Alterar operador de conta"</string>
|
||||||
|
<string name="screen_server_confirmation_message_login_element_dot_io">"Um servidor privado para funcionários da Element."</string>
|
||||||
|
<string name="screen_server_confirmation_message_login_matrix_dot_org">"A Matrix é uma rede aberta de comunicação descentralizada e segura."</string>
|
||||||
|
<string name="screen_server_confirmation_message_register">"É aqui que as tuas conversas vão ficar — tal como num serviço de e-mail."</string>
|
||||||
|
<string name="screen_server_confirmation_title_login">"Irás iniciar sessão em %1$s"</string>
|
||||||
|
<string name="screen_server_confirmation_title_register">"Irás criar uma conta em %1$s"</string>
|
||||||
|
<string name="screen_waitlist_message">"Há uma grande procura pela %1$s no %2$s, de momento. Volta à aplicação daqui a uns dias e tenta novamente.
|
||||||
|
|
||||||
|
Obrigado!"</string>
|
||||||
|
<string name="screen_waitlist_message_success">"Bem-vindo à %1$s!"</string>
|
||||||
|
<string name="screen_waitlist_title">"Estás quase lá."</string>
|
||||||
|
<string name="screen_waitlist_title_success">"Está dentro"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -14,6 +14,8 @@
|
||||||
<string name="screen_change_account_provider_subtitle">"Utilizați un alt furnizor de cont, cum ar fi propriul server privat sau un cont de serviciu."</string>
|
<string name="screen_change_account_provider_subtitle">"Utilizați un alt furnizor de cont, cum ar fi propriul server privat sau un cont de serviciu."</string>
|
||||||
<string name="screen_change_account_provider_title">"Schimbați furnizorul contului"</string>
|
<string name="screen_change_account_provider_title">"Schimbați furnizorul contului"</string>
|
||||||
<string name="screen_change_server_error_invalid_homeserver">"Nu am putut accesa acest homeserver. Te rugăm să verifici că ai introdus corect adresa URL a homeserver-ului. Dacă adresa URL este corectă, contactează administratorul homeserver-ului pentru ajutor suplimentar."</string>
|
<string name="screen_change_server_error_invalid_homeserver">"Nu am putut accesa acest homeserver. Te rugăm să verifici că ai introdus corect adresa URL a homeserver-ului. Dacă adresa URL este corectă, contactează administratorul homeserver-ului pentru ajutor suplimentar."</string>
|
||||||
|
<string name="screen_change_server_error_invalid_well_known">"Sliding sync nu este disponibil din cauza unei probleme în fișierul well-known:
|
||||||
|
%1$s"</string>
|
||||||
<string name="screen_change_server_error_no_sliding_sync_message">"Momentan acest server nu oferă suport pentru sliding sync."</string>
|
<string name="screen_change_server_error_no_sliding_sync_message">"Momentan acest server nu oferă suport pentru sliding sync."</string>
|
||||||
<string name="screen_change_server_form_header">"Adresa URL a homeserver-ului"</string>
|
<string name="screen_change_server_form_header">"Adresa URL a homeserver-ului"</string>
|
||||||
<string name="screen_change_server_form_notice">"Vă putețo conecta numai la un server existent care oferă suport pentru sliding sync. Administratorul homeserver-ului dumneavoastră va trebui să îl configureze. %1$s"</string>
|
<string name="screen_change_server_form_notice">"Vă putețo conecta numai la un server existent care oferă suport pentru sliding sync. Administratorul homeserver-ului dumneavoastră va trebui să îl configureze. %1$s"</string>
|
||||||
|
|
@ -22,6 +24,7 @@
|
||||||
<string name="screen_login_error_deactivated_account">"Acest cont a fost dezactivat."</string>
|
<string name="screen_login_error_deactivated_account">"Acest cont a fost dezactivat."</string>
|
||||||
<string name="screen_login_error_invalid_credentials">"Utilizator și/sau parolă incorecte"</string>
|
<string name="screen_login_error_invalid_credentials">"Utilizator și/sau parolă incorecte"</string>
|
||||||
<string name="screen_login_error_invalid_user_id">"Acesta nu este un identificator de utilizator valid. Format așteptat: „@user:homeserver.org”"</string>
|
<string name="screen_login_error_invalid_user_id">"Acesta nu este un identificator de utilizator valid. Format așteptat: „@user:homeserver.org”"</string>
|
||||||
|
<string name="screen_login_error_refresh_tokens">"Acest server este configurat pentru a utiliza token-uri de reîmprospătare. Acestea nu sunt acceptate atunci când utilizați autentificare bazată pe parolă."</string>
|
||||||
<string name="screen_login_error_unsupported_authentication">"Homeserver-ul selectat nu acceptă autentificarea prin parola sau OIDC. Te rugăm să contactezi administratorul sau să alegi un alt homeserver."</string>
|
<string name="screen_login_error_unsupported_authentication">"Homeserver-ul selectat nu acceptă autentificarea prin parola sau OIDC. Te rugăm să contactezi administratorul sau să alegi un alt homeserver."</string>
|
||||||
<string name="screen_login_form_header">"Introduceți detaliile"</string>
|
<string name="screen_login_form_header">"Introduceți detaliile"</string>
|
||||||
<string name="screen_login_subtitle">"Matrix este o rețea deschisă pentru o comunicare sigură și descentralizată."</string>
|
<string name="screen_login_subtitle">"Matrix este o rețea deschisă pentru o comunicare sigură și descentralizată."</string>
|
||||||
|
|
|
||||||
45
features/login/impl/src/main/res/values-zh/translations.xml
Normal file
45
features/login/impl/src/main/res/values-zh/translations.xml
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_account_provider_change">"更改账户提供者"</string>
|
||||||
|
<string name="screen_account_provider_form_hint">"服务器地址"</string>
|
||||||
|
<string name="screen_account_provider_form_notice">"输入搜索词或域名地址。"</string>
|
||||||
|
<string name="screen_account_provider_form_subtitle">"搜索公司、社区或私人服务器。"</string>
|
||||||
|
<string name="screen_account_provider_form_title">"查找账户提供者"</string>
|
||||||
|
<string name="screen_account_provider_signin_subtitle">"这是您的对话将进行的地方,就像您使用电子邮件提供商来保存电子邮件一样。"</string>
|
||||||
|
<string name="screen_account_provider_signin_title">"您即将登录%s"</string>
|
||||||
|
<string name="screen_account_provider_signup_subtitle">"这是您的对话将进行的地方,就像您使用电子邮件提供商来保存电子邮件一样。"</string>
|
||||||
|
<string name="screen_account_provider_signup_title">"您即将在 %s 上创建一个帐户"</string>
|
||||||
|
<string name="screen_change_account_provider_matrix_org_subtitle">"Matrix.org 由 Matrix.org 基金会运营,是用于安全、去中心化的通信的公共 Matrix 网络上的大型免费服务器。"</string>
|
||||||
|
<string name="screen_change_account_provider_other">"其他"</string>
|
||||||
|
<string name="screen_change_account_provider_subtitle">"使用其他帐户提供者,例如您自己的私人服务器或工作帐户。"</string>
|
||||||
|
<string name="screen_change_account_provider_title">"更改账户提供者"</string>
|
||||||
|
<string name="screen_change_server_error_invalid_homeserver">"我们无法访问此主服务器。请检查您输入的主服务器网址是否正确。如果 URL 正确,请联系您的主服务器管理员寻求进一步帮助。"</string>
|
||||||
|
<string name="screen_change_server_error_invalid_well_known">"由于 Well Known 文件中的问题,Sliding Sync 不可用:
|
||||||
|
%1$s"</string>
|
||||||
|
<string name="screen_change_server_error_no_sliding_sync_message">"该服务器目前不支持sliding sync。"</string>
|
||||||
|
<string name="screen_change_server_form_header">"主服务器网址"</string>
|
||||||
|
<string name="screen_change_server_form_notice">"您只能连接到支持sliding sync的现有服务器。您的主服务器管理员需要对其进行配置。%1$s"</string>
|
||||||
|
<string name="screen_change_server_subtitle">"您的服务器地址是什么?"</string>
|
||||||
|
<string name="screen_change_server_title">"选择服务器"</string>
|
||||||
|
<string name="screen_login_error_deactivated_account">"该账户已被停用。"</string>
|
||||||
|
<string name="screen_login_error_invalid_credentials">"错误的用户名和/或密码"</string>
|
||||||
|
<string name="screen_login_error_invalid_user_id">"这不是合法的用户 ID。期望格式:‘@user:homeserver.org’。"</string>
|
||||||
|
<string name="screen_login_error_refresh_tokens">"此服务器使用刷新令牌。使用密码登录时不支持这些功能。"</string>
|
||||||
|
<string name="screen_login_error_unsupported_authentication">"该服务器不支持密码登录和 OIDC 第三方账户登录。请联系服务器管理员,或选择别的服务器。"</string>
|
||||||
|
<string name="screen_login_form_header">"输入您的详细信息"</string>
|
||||||
|
<string name="screen_login_subtitle">"Matrix 是一个用于安全、去中心化通信的开放网络。"</string>
|
||||||
|
<string name="screen_login_title">"欢迎回来!"</string>
|
||||||
|
<string name="screen_login_title_with_homeserver">"登录到 %1$s"</string>
|
||||||
|
<string name="screen_server_confirmation_change_server">"更改账户提供者"</string>
|
||||||
|
<string name="screen_server_confirmation_message_login_element_dot_io">"专为 Element 员工提供的私人服务器。"</string>
|
||||||
|
<string name="screen_server_confirmation_message_login_matrix_dot_org">"Matrix 是一个用于安全、去中心化通信的开放网络。"</string>
|
||||||
|
<string name="screen_server_confirmation_message_register">"这是您的对话将进行的地方,就像您使用电子邮件提供商来保存电子邮件一样。"</string>
|
||||||
|
<string name="screen_server_confirmation_title_login">"你即将登录 %1$s"</string>
|
||||||
|
<string name="screen_server_confirmation_title_register">"你即将在 %1$s 上创建一个账户"</string>
|
||||||
|
<string name="screen_waitlist_message">"目前 %1$s 上 %2$s 的负载很大。过几天再回来试试吧。
|
||||||
|
|
||||||
|
感谢您的耐心!"</string>
|
||||||
|
<string name="screen_waitlist_message_success">"欢迎使用 %1$s"</string>
|
||||||
|
<string name="screen_waitlist_title">"马上就好。"</string>
|
||||||
|
<string name="screen_waitlist_title_success">"您已加入。"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -55,7 +55,6 @@ dependencies {
|
||||||
testImplementation(libs.test.truth)
|
testImplementation(libs.test.truth)
|
||||||
testImplementation(libs.test.turbine)
|
testImplementation(libs.test.turbine)
|
||||||
testImplementation(libs.test.robolectric)
|
testImplementation(libs.test.robolectric)
|
||||||
testImplementation(libs.test.junitext)
|
|
||||||
testImplementation(libs.androidx.compose.ui.test.junit)
|
testImplementation(libs.androidx.compose.ui.test.junit)
|
||||||
testReleaseImplementation(libs.androidx.compose.ui.test.manifest)
|
testReleaseImplementation(libs.androidx.compose.ui.test.manifest)
|
||||||
testImplementation(projects.libraries.matrix.test)
|
testImplementation(projects.libraries.matrix.test)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_signout_confirmation_dialog_content">"დარწმუნებული ხართ, რომ გსურთ გამოსვლა?"</string>
|
||||||
|
<string name="screen_signout_confirmation_dialog_submit">"გამოსვლა"</string>
|
||||||
|
<string name="screen_signout_confirmation_dialog_title">"გამოსვლა"</string>
|
||||||
|
<string name="screen_signout_in_progress_dialog_content">"გასვლა…"</string>
|
||||||
|
<string name="screen_signout_preference_item">"გამოსვლა"</string>
|
||||||
|
</resources>
|
||||||
18
features/logout/impl/src/main/res/values-pt/translations.xml
Normal file
18
features/logout/impl/src/main/res/values-pt/translations.xml
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_signout_confirmation_dialog_content">"Tens a certeza que queres terminar a sessão?"</string>
|
||||||
|
<string name="screen_signout_confirmation_dialog_submit">"Terminar sessão"</string>
|
||||||
|
<string name="screen_signout_confirmation_dialog_title">"Terminar sessão"</string>
|
||||||
|
<string name="screen_signout_in_progress_dialog_content">"A terminar sessão…"</string>
|
||||||
|
<string name="screen_signout_key_backup_disabled_subtitle">"Estás prestes a terminar a tua última sessão. Se continuares, perderás o acesso às tuas mensagens cifradas."</string>
|
||||||
|
<string name="screen_signout_key_backup_disabled_title">"Desativaste a cópia de segurança"</string>
|
||||||
|
<string name="screen_signout_key_backup_offline_subtitle">"As tuas chaves ainda estavam a ser guardadas quando ficaste desligado. Volta a ligar-te para que as tuas chaves possam ser guardadas antes de encerrares a sessão."</string>
|
||||||
|
<string name="screen_signout_key_backup_offline_title">"As tuas chaves ainda estão a ser guardadas"</string>
|
||||||
|
<string name="screen_signout_key_backup_ongoing_subtitle">"Por favor, aguarda a conclusão desta operação antes de terminares a sessão."</string>
|
||||||
|
<string name="screen_signout_key_backup_ongoing_title">"As tuas chaves ainda estão a ser guardadas"</string>
|
||||||
|
<string name="screen_signout_preference_item">"Terminar sessão"</string>
|
||||||
|
<string name="screen_signout_recovery_disabled_subtitle">"Estás prestes a terminar a tua última sessão. Se continuares, perderás o acesso às tuas mensagens cifradas."</string>
|
||||||
|
<string name="screen_signout_recovery_disabled_title">"Recuperação não configurada"</string>
|
||||||
|
<string name="screen_signout_save_recovery_key_subtitle">"Estás prestes a terminar a tua última sessão. Se continuares, poderás perder o acesso às tuas mensagens cifradas."</string>
|
||||||
|
<string name="screen_signout_save_recovery_key_title">"Guardaste a tua chave de recuperação?"</string>
|
||||||
|
</resources>
|
||||||
18
features/logout/impl/src/main/res/values-zh/translations.xml
Normal file
18
features/logout/impl/src/main/res/values-zh/translations.xml
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_signout_confirmation_dialog_content">"确定要登出吗?"</string>
|
||||||
|
<string name="screen_signout_confirmation_dialog_submit">"登出"</string>
|
||||||
|
<string name="screen_signout_confirmation_dialog_title">"登出"</string>
|
||||||
|
<string name="screen_signout_in_progress_dialog_content">"正在登出…"</string>
|
||||||
|
<string name="screen_signout_key_backup_disabled_subtitle">"您即将登出最后一个会话。如果现在登出,你将无法访问加密的消息。"</string>
|
||||||
|
<string name="screen_signout_key_backup_disabled_title">"您已关闭备份"</string>
|
||||||
|
<string name="screen_signout_key_backup_offline_subtitle">"当你离线时,你的密钥仍在备份中。重新连接,以便在登出之前备份密钥。"</string>
|
||||||
|
<string name="screen_signout_key_backup_offline_title">"您的密钥仍在备份中"</string>
|
||||||
|
<string name="screen_signout_key_backup_ongoing_subtitle">"请等待此操作完成后再登出。"</string>
|
||||||
|
<string name="screen_signout_key_backup_ongoing_title">"您的密钥仍在备份中"</string>
|
||||||
|
<string name="screen_signout_preference_item">"登出"</string>
|
||||||
|
<string name="screen_signout_recovery_disabled_subtitle">"您即将登出最后一个会话。如果现在登出,你将无法访问加密的消息。"</string>
|
||||||
|
<string name="screen_signout_recovery_disabled_title">"未设置恢复"</string>
|
||||||
|
<string name="screen_signout_save_recovery_key_subtitle">"您即将登出最后一个会话。如果现在登出,你将无法访问加密的消息。"</string>
|
||||||
|
<string name="screen_signout_save_recovery_key_title">"您保存了恢复密钥吗?"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -144,7 +144,9 @@ class LogoutPresenterTest {
|
||||||
@Test
|
@Test
|
||||||
fun `present - logout with error then cancel`() = runTest {
|
fun `present - logout with error then cancel`() = runTest {
|
||||||
val matrixClient = FakeMatrixClient().apply {
|
val matrixClient = FakeMatrixClient().apply {
|
||||||
givenLogoutError(A_THROWABLE)
|
logoutLambda = { _ ->
|
||||||
|
throw A_THROWABLE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
val presenter = createLogoutPresenter(
|
val presenter = createLogoutPresenter(
|
||||||
matrixClient,
|
matrixClient,
|
||||||
|
|
@ -170,7 +172,13 @@ class LogoutPresenterTest {
|
||||||
@Test
|
@Test
|
||||||
fun `present - logout with error then force`() = runTest {
|
fun `present - logout with error then force`() = runTest {
|
||||||
val matrixClient = FakeMatrixClient().apply {
|
val matrixClient = FakeMatrixClient().apply {
|
||||||
givenLogoutError(A_THROWABLE)
|
logoutLambda = { ignoreSdkError ->
|
||||||
|
if (!ignoreSdkError) {
|
||||||
|
throw A_THROWABLE
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
val presenter = createLogoutPresenter(
|
val presenter = createLogoutPresenter(
|
||||||
matrixClient,
|
matrixClient,
|
||||||
|
|
|
||||||
|
|
@ -125,7 +125,9 @@ class DefaultDirectLogoutPresenterTest {
|
||||||
@Test
|
@Test
|
||||||
fun `present - logout with error then cancel`() = runTest {
|
fun `present - logout with error then cancel`() = runTest {
|
||||||
val matrixClient = FakeMatrixClient().apply {
|
val matrixClient = FakeMatrixClient().apply {
|
||||||
givenLogoutError(A_THROWABLE)
|
logoutLambda = { _ ->
|
||||||
|
throw A_THROWABLE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
val presenter = createDefaultDirectLogoutPresenter(
|
val presenter = createDefaultDirectLogoutPresenter(
|
||||||
matrixClient,
|
matrixClient,
|
||||||
|
|
@ -151,7 +153,13 @@ class DefaultDirectLogoutPresenterTest {
|
||||||
@Test
|
@Test
|
||||||
fun `present - logout with error then force`() = runTest {
|
fun `present - logout with error then force`() = runTest {
|
||||||
val matrixClient = FakeMatrixClient().apply {
|
val matrixClient = FakeMatrixClient().apply {
|
||||||
givenLogoutError(A_THROWABLE)
|
logoutLambda = { ignoreSdkError ->
|
||||||
|
if (!ignoreSdkError) {
|
||||||
|
throw A_THROWABLE
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
val presenter = createDefaultDirectLogoutPresenter(
|
val presenter = createDefaultDirectLogoutPresenter(
|
||||||
matrixClient,
|
matrixClient,
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,6 @@ dependencies {
|
||||||
testImplementation(projects.libraries.mediaviewer.test)
|
testImplementation(projects.libraries.mediaviewer.test)
|
||||||
testImplementation(projects.libraries.testtags)
|
testImplementation(projects.libraries.testtags)
|
||||||
testImplementation(libs.test.mockk)
|
testImplementation(libs.test.mockk)
|
||||||
testImplementation(libs.test.junitext)
|
|
||||||
testImplementation(libs.test.robolectric)
|
testImplementation(libs.test.robolectric)
|
||||||
testImplementation(projects.features.poll.test)
|
testImplementation(projects.features.poll.test)
|
||||||
testImplementation(projects.features.poll.impl)
|
testImplementation(projects.features.poll.impl)
|
||||||
|
|
|
||||||
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