Merge branch 'release/25.10.1'
This commit is contained in:
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -69,7 +69,7 @@ jobs:
|
||||
retention-days: 5
|
||||
overwrite: true
|
||||
if-no-files-found: error
|
||||
- uses: rnkdsh/action-upload-diawi@26292a7b424bdc9f4ab4ccea6202fc513f571370 # v1.5.11
|
||||
- uses: rnkdsh/action-upload-diawi@4e1421305be7cfc510d05f47850262eeaf345108 # v1.5.12
|
||||
id: diawi
|
||||
# Do not fail the whole build if Diawi upload fails
|
||||
continue-on-error: true
|
||||
|
||||
2
.github/workflows/generate_github_pages.yml
vendored
2
.github/workflows/generate_github_pages.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
||||
- name: Set up Python 3.12
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.13
|
||||
python-version: 3.14
|
||||
- name: Run World screenshots generation script
|
||||
run: |
|
||||
./tools/test/generateWorldScreenshots.py
|
||||
|
||||
37
.github/workflows/quality.yml
vendored
37
.github/workflows/quality.yml
vendored
@@ -37,7 +37,7 @@ jobs:
|
||||
- name: Set up Python 3.12
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.13
|
||||
python-version: 3.14
|
||||
- name: Search for invalid screenshot files
|
||||
run: ./tools/test/checkInvalidScreenshots.py
|
||||
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
- name: Set up Python 3.12
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.13
|
||||
python-version: 3.14
|
||||
- name: Search for invalid dependencies
|
||||
run: ./tools/dependencies/checkDependencies.py
|
||||
|
||||
@@ -103,6 +103,39 @@ jobs:
|
||||
path: |
|
||||
**/build/reports/**/*.*
|
||||
|
||||
compose:
|
||||
name: Compose tests
|
||||
runs-on: ubuntu-latest
|
||||
# Allow all jobs on main and develop. Just one per PR.
|
||||
concurrency:
|
||||
group: ${{ github.ref == 'refs/heads/main' && format('check-compose-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('check-compose-develop-{0}', github.sha) || format('check-compose-{0}', github.ref) }}
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
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: Add SSH private keys for submodule repositories
|
||||
uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd # v0.9.1
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.ELEMENT_ENTERPRISE_DEPLOY_KEY }}
|
||||
- name: Clone submodules
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
||||
run: git submodule update --init --recursive
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@v5
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
- name: Run compose tests
|
||||
run: ./tools/compose/check_stability.sh
|
||||
|
||||
lint:
|
||||
name: Android lint check
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
2
.github/workflows/sync-localazy.yml
vendored
2
.github/workflows/sync-localazy.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
- name: Set up Python 3.12
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.13
|
||||
python-version: 3.14
|
||||
- name: Setup Localazy
|
||||
run: |
|
||||
curl -sS https://dist.localazy.com/debian/pubkey.gpg | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/localazy.gpg
|
||||
|
||||
2
.github/workflows/sync-sas-strings.yml
vendored
2
.github/workflows/sync-sas-strings.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
- name: Set up Python 3.12
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.13
|
||||
python-version: 3.14
|
||||
- name: Install Prerequisite dependencies
|
||||
run: |
|
||||
pip install requests
|
||||
|
||||
80
CHANGES.md
80
CHANGES.md
@@ -1,3 +1,83 @@
|
||||
Changes in Element X v25.10.0
|
||||
=============================
|
||||
|
||||
<!-- Release notes generated using configuration in .github/release.yml at v25.10.0 -->
|
||||
|
||||
## What's Changed
|
||||
### ✨ Features
|
||||
* Use shared recent emoji reactions from account data by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5402
|
||||
* Follow permalinks to and from threads by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5414
|
||||
* Add support for Spaces by @bmarty in https://github.com/element-hq/element-x-android/pull/5462
|
||||
* Add Labs screen for beta testing of public features by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5465
|
||||
### 🙌 Improvements
|
||||
* Update the strings for the device verification flow by @andybalaam in https://github.com/element-hq/element-x-android/pull/5419
|
||||
* Set a notification sound by @bmarty in https://github.com/element-hq/element-x-android/pull/5469
|
||||
* Improve current push provider test: give info about the distributor. by @bmarty in https://github.com/element-hq/element-x-android/pull/5471
|
||||
* Improve AnnouncementService. by @bmarty in https://github.com/element-hq/element-x-android/pull/5482
|
||||
### 🐛 Bugfixes
|
||||
* Improvement and bugfix on incoming verification request screen by @bmarty in https://github.com/element-hq/element-x-android/pull/5426
|
||||
* Space : makes sure to use room heroes for avatar by @ganfra in https://github.com/element-hq/element-x-android/pull/5488
|
||||
* Filter out direct room in the leave space screen. by @bmarty in https://github.com/element-hq/element-x-android/pull/5498
|
||||
### 🗣 Translations
|
||||
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/5427
|
||||
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/5460
|
||||
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/5486
|
||||
### 🧱 Build
|
||||
* Remove unused dependency on `javax.inject:javax.inject` by @bmarty in https://github.com/element-hq/element-x-android/pull/5445
|
||||
* Internalize compound-android by @bmarty in https://github.com/element-hq/element-x-android/pull/5457
|
||||
### 🚧 In development 🚧
|
||||
* Sdk : use latest apis for space by @ganfra in https://github.com/element-hq/element-x-android/pull/5404
|
||||
* Multi accounts - experimental first implementation by @bmarty in https://github.com/element-hq/element-x-android/pull/5285
|
||||
* Leave space - UI by @bmarty in https://github.com/element-hq/element-x-android/pull/5354
|
||||
* Leave spave: iteration on string value. by @bmarty in https://github.com/element-hq/element-x-android/pull/5425
|
||||
* Feature : space list join action by @ganfra in https://github.com/element-hq/element-x-android/pull/5431
|
||||
* Room list space invite by @ganfra in https://github.com/element-hq/element-x-android/pull/5449
|
||||
* Leave space: use SDK API. by @bmarty in https://github.com/element-hq/element-x-android/pull/5432
|
||||
* Space annoucement by @bmarty in https://github.com/element-hq/element-x-android/pull/5451
|
||||
* feature(space) : keep space children in the presenter by @ganfra in https://github.com/element-hq/element-x-android/pull/5456
|
||||
* Spaces : some tweaks around ui by @ganfra in https://github.com/element-hq/element-x-android/pull/5468
|
||||
* Use "BETA" word from Localazy and ensure layout is correct by @bmarty in https://github.com/element-hq/element-x-android/pull/5470
|
||||
* Disable avatar cluster for now by @bmarty in https://github.com/element-hq/element-x-android/pull/5492
|
||||
### Dependency upgrades
|
||||
* Update dependency com.posthog:posthog-android to v3.21.3 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5360
|
||||
* Update dependency io.element.android:element-call-embedded to v0.16.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5408
|
||||
* Update dependency net.java.dev.jna:jna to v5.18.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5398
|
||||
* Update plugin dependencycheck to v12.1.6 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5405
|
||||
* Update dependency org.matrix.rustcomponents:sdk-android to v25.9.25 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5412
|
||||
* Update dependency androidx.sqlite:sqlite-ktx to v2.6.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5409
|
||||
* Update kotlin by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5317
|
||||
* Update metro to v0.6.7 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5416
|
||||
* Update dependency app.cash.molecule:molecule-runtime to v2.2.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5413
|
||||
* Update dependency com.posthog:posthog-android to v3.22.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5415
|
||||
* Update metro to v0.6.8 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5422
|
||||
* fix(deps): update dependency org.matrix.rustcomponents:sdk-android to v25.10.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5438
|
||||
* fix(deps): update dependency net.java.dev.jna:jna to v5.18.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5437
|
||||
* fix(deps): update dependency io.mockk:mockk to v1.14.6 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5441
|
||||
* Update gradle/actions action to v5 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5444
|
||||
* fix(deps): update dependency io.sentry:sentry-android to v8.23.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5442
|
||||
* fix(deps): update dependency org.maplibre.gl:android-sdk to v12 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5455
|
||||
* fix(deps): update dependency com.posthog:posthog-android to v3.23.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5463
|
||||
* fix(deps): update roborazzi to v1.50.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5464
|
||||
* fix(deps): update telephoto to v0.18.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5459
|
||||
### Others
|
||||
* Ensure Metro `@AssistedInject` is used. by @bmarty in https://github.com/element-hq/element-x-android/pull/5420
|
||||
* Misc : destroy SpaceRoomList by @ganfra in https://github.com/element-hq/element-x-android/pull/5436
|
||||
* Remove CurrentSessionIdHolder and inject SessionId instead. by @bmarty in https://github.com/element-hq/element-x-android/pull/5440
|
||||
* Only offer to verify if a cross-signed device is available by @uhoreg in https://github.com/element-hq/element-x-android/pull/5433
|
||||
* Replace fun by val in MatrixClient by @bmarty in https://github.com/element-hq/element-x-android/pull/5466
|
||||
* Space : makes sure to use SpaceRoom.displayName from sdk by @ganfra in https://github.com/element-hq/element-x-android/pull/5476
|
||||
* Add preview with all icons in the Showkase browser by @bmarty in https://github.com/element-hq/element-x-android/pull/5485
|
||||
* Ensure that we are using Immutable instead of Persistent by @bmarty in https://github.com/element-hq/element-x-android/pull/5490
|
||||
* Reduce number of Previews for Avatar. by @bmarty in https://github.com/element-hq/element-x-android/pull/5495
|
||||
* Fix error when attempting to verify with recovery key with missing backup key by @uhoreg in https://github.com/element-hq/element-x-android/pull/5314
|
||||
* Sync strings by @bmarty in https://github.com/element-hq/element-x-android/pull/5499
|
||||
* feature(space): make sure to handle topic properly by @ganfra in https://github.com/element-hq/element-x-android/pull/5493
|
||||
|
||||
## New Contributors
|
||||
* @uhoreg made their first contribution in https://github.com/element-hq/element-x-android/pull/5433
|
||||
|
||||
**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v25.09.2...v25.10.0
|
||||
|
||||
Changes in Element X v25.09.2
|
||||
=============================
|
||||
|
||||
|
||||
@@ -197,6 +197,12 @@ android {
|
||||
buildConfigFieldStr("FLAVOR_DESCRIPTION", "FDroid")
|
||||
}
|
||||
}
|
||||
|
||||
packaging {
|
||||
resources.pickFirsts += setOf(
|
||||
"META-INF/versions/9/OSGI-INF/MANIFEST.MF",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
androidComponents {
|
||||
@@ -318,6 +324,7 @@ licensee {
|
||||
allowUrl("https://jsoup.org/license")
|
||||
allowUrl("https://asm.ow2.io/license.html")
|
||||
allowUrl("https://www.gnu.org/licenses/agpl-3.0.txt")
|
||||
allowUrl("https://github.com/mhssn95/compose-color-picker/blob/main/LICENSE")
|
||||
ignoreDependencies("com.github.matrix-org", "matrix-analytics-events")
|
||||
// Ignore dependency that are not third-party licenses to us.
|
||||
ignoreDependencies(groupId = "io.element.android")
|
||||
|
||||
@@ -34,10 +34,17 @@
|
||||
<provider
|
||||
android:name="androidx.startup.InitializationProvider"
|
||||
android:authorities="${applicationId}.androidx-startup"
|
||||
android:exported="false">
|
||||
android:exported="false"
|
||||
tools:node="merge">
|
||||
<meta-data
|
||||
android:name='androidx.lifecycle.ProcessLifecycleInitializer'
|
||||
android:value='androidx.startup' />
|
||||
|
||||
<!-- Remove to use Application workManagerConfiguration -->
|
||||
<meta-data
|
||||
android:name="androidx.work.WorkManagerInitializer"
|
||||
android:value="androidx.startup"
|
||||
tools:node="remove" />
|
||||
</provider>
|
||||
|
||||
<!--
|
||||
@@ -175,7 +182,6 @@
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_providers" />
|
||||
</provider>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
@@ -9,17 +9,23 @@ package io.element.android.x
|
||||
|
||||
import android.app.Application
|
||||
import androidx.startup.AppInitializer
|
||||
import androidx.work.Configuration
|
||||
import dev.zacsweers.metro.createGraphFactory
|
||||
import io.element.android.features.cachecleaner.api.CacheCleanerInitializer
|
||||
import io.element.android.libraries.di.DependencyInjectionGraphOwner
|
||||
import io.element.android.libraries.workmanager.api.di.MetroWorkerFactory
|
||||
import io.element.android.x.di.AppGraph
|
||||
import io.element.android.x.info.logApplicationInfo
|
||||
import io.element.android.x.initializer.CrashInitializer
|
||||
import io.element.android.x.initializer.PlatformInitializer
|
||||
|
||||
class ElementXApplication : Application(), DependencyInjectionGraphOwner {
|
||||
class ElementXApplication : Application(), DependencyInjectionGraphOwner, Configuration.Provider {
|
||||
override val graph: AppGraph = createGraphFactory<AppGraph.Factory>().create(this)
|
||||
|
||||
override val workManagerConfiguration: Configuration = Configuration.Builder()
|
||||
.setWorkerFactory(MetroWorkerFactory(graph.workerProviders))
|
||||
.build()
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
AppInitializer.getInstance(this).apply {
|
||||
@@ -27,6 +33,7 @@ class ElementXApplication : Application(), DependencyInjectionGraphOwner {
|
||||
initializeComponent(PlatformInitializer::class.java)
|
||||
initializeComponent(CacheCleanerInitializer::class.java)
|
||||
}
|
||||
|
||||
logApplicationInfo(this)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,16 +8,24 @@
|
||||
package io.element.android.x.di
|
||||
|
||||
import android.content.Context
|
||||
import androidx.work.ListenableWorker
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.DependencyGraph
|
||||
import dev.zacsweers.metro.Multibinds
|
||||
import dev.zacsweers.metro.Provides
|
||||
import io.element.android.libraries.architecture.NodeFactoriesBindings
|
||||
import io.element.android.libraries.di.annotations.ApplicationContext
|
||||
import io.element.android.libraries.workmanager.api.di.MetroWorkerFactory
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
@DependencyGraph(AppScope::class)
|
||||
interface AppGraph : NodeFactoriesBindings {
|
||||
val sessionGraphFactory: SessionGraph.Factory
|
||||
|
||||
@Multibinds
|
||||
val workerProviders:
|
||||
Map<KClass<out ListenableWorker>, MetroWorkerFactory.WorkerInstanceFactory<*>>
|
||||
|
||||
@DependencyGraph.Factory
|
||||
interface Factory {
|
||||
fun create(
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
<locale android:name="el"/>
|
||||
<locale android:name="en"/>
|
||||
<locale android:name="en_US"/>
|
||||
<locale android:name="eo"/>
|
||||
<locale android:name="es"/>
|
||||
<locale android:name="et"/>
|
||||
<locale android:name="eu"/>
|
||||
|
||||
@@ -73,7 +73,7 @@ class LoggedInAppScopeFlowNode(
|
||||
override fun onBuilt() {
|
||||
super.onBuilt()
|
||||
lifecycle.subscribe(
|
||||
onCreate = {
|
||||
onResume = {
|
||||
SingletonImageLoader.setUnsafe(imageLoaderHolder.get(inputs.matrixClient))
|
||||
},
|
||||
)
|
||||
|
||||
@@ -63,7 +63,7 @@ class NotLoggedInFlowNode(
|
||||
override fun onBuilt() {
|
||||
super.onBuilt()
|
||||
lifecycle.subscribe(
|
||||
onCreate = {
|
||||
onResume = {
|
||||
SingletonImageLoader.setUnsafe(notLoggedInImageLoaderFactory.newImageLoader())
|
||||
},
|
||||
)
|
||||
|
||||
@@ -7,12 +7,10 @@
|
||||
|
||||
package io.element.android.appnav.root
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import io.element.android.features.rageshake.api.crash.CrashDetectionState
|
||||
import io.element.android.features.rageshake.api.detection.RageshakeDetectionState
|
||||
import io.element.android.services.apperror.api.AppErrorState
|
||||
|
||||
@Immutable
|
||||
data class RootState(
|
||||
val rageshakeDetectionState: RageshakeDetectionState,
|
||||
val crashDetectionState: CrashDetectionState,
|
||||
|
||||
Submodule enterprise updated: ffc02b8d0f...c5465c9579
2
fastlane/metadata/android/en-US/changelogs/202510010.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/202510010.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Main changes in this version: bug fixes around notifications and UX improvements.
|
||||
Full changelog: https://github.com/element-hq/element-x-android/releases
|
||||
@@ -1,11 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_space_announcement_item1">"Se klynger, du har oprettet eller tilmeldt dig"</string>
|
||||
<string name="screen_space_announcement_item2">"Acceptere eller afvise invitationer til klynger"</string>
|
||||
<string name="screen_space_announcement_item3">"Finde alle rum, du kan deltage i, i dine klynger"</string>
|
||||
<string name="screen_space_announcement_item4">"Deltage i offentlige klynger"</string>
|
||||
<string name="screen_space_announcement_item5">"Forlade de klynger, du har tilsluttet dig"</string>
|
||||
<string name="screen_space_announcement_notice">"Oprettelse og administration af klynger kommer snart."</string>
|
||||
<string name="screen_space_announcement_subtitle">"Velkommen til betaversionen af Klynger! Med denne første version kan du:"</string>
|
||||
<string name="screen_space_announcement_title">"Introduktion til Klynger"</string>
|
||||
<string name="screen_space_announcement_item1">"Se grupper, du har oprettet eller tilmeldt dig"</string>
|
||||
<string name="screen_space_announcement_item2">"Acceptere eller afvise invitationer til grupper"</string>
|
||||
<string name="screen_space_announcement_item3">"Finde alle rum, du kan deltage i, i dine grupper"</string>
|
||||
<string name="screen_space_announcement_item4">"Deltage i offentlige grupper"</string>
|
||||
<string name="screen_space_announcement_item5">"Forlade de grupper, du har tilsluttet dig"</string>
|
||||
<string name="screen_space_announcement_notice">"Filtrering, oprettelse og administration af grupper kommer snart."</string>
|
||||
<string name="screen_space_announcement_subtitle">"Velkommen til betaversionen af Grupper! Med denne første version kan du:"</string>
|
||||
<string name="screen_space_announcement_title">"Introduktion til Grupper"</string>
|
||||
</resources>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<string name="screen_space_announcement_item3">"Chats innerhalb deiner Spaces entdecken, um ihnen beizutreten"</string>
|
||||
<string name="screen_space_announcement_item4">"Öffentlichen Spaces beitreten"</string>
|
||||
<string name="screen_space_announcement_item5">"Spaces verlassen, bei denen du Mitglied bist"</string>
|
||||
<string name="screen_space_announcement_notice">"Das Erstellen und Verwalten von Spaces ist bald verfügbar."</string>
|
||||
<string name="screen_space_announcement_notice">"Das Filtern, Erstellen und Verwalten von Spaces ist bald verfügbar."</string>
|
||||
<string name="screen_space_announcement_subtitle">"Willkommen bei der Beta-Version von Spaces! Mit dieser ersten Version kannst du:"</string>
|
||||
<string name="screen_space_announcement_title">"Einführung in Spaces"</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_space_announcement_item1">"Vaadata kogukondi, mille oled loonud või millega oled liitunud"</string>
|
||||
<string name="screen_space_announcement_item2">"Nõustuda kutsetega liitumiseks kogukonnaga või sellest keelduda"</string>
|
||||
<string name="screen_space_announcement_item3">"Uurida neis kogukondades leiduvaid jututube ning nendega liituda"</string>
|
||||
<string name="screen_space_announcement_item4">"Liituda avalike kogukondadega"</string>
|
||||
<string name="screen_space_announcement_item5">"Lahkuda kogukonnast, millega oled liitunud"</string>
|
||||
<string name="screen_space_announcement_notice">"Kogukondade filtreerimine, loomine ja haldamine lisandub peagi"</string>
|
||||
<string name="screen_space_announcement_subtitle">"Tere tulemast kasutama kogukondade beetaversiooni! Selles esimeses versioonis saad sa:"</string>
|
||||
<string name="screen_space_announcement_title">"Võtame kasutusele kogukonnad"</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_space_announcement_item1">"دیدن فضاهایی که ساخته یا پیوستهاید"</string>
|
||||
<string name="screen_space_announcement_item2">"پذیرش یا رد دعوتها به فضاها"</string>
|
||||
<string name="screen_space_announcement_item3">"کشف تمامی اتاقهایی که میتوانید در فضاهایتان بپیوندید"</string>
|
||||
<string name="screen_space_announcement_item4">"پیوستن به فضاهای عمومی"</string>
|
||||
<string name="screen_space_announcement_item5">"ترک هر فضایی که پیوستهاید"</string>
|
||||
<string name="screen_space_announcement_notice">"پالایش، ایجاد و مدیریت کردن فضاها به زودی."</string>
|
||||
<string name="screen_space_announcement_subtitle">"به نگارش آزمایشی فضاها خوش آمدید! در این نگارش میتوانید:"</string>
|
||||
<string name="screen_space_announcement_title">"معرّفی فضاها"</string>
|
||||
</resources>
|
||||
@@ -5,7 +5,7 @@
|
||||
<string name="screen_space_announcement_item3">"Löytää kaikki huoneet, joihin voit liittyä tiloissasi"</string>
|
||||
<string name="screen_space_announcement_item4">"Liittyä julkisiin tiloihin"</string>
|
||||
<string name="screen_space_announcement_item5">"Poistua mistä tahansa tilasta, johon olet liittynyt"</string>
|
||||
<string name="screen_space_announcement_notice">"Tilojen luominen ja hallinta on tulossa pian."</string>
|
||||
<string name="screen_space_announcement_notice">"Tilojen suodatus, luominen ja hallinta on tulossa pian."</string>
|
||||
<string name="screen_space_announcement_subtitle">"Tervetuloa tilojen beetaversioon! Tämän ensimmäisen version avulla voit:"</string>
|
||||
<string name="screen_space_announcement_title">"Esittelyssä tilat"</string>
|
||||
</resources>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<string name="screen_space_announcement_item3">"Découvrir les salons que vous pouvez joindre depuis vos espaces"</string>
|
||||
<string name="screen_space_announcement_item4">"Rejoindre les espaces publics"</string>
|
||||
<string name="screen_space_announcement_item5">"Quitter les espaces dont vous êtes membre."</string>
|
||||
<string name="screen_space_announcement_notice">"La création et la gestion des espaces seront bientôt disponibles."</string>
|
||||
<string name="screen_space_announcement_notice">"Le filtrage, la création et la gestion des espaces seront bientôt disponibles."</string>
|
||||
<string name="screen_space_announcement_subtitle">"Bienvenue dans la version bêta des espaces! Avec cette première version, vous pourrez :"</string>
|
||||
<string name="screen_space_announcement_title">"Ajout des espaces"</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_space_announcement_item1">"Az Ön által létrehozott vagy csatlakozott térek megtekintése"</string>
|
||||
<string name="screen_space_announcement_item2">"A meghívások elfogadására vagy elutasítására a terekhez"</string>
|
||||
<string name="screen_space_announcement_item3">"Szobák felfedezése a terekben, amelyekhez csatlakozhat"</string>
|
||||
<string name="screen_space_announcement_item4">"Csatlakozás nyilvános terekhez"</string>
|
||||
<string name="screen_space_announcement_item5">"Terek elhagyása"</string>
|
||||
<string name="screen_space_announcement_notice">"Terek szűrése, készítése és kezelése hamarosan érkezik."</string>
|
||||
<string name="screen_space_announcement_subtitle">"Üdvözöljük a tér béta verziójában! Ezzel az első verzióval a következőket teheti:"</string>
|
||||
</resources>
|
||||
@@ -5,7 +5,7 @@
|
||||
<string name="screen_space_announcement_item3">"Oppdag alle rom du kan bli med i i dine områder"</string>
|
||||
<string name="screen_space_announcement_item4">"Bli med i offentlige områder"</string>
|
||||
<string name="screen_space_announcement_item5">"Forlat områder du har blitt med i"</string>
|
||||
<string name="screen_space_announcement_notice">"Oppretting og administrasjon av områder kommer snart."</string>
|
||||
<string name="screen_space_announcement_notice">"Oppretting, filtrering og administrasjon av områder kommer snart."</string>
|
||||
<string name="screen_space_announcement_subtitle">"Velkommen til betaversjonen av Områder! Med denne første versjonen kan du:"</string>
|
||||
<string name="screen_space_announcement_title">"Vi introduserer Områder"</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_space_announcement_item1">"Wyświetlić przestrzenie, które stworzyłeś lub do których dołączyłeś"</string>
|
||||
<string name="screen_space_announcement_item2">"Akceptować lub odrzucać zaproszenia"</string>
|
||||
<string name="screen_space_announcement_item3">"Odkrywać wszystkie pokoje, do których możesz dołączyć w swoich przestrzeniach"</string>
|
||||
<string name="screen_space_announcement_item4">"Dołączać do przestrzeni publicznych"</string>
|
||||
<string name="screen_space_announcement_item5">"Opuszczać jakąkolwiek przestrzeń, do której dołączyłeś"</string>
|
||||
<string name="screen_space_announcement_notice">"Filtrowanie, tworzenie i zarządzanie przestrzeniami pojawi się wkrótce."</string>
|
||||
<string name="screen_space_announcement_subtitle">"Witamy w wersji beta przestrzeni! W tej wersji możesz:"</string>
|
||||
<string name="screen_space_announcement_title">"Przedstawiamy przestrzenie"</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_space_announcement_item1">"檢視您建立或加入的空間"</string>
|
||||
<string name="screen_space_announcement_item2">"接受或拒絕空間邀請"</string>
|
||||
<string name="screen_space_announcement_item3">"探索空間內您可以加入的任何聊天室"</string>
|
||||
<string name="screen_space_announcement_item4">"加入公開空間"</string>
|
||||
<string name="screen_space_announcement_item5">"離開任何您已加入的空間"</string>
|
||||
<string name="screen_space_announcement_notice">"篩選、建立與管理空間功能即將推出。"</string>
|
||||
<string name="screen_space_announcement_subtitle">"歡迎使用空間的測試版!此初始版本可讓您:"</string>
|
||||
<string name="screen_space_announcement_title">"介紹空間"</string>
|
||||
</resources>
|
||||
@@ -64,6 +64,7 @@ class CallScreenPresenter(
|
||||
private val appForegroundStateService: AppForegroundStateService,
|
||||
@AppCoroutineScope
|
||||
private val appCoroutineScope: CoroutineScope,
|
||||
private val widgetMessageSerializer: WidgetMessageSerializer,
|
||||
) : Presenter<CallScreenState> {
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
@@ -258,7 +259,7 @@ class CallScreenPresenter(
|
||||
}
|
||||
|
||||
private fun parseMessage(message: String): WidgetMessage? {
|
||||
return WidgetMessageSerializer.deserialize(message).getOrNull()
|
||||
return widgetMessageSerializer.deserialize(message).getOrNull()
|
||||
}
|
||||
|
||||
private fun sendHangupMessage(widgetId: String, messageInterceptor: WidgetMessageInterceptor) {
|
||||
@@ -269,7 +270,7 @@ class CallScreenPresenter(
|
||||
action = WidgetMessage.Action.HangUp,
|
||||
data = null,
|
||||
)
|
||||
messageInterceptor.sendMessage(WidgetMessageSerializer.serialize(message))
|
||||
messageInterceptor.sendMessage(widgetMessageSerializer.serialize(message))
|
||||
}
|
||||
|
||||
private fun CoroutineScope.close(widgetDriver: MatrixWidgetDriver?, navigator: CallScreenNavigator) = launch(dispatchers.io) {
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
package io.element.android.features.call.impl.ui
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.util.Log
|
||||
import android.view.ViewGroup
|
||||
import android.webkit.ConsoleMessage
|
||||
import android.webkit.JavascriptInterface
|
||||
@@ -60,6 +59,7 @@ interface CallScreenNavigator {
|
||||
internal fun CallScreenView(
|
||||
state: CallScreenState,
|
||||
pipState: PictureInPictureState,
|
||||
onConsoleMessage: (ConsoleMessage) -> Unit,
|
||||
requestPermissions: (Array<String>, RequestPermissionCallback) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
@@ -108,6 +108,7 @@ internal fun CallScreenView(
|
||||
val callback: RequestPermissionCallback = { request.grant(it) }
|
||||
requestPermissions(androidPermissions.toTypedArray(), callback)
|
||||
},
|
||||
onConsoleMessage = onConsoleMessage,
|
||||
onCreateWebView = { webView ->
|
||||
webView.addBackHandler(onBackPressed = ::handleBack)
|
||||
val interceptor = WebViewWidgetMessageInterceptor(
|
||||
@@ -174,6 +175,7 @@ private fun CallWebView(
|
||||
url: AsyncData<String>,
|
||||
userAgent: String,
|
||||
onPermissionsRequest: (PermissionRequest) -> Unit,
|
||||
onConsoleMessage: (ConsoleMessage) -> Unit,
|
||||
onCreateWebView: (WebView) -> Unit,
|
||||
onDestroyWebView: (WebView) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
@@ -188,7 +190,11 @@ private fun CallWebView(
|
||||
factory = { context ->
|
||||
WebView(context).apply {
|
||||
onCreateWebView(this)
|
||||
setup(userAgent, onPermissionsRequest)
|
||||
setup(
|
||||
userAgent = userAgent,
|
||||
onPermissionsRequested = onPermissionsRequest,
|
||||
onConsoleMessage = onConsoleMessage,
|
||||
)
|
||||
}
|
||||
},
|
||||
update = { webView ->
|
||||
@@ -208,6 +214,7 @@ private fun CallWebView(
|
||||
private fun WebView.setup(
|
||||
userAgent: String,
|
||||
onPermissionsRequested: (PermissionRequest) -> Unit,
|
||||
onConsoleMessage: (ConsoleMessage) -> Unit,
|
||||
) {
|
||||
layoutParams = ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
@@ -232,35 +239,7 @@ private fun WebView.setup(
|
||||
}
|
||||
|
||||
override fun onConsoleMessage(consoleMessage: ConsoleMessage): Boolean {
|
||||
val priority = when (consoleMessage.messageLevel()) {
|
||||
ConsoleMessage.MessageLevel.ERROR -> Log.ERROR
|
||||
ConsoleMessage.MessageLevel.WARNING -> Log.WARN
|
||||
else -> Log.DEBUG
|
||||
}
|
||||
|
||||
val message = buildString {
|
||||
append(consoleMessage.sourceId())
|
||||
append(":")
|
||||
append(consoleMessage.lineNumber())
|
||||
append(" ")
|
||||
append(consoleMessage.message())
|
||||
}
|
||||
|
||||
if (message.contains("password=")) {
|
||||
// Avoid logging any messages that contain "password" to prevent leaking sensitive information
|
||||
return true
|
||||
}
|
||||
|
||||
Timber.tag("WebView").log(
|
||||
priority = priority,
|
||||
message = buildString {
|
||||
append(consoleMessage.sourceId())
|
||||
append(":")
|
||||
append(consoleMessage.lineNumber())
|
||||
append(" ")
|
||||
append(consoleMessage.message())
|
||||
},
|
||||
)
|
||||
onConsoleMessage(consoleMessage)
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -286,6 +265,7 @@ internal fun CallScreenViewPreview(
|
||||
state = state,
|
||||
pipState = aPictureInPictureState(),
|
||||
requestPermissions = { _, _ -> },
|
||||
onConsoleMessage = {},
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ import io.element.android.features.call.impl.pip.PipView
|
||||
import io.element.android.features.call.impl.services.CallForegroundService
|
||||
import io.element.android.features.call.impl.utils.CallIntentDataParser
|
||||
import io.element.android.features.enterprise.api.EnterpriseService
|
||||
import io.element.android.libraries.androidutils.browser.ConsoleMessageLogger
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.architecture.bindings
|
||||
import io.element.android.libraries.audio.api.AudioFocus
|
||||
@@ -65,6 +66,7 @@ class ElementCallActivity :
|
||||
@Inject lateinit var pictureInPicturePresenter: PictureInPicturePresenter
|
||||
@Inject lateinit var buildMeta: BuildMeta
|
||||
@Inject lateinit var audioFocus: AudioFocus
|
||||
@Inject lateinit var consoleMessageLogger: ConsoleMessageLogger
|
||||
|
||||
private lateinit var presenter: Presenter<CallScreenState>
|
||||
|
||||
@@ -119,6 +121,9 @@ class ElementCallActivity :
|
||||
CallScreenView(
|
||||
state = state,
|
||||
pipState = pipState,
|
||||
onConsoleMessage = {
|
||||
consoleMessageLogger.log("ElementCall", it)
|
||||
},
|
||||
requestPermissions = { permissions, callback ->
|
||||
requestPermissionCallback = callback
|
||||
requestPermissionsLauncher.launch(permissions)
|
||||
|
||||
@@ -28,7 +28,9 @@ import io.element.android.libraries.core.extensions.runCatchingExceptions
|
||||
import io.element.android.libraries.di.annotations.AppCoroutineScope
|
||||
import io.element.android.libraries.di.annotations.ApplicationContext
|
||||
import io.element.android.libraries.matrix.api.MatrixClientProvider
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.room.BaseRoom
|
||||
import io.element.android.libraries.matrix.ui.media.ImageLoaderHolder
|
||||
import io.element.android.libraries.push.api.notifications.ForegroundServiceType
|
||||
import io.element.android.libraries.push.api.notifications.NotificationIdProvider
|
||||
@@ -39,16 +41,17 @@ import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.mapLatest
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
@@ -180,13 +183,7 @@ class DefaultActiveCallManager(
|
||||
|
||||
val previousActiveCall = activeCall.value ?: return
|
||||
val notificationData = (previousActiveCall.callState as? CallState.Ringing)?.notificationData ?: return
|
||||
activeCall.value = null
|
||||
if (activeWakeLock?.isHeld == true) {
|
||||
Timber.tag(tag).d("Releasing partial wakelock after timeout")
|
||||
activeWakeLock.release()
|
||||
}
|
||||
|
||||
cancelIncomingCallNotification()
|
||||
removeCurrentCall()
|
||||
|
||||
if (displayMissedCallNotification) {
|
||||
displayMissedCallNotification(notificationData)
|
||||
@@ -211,24 +208,16 @@ class DefaultActiveCallManager(
|
||||
?.declineCall(notificationData.eventId)
|
||||
}
|
||||
|
||||
cancelIncomingCallNotification()
|
||||
if (activeWakeLock?.isHeld == true) {
|
||||
Timber.tag(tag).d("Releasing partial wakelock after hang up")
|
||||
activeWakeLock.release()
|
||||
}
|
||||
timedOutCallJob?.cancel()
|
||||
activeCall.value = null
|
||||
removeCurrentCall()
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the current active call and any associated UI, cancelling the timeouts and wakelocks.
|
||||
*/
|
||||
override suspend fun joinedCall(callType: CallType) = mutex.withLock {
|
||||
Timber.tag(tag).d("Joined call: $callType")
|
||||
|
||||
cancelIncomingCallNotification()
|
||||
if (activeWakeLock?.isHeld == true) {
|
||||
Timber.tag(tag).d("Releasing partial wakelock after joining call")
|
||||
activeWakeLock.release()
|
||||
}
|
||||
timedOutCallJob?.cancel()
|
||||
removeCurrentCall()
|
||||
|
||||
activeCall.value = ActiveCall(
|
||||
callType = callType,
|
||||
@@ -236,6 +225,23 @@ class DefaultActiveCallManager(
|
||||
)
|
||||
}
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||
internal fun removeCurrentCall() {
|
||||
// Cancel and remove the timeout call job, if any
|
||||
timedOutCallJob?.cancel()
|
||||
timedOutCallJob = null
|
||||
|
||||
// Remove the active call and cancel the notification
|
||||
activeCall.value = null
|
||||
cancelIncomingCallNotification()
|
||||
|
||||
// Also remove any wake locks that may be held
|
||||
if (activeWakeLock?.isHeld == true) {
|
||||
Timber.tag(tag).d("Releasing partial wakelock after call declined from another session")
|
||||
activeWakeLock.release()
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
private suspend fun showIncomingCallNotification(notificationData: CallNotificationData) {
|
||||
Timber.tag(tag).d("Displaying ringing call notification")
|
||||
@@ -281,73 +287,75 @@ class DefaultActiveCallManager(
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private fun observeRingingCall() {
|
||||
activeCall
|
||||
.filterNotNull()
|
||||
.filter { it.callState is CallState.Ringing && it.callType is CallType.RoomCall }
|
||||
.flatMapLatest { activeCall ->
|
||||
val callType = activeCall.callType as CallType.RoomCall
|
||||
val ringingInfo = activeCall.callState as CallState.Ringing
|
||||
val client = matrixClientProvider.getOrRestore(callType.sessionId).getOrNull() ?: run {
|
||||
Timber.tag(tag).d("Couldn't find session for incoming call: $activeCall")
|
||||
return@flatMapLatest flowOf()
|
||||
}
|
||||
val room = client.getRoom(callType.roomId) ?: run {
|
||||
Timber.tag(tag).d("Couldn't find room for incoming call: $activeCall")
|
||||
return@flatMapLatest flowOf()
|
||||
}
|
||||
val roomForActiveCallFlow: Flow<Pair<BaseRoom, EventId>?> = activeCall.mapLatest { activeCall ->
|
||||
val callType = activeCall?.callType as? CallType.RoomCall ?: return@mapLatest null
|
||||
val ringingInfo = activeCall.callState as? CallState.Ringing ?: return@mapLatest null
|
||||
val client = matrixClientProvider.getOrRestore(callType.sessionId).getOrNull() ?: run {
|
||||
Timber.tag(tag).d("Couldn't find session for incoming call: $activeCall")
|
||||
return@mapLatest null
|
||||
}
|
||||
val room = client.getRoom(callType.roomId) ?: run {
|
||||
Timber.tag(tag).d("Couldn't find room for incoming call: $activeCall")
|
||||
return@mapLatest null
|
||||
}
|
||||
|
||||
Timber.tag(tag).d("Found room for ringing call: ${room.roomId}")
|
||||
Timber.tag(tag).d("Found room for ringing call: ${room.roomId}")
|
||||
|
||||
val eventId = ringingInfo.notificationData.eventId
|
||||
room to eventId
|
||||
}
|
||||
|
||||
roomForActiveCallFlow
|
||||
.flatMapLatest { pair ->
|
||||
val (room, eventId) = pair
|
||||
// This will cancel the previous iteration of flatMapLatest if the active call is now null
|
||||
?: return@flatMapLatest flowOf()
|
||||
|
||||
// If we have declined from another phone we want to stop ringing.
|
||||
room.subscribeToCallDecline(ringingInfo.notificationData.eventId)
|
||||
room.subscribeToCallDecline(eventId)
|
||||
.filter { decliner ->
|
||||
Timber.tag(tag).d("Call: $activeCall was declined by $decliner")
|
||||
// only want to listen if the call was declined from another of my sessions,
|
||||
// (we are ringing for an incoming call in a DM)
|
||||
decliner == client.sessionId
|
||||
decliner == room.sessionId
|
||||
}
|
||||
}
|
||||
.onEach { decliner ->
|
||||
Timber.tag(tag).d("Call: $activeCall was declined by user from another session")
|
||||
// Remove the active call and cancel the notification
|
||||
activeCall.value = null
|
||||
if (activeWakeLock?.isHeld == true) {
|
||||
Timber.tag(tag).d("Releasing partial wakelock after call declined from another session")
|
||||
activeWakeLock.release()
|
||||
}
|
||||
cancelIncomingCallNotification()
|
||||
removeCurrentCall()
|
||||
}
|
||||
.launchIn(coroutineScope)
|
||||
|
||||
// This will observe ringing calls and ensure they're terminated if the room call is cancelled or if the user
|
||||
// has joined the call from another session.
|
||||
activeCall
|
||||
.filterNotNull()
|
||||
.filter { it.callState is CallState.Ringing && it.callType is CallType.RoomCall }
|
||||
.flatMapLatest { activeCall ->
|
||||
val callType = activeCall.callType as CallType.RoomCall
|
||||
// Get a flow of updated `hasRoomCall` and `activeRoomCallParticipants` values for the room
|
||||
val room = matrixClientProvider.getOrRestore(callType.sessionId).getOrNull()?.getRoom(callType.roomId) ?: run {
|
||||
Timber.tag(tag).d("Couldn't find room for incoming call: $activeCall")
|
||||
return@flatMapLatest flowOf()
|
||||
}
|
||||
roomForActiveCallFlow
|
||||
.flatMapLatest { pair ->
|
||||
val (room, _) = pair
|
||||
// This will cancel the previous iteration of flatMapLatest if the active call is now null
|
||||
?: return@flatMapLatest flowOf()
|
||||
|
||||
// We now observe the room info for changes to the active call state and the call participants
|
||||
room.roomInfoFlow.map {
|
||||
Timber.tag(tag).d("Has room call status changed for ringing call: ${it.hasRoomCall}")
|
||||
it.hasRoomCall to (callType.sessionId in it.activeRoomCallParticipants)
|
||||
val participants = it.activeRoomCallParticipants
|
||||
Timber.tag(tag).d("Room call status changed for ringing call | hasRoomCall: ${it.hasRoomCall} | participants: $participants")
|
||||
val userIsInTheCall = room.sessionId in participants
|
||||
it.hasRoomCall to userIsInTheCall
|
||||
}
|
||||
}
|
||||
// We only want to check if the room active call status changes
|
||||
// Filter out duplicate values
|
||||
.distinctUntilChanged()
|
||||
// Skip the first one, we're not interested in it (if the check below passes, it had to be active anyway)
|
||||
.drop(1)
|
||||
.onEach { (roomHasActiveCall, userIsInTheCall) ->
|
||||
if (!roomHasActiveCall) {
|
||||
// The call was cancelled
|
||||
timedOutCallJob?.cancel()
|
||||
incomingCallTimedOut(displayMissedCallNotification = true)
|
||||
val notificationData = (activeCall.value?.callState as? CallState.Ringing)?.notificationData
|
||||
removeCurrentCall()
|
||||
|
||||
if (notificationData != null) {
|
||||
displayMissedCallNotification(notificationData)
|
||||
}
|
||||
} else if (userIsInTheCall) {
|
||||
// The user joined the call from another session
|
||||
timedOutCallJob?.cancel()
|
||||
incomingCallTimedOut(displayMissedCallNotification = false)
|
||||
removeCurrentCall()
|
||||
}
|
||||
}
|
||||
.launchIn(coroutineScope)
|
||||
|
||||
@@ -44,6 +44,13 @@ class WebViewAudioManager(
|
||||
private val coroutineScope: CoroutineScope,
|
||||
private val onInvalidAudioDeviceAdded: (InvalidAudioDeviceReason) -> Unit,
|
||||
) {
|
||||
private val json by lazy {
|
||||
Json {
|
||||
encodeDefaults = true
|
||||
explicitNulls = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to disable bluetooth audio devices. This must be done on Android versions lower than Android 12,
|
||||
* since the WebView approach breaks when using the legacy Bluetooth audio APIs.
|
||||
@@ -308,11 +315,7 @@ class WebViewAudioManager(
|
||||
devices: List<SerializableAudioDevice> = listAudioDevices().map(SerializableAudioDevice::fromAudioDeviceInfo),
|
||||
) {
|
||||
Timber.d("Updating available audio devices")
|
||||
val jsonSerializer = Json {
|
||||
encodeDefaults = true
|
||||
explicitNulls = false
|
||||
}
|
||||
val deviceList = jsonSerializer.encodeToString(devices)
|
||||
val deviceList = json.encodeToString(devices)
|
||||
webView.evaluateJavascript("controls.setAvailableOutputDevices($deviceList);", {
|
||||
Timber.d("Audio: setAvailableOutputDevices result: $it")
|
||||
})
|
||||
|
||||
@@ -7,18 +7,20 @@
|
||||
|
||||
package io.element.android.features.call.impl.utils
|
||||
|
||||
import dev.zacsweers.metro.Inject
|
||||
import io.element.android.features.call.impl.data.WidgetMessage
|
||||
import io.element.android.libraries.androidutils.json.JsonProvider
|
||||
import io.element.android.libraries.core.extensions.runCatchingExceptions
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
object WidgetMessageSerializer {
|
||||
private val coder = Json { ignoreUnknownKeys = true }
|
||||
|
||||
@Inject
|
||||
class WidgetMessageSerializer(
|
||||
private val json: JsonProvider,
|
||||
) {
|
||||
fun deserialize(message: String): Result<WidgetMessage> {
|
||||
return runCatchingExceptions { coder.decodeFromString(WidgetMessage.serializer(), message) }
|
||||
return runCatchingExceptions { json().decodeFromString(WidgetMessage.serializer(), message) }
|
||||
}
|
||||
|
||||
fun serialize(message: WidgetMessage): String {
|
||||
return coder.encodeToString(WidgetMessage.serializer(), message)
|
||||
return json().encodeToString(WidgetMessage.serializer(), message)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,9 +16,11 @@ import io.element.android.features.call.api.CallType
|
||||
import io.element.android.features.call.impl.ui.CallScreenEvents
|
||||
import io.element.android.features.call.impl.ui.CallScreenNavigator
|
||||
import io.element.android.features.call.impl.ui.CallScreenPresenter
|
||||
import io.element.android.features.call.impl.utils.WidgetMessageSerializer
|
||||
import io.element.android.features.call.utils.FakeActiveCallManager
|
||||
import io.element.android.features.call.utils.FakeCallWidgetProvider
|
||||
import io.element.android.features.call.utils.FakeWidgetMessageInterceptor
|
||||
import io.element.android.libraries.androidutils.json.DefaultJsonProvider
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.matrix.api.sync.SyncState
|
||||
@@ -50,7 +52,8 @@ import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class) class CallScreenPresenterTest {
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class CallScreenPresenterTest {
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
@@ -409,6 +412,7 @@ import kotlin.time.Duration.Companion.seconds
|
||||
languageTagProvider = FakeLanguageTagProvider("en-US"),
|
||||
appForegroundStateService = appForegroundStateService,
|
||||
appCoroutineScope = backgroundScope,
|
||||
widgetMessageSerializer = WidgetMessageSerializer(DefaultJsonProvider()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import io.element.android.libraries.matrix.test.AN_EVENT_ID_2
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID_2
|
||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||
import io.element.android.libraries.matrix.test.A_USER_ID_2
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClientProvider
|
||||
import io.element.android.libraries.matrix.test.room.FakeBaseRoom
|
||||
@@ -46,6 +47,7 @@ import io.element.android.tests.testutils.lambda.value
|
||||
import io.element.android.tests.testutils.plantTestTimber
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.mockk
|
||||
import io.mockk.spyk
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
@@ -331,6 +333,49 @@ class DefaultActiveCallManagerTest {
|
||||
assertThat(manager.activeCall.value).isNull()
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Test
|
||||
fun `observeRingingCalls - declining won't do anything if the call was already cancelled`() = runTest {
|
||||
val room = FakeBaseRoom().apply {
|
||||
givenRoomInfo(aRoomInfo())
|
||||
}
|
||||
val client = FakeMatrixClient().apply {
|
||||
givenGetRoomResult(A_ROOM_ID, room)
|
||||
}
|
||||
val matrixClientProvider = FakeMatrixClientProvider(getClient = { Result.success(client) })
|
||||
val notificationManagerCompat = mockk<NotificationManagerCompat>(relaxed = true)
|
||||
val manager = spyk<DefaultActiveCallManager>(
|
||||
createActiveCallManager(
|
||||
matrixClientProvider = matrixClientProvider,
|
||||
notificationManagerCompat = notificationManagerCompat,
|
||||
)
|
||||
)
|
||||
|
||||
manager.registerIncomingCall(aCallNotificationData())
|
||||
|
||||
// Call is active (the other user join the call)
|
||||
room.givenRoomInfo(aRoomInfo(hasRoomCall = true))
|
||||
advanceTimeBy(1)
|
||||
|
||||
// Call is cancelled by us, hanging up
|
||||
manager.hungUpCall(CallType.RoomCall(A_SESSION_ID, A_ROOM_ID))
|
||||
advanceTimeBy(1)
|
||||
|
||||
verify(exactly = 1) { notificationManagerCompat.cancel(any()) }
|
||||
verify(exactly = 1) { manager.removeCurrentCall() }
|
||||
assertThat(manager.activeCall.value).isNull()
|
||||
assertThat(manager.activeWakeLock?.isHeld).isNull()
|
||||
|
||||
// Simulate that another user declined the call
|
||||
room.givenDecliner(A_USER_ID_2, AN_EVENT_ID)
|
||||
advanceTimeBy(1)
|
||||
|
||||
// Check everything stays the same, no extra call to cancelling notifications
|
||||
verify(exactly = 1) { notificationManagerCompat.cancel(any()) }
|
||||
verify(exactly = 1) { manager.removeCurrentCall() }
|
||||
assertThat(manager.activeWakeLock?.isHeld).isNull()
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Test
|
||||
fun `observeRingingCalls - will do nothing if either the session or the room are not found`() = runTest {
|
||||
|
||||
@@ -17,22 +17,22 @@
|
||||
<string name="screen_room_change_role_administrators_title">"ویرایش مدیران"</string>
|
||||
<string name="screen_room_change_role_confirm_add_admin_description">"قادر نخواهید بود این کنش را بازکردانید. داردید کاربر را به سطح قدرت خودتان ارتقا میدهید."</string>
|
||||
<string name="screen_room_change_role_confirm_add_admin_title">"افزودن مدیر؟"</string>
|
||||
<string name="screen_room_change_role_confirm_change_owners_title">"انتقال مالکیت؟"</string>
|
||||
<string name="screen_room_change_role_confirm_demote_self_action">"تنزل بده"</string>
|
||||
<string name="screen_room_change_role_confirm_demote_self_description">"شما نمیتوانید این تغییر را بازگردانید زیرا در حال تنزل نقش خود در اتاق هستید، اگر آخرین کاربر ممتاز در اتاق باشید، امکان دستیابی مجدد به دسترسیهای سطح بالای اتاق غیرممکن است."</string>
|
||||
<string name="screen_room_change_role_confirm_demote_self_title">"تنزل نقش شما در اتاق؟"</string>
|
||||
<string name="screen_room_change_role_invited_member_name">"%1$s (منتظر)"</string>
|
||||
<string name="screen_room_change_role_invited_member_name_android">"(منتظر)"</string>
|
||||
<string name="screen_room_change_role_moderators_admin_section_footer">"مدیران به صورت خودکار اجازههای نظارتی را دارند"</string>
|
||||
<string name="screen_room_change_role_moderators_owner_section_footer">"ماکان به صورت خودکار اجازههای مدیریتی را دارند."</string>
|
||||
<string name="screen_room_change_role_moderators_title">"ویرایش ناظران"</string>
|
||||
<string name="screen_room_change_role_owners_title">"گزینش مالکان"</string>
|
||||
<string name="screen_room_change_role_section_administrators">"مدیران"</string>
|
||||
<string name="screen_room_change_role_section_moderators">"ناظمها"</string>
|
||||
<string name="screen_room_change_role_section_users">"اعضا"</string>
|
||||
<string name="screen_room_change_role_unsaved_changes_description">"تغییراتی ذخیره نشده دارید."</string>
|
||||
<string name="screen_room_change_role_unsaved_changes_title">"ذخیرهٔ تغییرات؟"</string>
|
||||
<plurals name="screen_room_member_list_header_title">
|
||||
<item quantity="one">"%1$d نفر"</item>
|
||||
<item quantity="other">"%1$d نفر"</item>
|
||||
</plurals>
|
||||
<string name="screen_room_member_list_banned_empty">"هیچ کاربر محرومی در این اتاق نیست."</string>
|
||||
<string name="screen_room_member_list_manage_member_remove_confirmation_ban">"برداشت و تحریم عضو"</string>
|
||||
<string name="screen_room_member_list_manage_member_remove_confirmation_kick">"تنها برداشتن عضو"</string>
|
||||
<string name="screen_room_member_list_manage_member_unban_action">"رفع انسداد"</string>
|
||||
@@ -47,12 +47,14 @@
|
||||
<string name="screen_room_member_list_room_members_header_title">"اعضای اتاق"</string>
|
||||
<string name="screen_room_member_list_unbanning_user">"رفع تحریم %1$s"</string>
|
||||
<string name="screen_room_roles_and_permissions_admins">"مدیران"</string>
|
||||
<string name="screen_room_roles_and_permissions_admins_and_owners">"مدیران و مالکان"</string>
|
||||
<string name="screen_room_roles_and_permissions_change_my_role">"تغییر نقشم"</string>
|
||||
<string name="screen_room_roles_and_permissions_change_role_demote_to_member">"تنزّل به عضو"</string>
|
||||
<string name="screen_room_roles_and_permissions_change_role_demote_to_moderator">"تنزّل به ناظم"</string>
|
||||
<string name="screen_room_roles_and_permissions_member_moderation">"نظارت اعضا"</string>
|
||||
<string name="screen_room_roles_and_permissions_messages_and_content">"پیامها و محتوا"</string>
|
||||
<string name="screen_room_roles_and_permissions_moderators">"ناظمها"</string>
|
||||
<string name="screen_room_roles_and_permissions_owners">"مالکان"</string>
|
||||
<string name="screen_room_roles_and_permissions_permissions_header">"اجازهها"</string>
|
||||
<string name="screen_room_roles_and_permissions_reset">"بازنشانی اجازهها"</string>
|
||||
<string name="screen_room_roles_and_permissions_reset_confirm_title">"بازنشانی اجازهها؟"</string>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_room_change_permissions_administrators">"Tylko administratorzy"</string>
|
||||
<string name="screen_room_change_permissions_ban_people">"Banowanie osób"</string>
|
||||
<string name="screen_room_change_permissions_delete_messages">"Usuwanie wiadomości"</string>
|
||||
<string name="screen_room_change_permissions_delete_messages">"Usuń wiadomości"</string>
|
||||
<string name="screen_room_change_permissions_everyone">"Wszyscy"</string>
|
||||
<string name="screen_room_change_permissions_invite_people">"Zapraszanie osób i akceptowanie próśb o dołączenie"</string>
|
||||
<string name="screen_room_change_permissions_member_moderation">"Moderacja członków"</string>
|
||||
|
||||
@@ -17,6 +17,7 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.core.net.toUri
|
||||
import dev.zacsweers.metro.Inject
|
||||
import im.vector.app.features.analytics.plan.CreatedRoom
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
@@ -157,7 +158,7 @@ class ConfigureRoomPresenter(
|
||||
createRoomAction: MutableState<AsyncAction<RoomId>>
|
||||
) = launch {
|
||||
suspend {
|
||||
val avatarUrl = config.avatarUri?.let { uploadAvatar(it) }
|
||||
val avatarUrl = config.avatarUri?.let { uploadAvatar(it.toUri()) }
|
||||
val params = if (config.roomVisibility is RoomVisibilityState.Public) {
|
||||
CreateRoomParameters(
|
||||
name = config.roomName,
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
package io.element.android.features.createroom.impl.configureroom
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -192,7 +191,7 @@ private fun ConfigureRoomToolbar(
|
||||
|
||||
@Composable
|
||||
private fun RoomNameWithAvatar(
|
||||
avatarUri: Uri?,
|
||||
avatarUri: String?,
|
||||
roomName: String,
|
||||
onAvatarClick: () -> Unit,
|
||||
onChangeRoomName: (String) -> Unit,
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
package io.element.android.features.createroom.impl.configureroom
|
||||
|
||||
import android.net.Uri
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
@@ -15,7 +14,7 @@ import kotlinx.collections.immutable.persistentListOf
|
||||
data class CreateRoomConfig(
|
||||
val roomName: String? = null,
|
||||
val topic: String? = null,
|
||||
val avatarUri: Uri? = null,
|
||||
val avatarUri: String? = null,
|
||||
val invites: ImmutableList<MatrixUser> = persistentListOf(),
|
||||
val roomVisibility: RoomVisibilityState = RoomVisibilityState.Private,
|
||||
)
|
||||
|
||||
@@ -62,7 +62,7 @@ class CreateRoomConfigStore(
|
||||
fun setAvatarUri(uri: Uri?, cached: Boolean = false) {
|
||||
cachedAvatarUri = uri.takeIf { cached }
|
||||
createRoomConfigFlow.getAndUpdate { config ->
|
||||
config.copy(avatarUri = uri)
|
||||
config.copy(avatarUri = uri?.toString())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
package io.element.android.features.startchat.impl.configureroom
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.core.net.toUri
|
||||
import app.cash.turbine.TurbineTestContext
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import im.vector.app.features.analytics.plan.CreatedRoom
|
||||
@@ -155,15 +156,15 @@ class ConfigureRoomPresenterTest {
|
||||
// Pick avatar
|
||||
pickerProvider.givenResult(null)
|
||||
// From gallery
|
||||
val uriFromGallery = Uri.parse(AN_URI_FROM_GALLERY)
|
||||
pickerProvider.givenResult(uriFromGallery)
|
||||
val uriFromGallery = AN_URI_FROM_GALLERY
|
||||
pickerProvider.givenResult(uriFromGallery.toUri())
|
||||
newState.eventSink(ConfigureRoomEvents.HandleAvatarAction(AvatarAction.ChoosePhoto))
|
||||
newState = awaitItem()
|
||||
expectedConfig = expectedConfig.copy(avatarUri = uriFromGallery)
|
||||
assertThat(newState.config).isEqualTo(expectedConfig)
|
||||
// From camera
|
||||
val uriFromCamera = Uri.parse(AN_URI_FROM_CAMERA)
|
||||
pickerProvider.givenResult(uriFromCamera)
|
||||
val uriFromCamera = AN_URI_FROM_CAMERA
|
||||
pickerProvider.givenResult(uriFromCamera.toUri())
|
||||
assertThat(newState.cameraPermissionState.permissionGranted).isFalse()
|
||||
newState.eventSink(ConfigureRoomEvents.HandleAvatarAction(AvatarAction.TakePhoto))
|
||||
newState = awaitItem()
|
||||
@@ -175,8 +176,8 @@ class ConfigureRoomPresenterTest {
|
||||
expectedConfig = expectedConfig.copy(avatarUri = uriFromCamera)
|
||||
assertThat(newState.config).isEqualTo(expectedConfig)
|
||||
// Do it again, no permission is requested
|
||||
val uriFromCamera2 = Uri.parse(AN_URI_FROM_CAMERA_2)
|
||||
pickerProvider.givenResult(uriFromCamera2)
|
||||
val uriFromCamera2 = AN_URI_FROM_CAMERA_2
|
||||
pickerProvider.givenResult(uriFromCamera2.toUri())
|
||||
newState.eventSink(ConfigureRoomEvents.HandleAvatarAction(AvatarAction.TakePhoto))
|
||||
newState = awaitItem()
|
||||
expectedConfig = expectedConfig.copy(avatarUri = uriFromCamera2)
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_deactivate_account_list_item_3">"Delete your account information from our server."</string>
|
||||
</resources>
|
||||
@@ -5,7 +5,7 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
plugins {
|
||||
id("io.element.android-library")
|
||||
id("io.element.android-compose-library")
|
||||
}
|
||||
|
||||
android {
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
|
||||
package io.element.android.features.enterprise.api
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import io.element.android.compound.tokens.generated.SemanticColors
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@@ -17,8 +19,17 @@ interface EnterpriseService {
|
||||
fun defaultHomeserverList(): List<String>
|
||||
suspend fun isAllowedToConnectToHomeserver(homeserverUrl: String): Boolean
|
||||
|
||||
fun semanticColorsLight(): SemanticColors
|
||||
fun semanticColorsDark(): SemanticColors
|
||||
/**
|
||||
* Override the brand color.
|
||||
* @param brandColor the color in hex format (#RRGGBBAA or #RRGGBB), or null to reset to default.
|
||||
*/
|
||||
fun overrideBrandColor(brandColor: String?)
|
||||
|
||||
@Composable
|
||||
fun semanticColorsLight(): State<SemanticColors>
|
||||
|
||||
@Composable
|
||||
fun semanticColorsDark(): State<SemanticColors>
|
||||
|
||||
fun firebasePushGateway(): String?
|
||||
fun unifiedPushDefaultPushGateway(): String?
|
||||
|
||||
@@ -8,7 +8,7 @@ import extension.testCommonDependencies
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
plugins {
|
||||
id("io.element.android-library")
|
||||
id("io.element.android-compose-library")
|
||||
}
|
||||
|
||||
android {
|
||||
|
||||
@@ -7,6 +7,10 @@
|
||||
|
||||
package io.element.android.features.enterprise.impl
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import dev.zacsweers.metro.Inject
|
||||
@@ -28,9 +32,17 @@ class DefaultEnterpriseService : EnterpriseService {
|
||||
override fun defaultHomeserverList(): List<String> = emptyList()
|
||||
override suspend fun isAllowedToConnectToHomeserver(homeserverUrl: String) = true
|
||||
|
||||
override fun semanticColorsLight(): SemanticColors = compoundColorsLight
|
||||
override fun overrideBrandColor(brandColor: String?) = Unit
|
||||
|
||||
override fun semanticColorsDark(): SemanticColors = compoundColorsDark
|
||||
@Composable
|
||||
override fun semanticColorsLight(): State<SemanticColors> {
|
||||
return remember { derivedStateOf { compoundColorsLight } }
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun semanticColorsDark(): State<SemanticColors> {
|
||||
return remember { derivedStateOf { compoundColorsDark } }
|
||||
}
|
||||
|
||||
override fun firebasePushGateway(): String? = null
|
||||
override fun unifiedPushDefaultPushGateway(): String? = null
|
||||
|
||||
@@ -7,7 +7,12 @@
|
||||
|
||||
package io.element.android.features.enterprise.impl
|
||||
|
||||
import app.cash.molecule.RecompositionMode
|
||||
import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.compound.tokens.generated.compoundColorsDark
|
||||
import io.element.android.compound.tokens.generated.compoundColorsLight
|
||||
import io.element.android.libraries.matrix.test.A_HOMESERVER_URL
|
||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||
import kotlinx.coroutines.test.runTest
|
||||
@@ -37,4 +42,30 @@ class DefaultEnterpriseServiceTest {
|
||||
val defaultEnterpriseService = DefaultEnterpriseService()
|
||||
assertThat(defaultEnterpriseService.isEnterpriseUser(A_SESSION_ID)).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `semanticColorsLight always emits the same value`() = runTest {
|
||||
val defaultEnterpriseService = DefaultEnterpriseService()
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
defaultEnterpriseService.semanticColorsLight().value
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState).isEqualTo(compoundColorsLight)
|
||||
defaultEnterpriseService.overrideBrandColor("#87654321")
|
||||
expectNoEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `semanticColorsDark always emits the same value`() = runTest {
|
||||
val defaultEnterpriseService = DefaultEnterpriseService()
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
defaultEnterpriseService.semanticColorsDark().value
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState).isEqualTo(compoundColorsDark)
|
||||
defaultEnterpriseService.overrideBrandColor("#87654321")
|
||||
expectNoEvents()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
|
||||
package io.element.android.features.enterprise.test
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import io.element.android.compound.tokens.generated.SemanticColors
|
||||
import io.element.android.features.enterprise.api.BugReportUrl
|
||||
import io.element.android.features.enterprise.api.EnterpriseService
|
||||
@@ -22,8 +24,9 @@ class FakeEnterpriseService(
|
||||
private val isEnterpriseUserResult: (SessionId) -> Boolean = { lambdaError() },
|
||||
private val defaultHomeserverListResult: () -> List<String> = { emptyList() },
|
||||
private val isAllowedToConnectToHomeserverResult: (String) -> Boolean = { lambdaError() },
|
||||
private val semanticColorsLightResult: () -> SemanticColors = { lambdaError() },
|
||||
private val semanticColorsDarkResult: () -> SemanticColors = { lambdaError() },
|
||||
private val semanticColorsLightResult: () -> State<SemanticColors> = { lambdaError() },
|
||||
private val semanticColorsDarkResult: () -> State<SemanticColors> = { lambdaError() },
|
||||
private val overrideBrandColorResult: (String?) -> Unit = { lambdaError() },
|
||||
private val firebasePushGatewayResult: () -> String? = { lambdaError() },
|
||||
private val unifiedPushDefaultPushGatewayResult: () -> String? = { lambdaError() },
|
||||
) : EnterpriseService {
|
||||
@@ -39,11 +42,17 @@ class FakeEnterpriseService(
|
||||
isAllowedToConnectToHomeserverResult(homeserverUrl)
|
||||
}
|
||||
|
||||
override fun semanticColorsLight(): SemanticColors {
|
||||
override fun overrideBrandColor(brandColor: String?) {
|
||||
overrideBrandColorResult(brandColor)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun semanticColorsLight(): State<SemanticColors> {
|
||||
return semanticColorsLightResult()
|
||||
}
|
||||
|
||||
override fun semanticColorsDark(): SemanticColors {
|
||||
@Composable
|
||||
override fun semanticColorsDark(): State<SemanticColors> {
|
||||
return semanticColorsDarkResult()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_identity_confirmation_create_new_recovery_key">"Create a new backup password"</string>
|
||||
<string name="screen_identity_confirmation_subtitle">"Confirm this device to set up secure messaging."</string>
|
||||
<string name="screen_identity_confirmation_title">"Confirm it\'s you"</string>
|
||||
<string name="screen_identity_confirmation_use_recovery_key">"Use backup password"</string>
|
||||
<string name="screen_identity_confirmed_title">"Device confirmed"</string>
|
||||
<string name="screen_session_verification_enter_recovery_key">"Enter backup password"</string>
|
||||
</resources>
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
package io.element.android.features.home.impl
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import io.element.android.features.home.impl.roomlist.RoomListState
|
||||
import io.element.android.features.home.impl.spaces.HomeSpacesState
|
||||
import io.element.android.features.logout.api.direct.DirectLogoutState
|
||||
@@ -15,7 +14,6 @@ import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@Immutable
|
||||
data class HomeState(
|
||||
/**
|
||||
* The current user of this session, in case of multiple accounts, will contains 3 items, with the
|
||||
|
||||
@@ -19,6 +19,7 @@ import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
@@ -34,6 +35,7 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -48,6 +50,9 @@ import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.testtags.TestTags
|
||||
import io.element.android.libraries.testtags.testTag
|
||||
|
||||
/**
|
||||
* Ref: https://www.figma.com/design/G1xy0HDZKJf5TCRFmKb5d5/Compound-Android-Components?node-id=2191-606
|
||||
*/
|
||||
@Composable
|
||||
fun RoomListFiltersView(
|
||||
state: RoomListFiltersState,
|
||||
@@ -143,9 +148,12 @@ private fun RoomListClearFiltersButton(
|
||||
.clip(CircleShape)
|
||||
.background(ElementTheme.colors.bgActionPrimaryRest)
|
||||
.clickable(onClick = onClick)
|
||||
.padding(4.dp)
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
modifier = Modifier
|
||||
.align(Alignment.Center)
|
||||
.size(16.dp),
|
||||
imageVector = CompoundIcons.Close(),
|
||||
tint = ElementTheme.colors.iconOnSolidPrimary,
|
||||
contentDescription = stringResource(id = R.string.screen_roomlist_clear_filters),
|
||||
@@ -170,21 +178,34 @@ private fun RoomListFilterView(
|
||||
animationSpec = spring(stiffness = Spring.StiffnessMediumLow),
|
||||
label = "chip text colour",
|
||||
)
|
||||
val borderColour = animateColorAsState(
|
||||
targetValue = if (selected) Color.Transparent else ElementTheme.colors.borderInteractiveSecondary,
|
||||
animationSpec = spring(stiffness = Spring.StiffnessMediumLow),
|
||||
label = "chip border colour",
|
||||
)
|
||||
|
||||
FilterChip(
|
||||
selected = selected,
|
||||
onClick = { onClick(roomListFilter) },
|
||||
modifier = modifier.height(36.dp),
|
||||
modifier = modifier.height(32.dp),
|
||||
shape = CircleShape,
|
||||
colors = FilterChipDefaults.filterChipColors(
|
||||
containerColor = background.value,
|
||||
selectedContainerColor = background.value,
|
||||
labelColor = textColour.value,
|
||||
selectedLabelColor = textColour.value
|
||||
selectedLabelColor = textColour.value,
|
||||
),
|
||||
label = {
|
||||
Text(text = stringResource(id = roomListFilter.stringResource))
|
||||
}
|
||||
Text(
|
||||
text = stringResource(id = roomListFilter.stringResource),
|
||||
style = ElementTheme.typography.fontBodyMdRegular,
|
||||
)
|
||||
},
|
||||
border = FilterChipDefaults.filterChipBorder(
|
||||
enabled = true,
|
||||
selected = selected,
|
||||
borderColor = borderColour.value,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -192,6 +213,7 @@ private fun RoomListFilterView(
|
||||
@Composable
|
||||
internal fun RoomListFiltersViewPreview(@PreviewParameter(RoomListFiltersStateProvider::class) state: RoomListFiltersState) = ElementPreview {
|
||||
RoomListFiltersView(
|
||||
modifier = Modifier.padding(vertical = 4.dp),
|
||||
state = state,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ import io.element.android.libraries.push.api.battery.BatteryOptimizationState
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.ImmutableSet
|
||||
|
||||
@Immutable
|
||||
data class RoomListState(
|
||||
val contextMenu: ContextMenu,
|
||||
val declineInviteMenu: DeclineInviteMenu,
|
||||
|
||||
@@ -24,6 +24,7 @@ import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -33,6 +34,8 @@ import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.TextRange
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
@@ -41,7 +44,6 @@ import io.element.android.features.home.impl.contentType
|
||||
import io.element.android.features.home.impl.model.RoomListRoomSummary
|
||||
import io.element.android.features.home.impl.roomlist.RoomListEvents
|
||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||
import io.element.android.libraries.designsystem.components.form.textFieldState
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.components.FilledTextField
|
||||
@@ -111,18 +113,18 @@ private fun RoomListSearchContent(
|
||||
},
|
||||
navigationIcon = { BackButton(onClick = ::onBackButtonClick) },
|
||||
title = {
|
||||
var filter by textFieldState(state.query)
|
||||
var value by remember { mutableStateOf(TextFieldValue(state.query)) }
|
||||
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
FilledTextField(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.focusRequester(focusRequester),
|
||||
value = filter,
|
||||
value = value,
|
||||
singleLine = true,
|
||||
onValueChange = {
|
||||
filter = it
|
||||
state.eventSink(RoomListSearchEvents.QueryChanged(it))
|
||||
value = it
|
||||
state.eventSink(RoomListSearchEvents.QueryChanged(it.text))
|
||||
},
|
||||
colors = TextFieldDefaults.colors(
|
||||
focusedContainerColor = Color.Transparent,
|
||||
@@ -134,7 +136,7 @@ private fun RoomListSearchContent(
|
||||
errorIndicatorColor = Color.Transparent,
|
||||
),
|
||||
trailingIcon = {
|
||||
if (filter.isNotEmpty()) {
|
||||
if (value.text.isNotEmpty()) {
|
||||
IconButton(onClick = {
|
||||
state.eventSink(RoomListSearchEvents.ClearQuery)
|
||||
}) {
|
||||
@@ -148,7 +150,11 @@ private fun RoomListSearchContent(
|
||||
)
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
focusRequester.requestFocus()
|
||||
value = value.copy(selection = TextRange(value.text.length))
|
||||
if (!focusRequester.restoreFocusedChild()) {
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
focusRequester.saveFocusedChild()
|
||||
}
|
||||
},
|
||||
windowInsets = TopAppBarDefaults.windowInsets.copy(top = 0)
|
||||
|
||||
@@ -16,7 +16,9 @@ import io.element.android.features.invite.api.SeenInvitesStore
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.ui.safety.rememberHideInvitesAvatar
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.persistentSetOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableSet
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
@@ -28,7 +30,10 @@ class HomeSpacesPresenter(
|
||||
@Composable
|
||||
override fun present(): HomeSpacesState {
|
||||
val hideInvitesAvatar by client.rememberHideInvitesAvatar()
|
||||
val spaceRooms by client.spaceService.spaceRoomsFlow.collectAsState(emptyList())
|
||||
val spaceRooms by remember {
|
||||
client.spaceService.spaceRoomsFlow.map { it.toImmutableList() }
|
||||
}.collectAsState(persistentListOf())
|
||||
|
||||
val seenSpaceInvites by remember {
|
||||
seenInvitesStore.seenRoomIds().map { it.toImmutableSet() }
|
||||
}.collectAsState(persistentSetOf())
|
||||
|
||||
@@ -9,11 +9,12 @@ package io.element.android.features.home.impl.spaces
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.ImmutableSet
|
||||
|
||||
data class HomeSpacesState(
|
||||
val space: CurrentSpace,
|
||||
val spaceRooms: List<SpaceRoom>,
|
||||
val spaceRooms: ImmutableList<SpaceRoom>,
|
||||
val seenSpaceInvites: ImmutableSet<RoomId>,
|
||||
val hideInvitesAvatar: Boolean,
|
||||
val eventSink: (HomeSpacesEvents) -> Unit,
|
||||
|
||||
@@ -11,6 +11,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
|
||||
import io.element.android.libraries.previewutils.room.aSpaceRoom
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableSet
|
||||
|
||||
open class HomeSpacesStateProvider : PreviewParameterProvider<HomeSpacesState> {
|
||||
@@ -39,7 +40,7 @@ internal fun aHomeSpacesState(
|
||||
eventSink: (HomeSpacesEvents) -> Unit = {},
|
||||
) = HomeSpacesState(
|
||||
space = space,
|
||||
spaceRooms = spaceRooms,
|
||||
spaceRooms = spaceRooms.toImmutableList(),
|
||||
seenSpaceInvites = seenSpaceInvites.toImmutableSet(),
|
||||
hideInvitesAvatar = hideInvitesAvatar,
|
||||
eventSink = eventSink,
|
||||
|
||||
@@ -14,6 +14,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||
import io.element.android.libraries.matrix.ui.components.SpaceHeaderRootView
|
||||
@@ -47,6 +48,9 @@ fun HomeSpacesView(
|
||||
)
|
||||
}
|
||||
}
|
||||
item {
|
||||
HorizontalDivider()
|
||||
}
|
||||
state.spaceRooms.forEach { spaceRoom ->
|
||||
item(spaceRoom.roomId) {
|
||||
val isInvitation = spaceRoom.state == CurrentUserMembership.INVITED
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<string name="full_screen_intent_banner_message">"For at sikre, at du aldrig går glip af et vigtigt opkald, skal du ændre dine indstillinger til at tillade underretninger i fuld skærm, når din telefon er låst."</string>
|
||||
<string name="full_screen_intent_banner_title">"Gør din opkaldsoplevelse bedre"</string>
|
||||
<string name="screen_home_tab_chats">"Samtaler"</string>
|
||||
<string name="screen_home_tab_spaces">"Klynger"</string>
|
||||
<string name="screen_home_tab_spaces">"Grupper"</string>
|
||||
<string name="screen_invites_decline_chat_message">"Er du sikker på, at du vil afvise invitationen til at deltage i %1$s?"</string>
|
||||
<string name="screen_invites_decline_chat_title">"Afvis invitation"</string>
|
||||
<string name="screen_invites_decline_direct_chat_message">"Er du sikker på, at du vil afvise denne private samtale med %1$s?"</string>
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="banner_set_up_recovery_content">"Restore your account security and message history with a backup password if you have lost all your existing devices."</string>
|
||||
<string name="banner_set_up_recovery_submit">"Set up backup"</string>
|
||||
<string name="banner_set_up_recovery_title">"Set up backup to protect your account"</string>
|
||||
<string name="confirm_recovery_key_banner_message">"Confirm your backup password to maintain access to your message backup and message history."</string>
|
||||
<string name="confirm_recovery_key_banner_primary_button_title">"Enter your backup password"</string>
|
||||
<string name="confirm_recovery_key_banner_secondary_button_title">"Forgot your backup password?"</string>
|
||||
<string name="confirm_recovery_key_banner_title">"Your message backup is out of sync"</string>
|
||||
<string name="session_verification_banner_message">"Looks like you\'re using a new device. Confirm it with another linked device to access your encrypted messages."</string>
|
||||
</resources>
|
||||
@@ -3,6 +3,8 @@
|
||||
<string name="banner_battery_optimization_content_android">"Kui tahad olla kindel, et näed õigel ajal kõiki teavitusi, siis palun lülita akukasutuse optimeerimine välja."</string>
|
||||
<string name="banner_battery_optimization_submit_android">"Lülita akukasutuse optimeerimine välja"</string>
|
||||
<string name="banner_battery_optimization_title_android">"Sa ei näe kõiki teavitusi?"</string>
|
||||
<string name="banner_new_sound_message">"Sinu nutiseadme teavituste heli on uuenenud - see on nüüd selgem, kiirem ja vähem häiriv."</string>
|
||||
<string name="banner_new_sound_title">"Oleme sinu helisid värskendanud"</string>
|
||||
<string name="banner_set_up_recovery_content">"Loo uus taastevõti, mida saad kasutada oma krüptitud sõnumite ajaloo taastamisel olukorras, kus kaotad ligipääsu oma seadmetele."</string>
|
||||
<string name="banner_set_up_recovery_submit">"Seadista andmete taastamine"</string>
|
||||
<string name="banner_set_up_recovery_title">"Seadista taastamine"</string>
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
<string name="confirm_recovery_key_banner_title">"ذخیرهساز کلیدتان از همگام بودن در آمده"</string>
|
||||
<string name="full_screen_intent_banner_title">"بهبود تجریهٔ تماستان"</string>
|
||||
<string name="screen_home_tab_chats">"گپها"</string>
|
||||
<string name="screen_home_tab_spaces">"فضاها"</string>
|
||||
<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>
|
||||
@@ -19,6 +20,7 @@
|
||||
<string name="screen_migration_message">"فرایندی یک باره است. ممنون از شکیباییتان."</string>
|
||||
<string name="screen_migration_title">"برپایی حسابتان."</string>
|
||||
<string name="screen_roomlist_a11y_create_message">"ایجاد اتاق یا گفتوگویی جدید"</string>
|
||||
<string name="screen_roomlist_clear_filters">"پاک کردن پالایهها"</string>
|
||||
<string name="screen_roomlist_empty_message">"آغاز با پیام دادن به کسی."</string>
|
||||
<string name="screen_roomlist_empty_title">"هنوز گپی وجود ندارد."</string>
|
||||
<string name="screen_roomlist_filter_favourites">"علاقهمندیها"</string>
|
||||
@@ -39,6 +41,7 @@
|
||||
<string name="screen_roomlist_main_space_title">"گپها"</string>
|
||||
<string name="screen_roomlist_mark_as_read">"علامتگذاری به عنوان خوانده شده"</string>
|
||||
<string name="screen_roomlist_mark_as_unread">"نشان به ناخوانده"</string>
|
||||
<string name="screen_roomlist_tombstoned_room_description">"این اتاق ارتقا یافته"</string>
|
||||
<string name="session_verification_banner_message">"گویا از افزارهای جدید استفاده میکنید. تأیید با افزارهای دیگر برای دسترسی به پیامهای رمزنگاری شدهتان."</string>
|
||||
<string name="session_verification_banner_title">"تأیید کنید که خودتانید"</string>
|
||||
</resources>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<string name="banner_battery_optimization_content_android">"Kapcsolja ki az alkalmazás akkumulátoroptimalizálását, hogy biztosan megkapja az összes értesítést."</string>
|
||||
<string name="banner_battery_optimization_submit_android">"Optimalizálás letiltása"</string>
|
||||
<string name="banner_battery_optimization_title_android">"Nem érkeznek meg az értesítések?"</string>
|
||||
<string name="banner_new_sound_message">"Értesítési hangja frissült – tisztább, gyorsabb és kevésbé zavaró lett."</string>
|
||||
<string name="banner_set_up_recovery_content">"Hozzon létre egy új helyreállítási kulcsot, amellyel visszaállíthatja a titkosított üzenetek előzményeit, ha elveszíti az eszközökhöz való hozzáférést."</string>
|
||||
<string name="banner_set_up_recovery_submit">"Helyreállítás beállítása"</string>
|
||||
<string name="banner_set_up_recovery_title">"Helyreállítás beállítása a fiókja védelméhez"</string>
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
<string name="banner_battery_optimization_content_android">"Wyłącz optymalizację baterii dla tej aplikacji, aby upewnić się, że wszystkie powiadomienia są odbierane."</string>
|
||||
<string name="banner_battery_optimization_submit_android">"Wyłącz optymalizację"</string>
|
||||
<string name="banner_battery_optimization_title_android">"Powiadomienia nie dochodzą?"</string>
|
||||
<string name="banner_new_sound_message">"Sygnał powiadomień został zaktualizowany — jest wyraźniejszy, szybszy i mniej uciążliwy."</string>
|
||||
<string name="banner_new_sound_title">"Odświeżyliśmy Twoje dźwięki"</string>
|
||||
<string name="banner_set_up_recovery_content">"Wygeneruj nowy klucz przywracania, którego można użyć do przywrócenia historii wiadomości szyfrowanych w przypadku utraty dostępu do swoich urządzeń."</string>
|
||||
<string name="banner_set_up_recovery_submit">"Skonfiguruj przywracanie"</string>
|
||||
<string name="banner_set_up_recovery_title">"Skonfiguruj przywracanie"</string>
|
||||
@@ -13,6 +15,7 @@
|
||||
<string name="full_screen_intent_banner_message">"Upewnij się, że nie pominiesz żadnego połączenia. Zmień swoje ustawienia i zezwól na powiadomienia na blokadzie ekranu."</string>
|
||||
<string name="full_screen_intent_banner_title">"Popraw jakość swoich rozmów"</string>
|
||||
<string name="screen_home_tab_chats">"Wszystkie czaty"</string>
|
||||
<string name="screen_home_tab_spaces">"Przestrzenie"</string>
|
||||
<string name="screen_invites_decline_chat_message">"Czy na pewno chcesz odrzucić zaproszenie dołączenia do %1$s?"</string>
|
||||
<string name="screen_invites_decline_chat_title">"Odrzuć zaproszenie"</string>
|
||||
<string name="screen_invites_decline_direct_chat_message">"Czy na pewno chcesz odrzucić rozmowę prywatną z %1$s?"</string>
|
||||
@@ -32,6 +35,7 @@ Na razie możesz wyczyścić filtry, aby zobaczyć pozostałe czaty"</string>
|
||||
<string name="screen_roomlist_filter_invites">"Zaproszenia"</string>
|
||||
<string name="screen_roomlist_filter_invites_empty_state_title">"Nie masz żadnych oczekujących zaproszeń."</string>
|
||||
<string name="screen_roomlist_filter_low_priority">"Niski priorytet"</string>
|
||||
<string name="screen_roomlist_filter_low_priority_empty_state_title">"Nie masz jeszcze żadnych czatów o niskim priorytecie"</string>
|
||||
<string name="screen_roomlist_filter_mixed_empty_state_subtitle">"Wyczyść filtry, aby zobaczyć pozostałe czaty"</string>
|
||||
<string name="screen_roomlist_filter_mixed_empty_state_title">"Brak czatów dla podanych kryteriów"</string>
|
||||
<string name="screen_roomlist_filter_people">"Osoby"</string>
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
<string name="banner_battery_optimization_content_android">"停用此應用程式的電池最佳化,才能確保收到所有通知。"</string>
|
||||
<string name="banner_battery_optimization_submit_android">"停用最佳化"</string>
|
||||
<string name="banner_battery_optimization_title_android">"沒收到通知?"</string>
|
||||
<string name="banner_new_sound_message">"您的通知提示音已更新,更清晰、更快、更不易分心。"</string>
|
||||
<string name="banner_new_sound_title">"我們已更新您的音效設定"</string>
|
||||
<string name="banner_set_up_recovery_content">"若您遺失了所有現有裝置,則請使用復原金鑰以救援您的密碼學身份與訊息歷史紀錄。"</string>
|
||||
<string name="banner_set_up_recovery_submit">"設定復原"</string>
|
||||
<string name="banner_set_up_recovery_title">"設定備援以保護您的帳號"</string>
|
||||
|
||||
@@ -39,8 +39,6 @@ import io.element.android.libraries.core.meta.BuildMeta
|
||||
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.RoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.exception.ClientException
|
||||
import io.element.android.libraries.matrix.api.exception.ErrorKind
|
||||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||
import io.element.android.libraries.matrix.api.room.RoomInfo
|
||||
import io.element.android.libraries.matrix.api.room.RoomMembershipDetails
|
||||
@@ -141,11 +139,7 @@ class JoinRoomPresenter(
|
||||
preview.previewInfo.toContentState(membershipDetails)
|
||||
},
|
||||
onFailure = { throwable ->
|
||||
if (throwable is ClientException.MatrixApi && (throwable.kind == ErrorKind.NotFound || throwable.kind == ErrorKind.Forbidden)) {
|
||||
ContentState.UnknownRoom
|
||||
} else {
|
||||
ContentState.Failure(throwable)
|
||||
}
|
||||
ContentState.UnknownRoom
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
internal const val MAX_KNOCK_MESSAGE_LENGTH = 500
|
||||
|
||||
@Immutable
|
||||
data class JoinRoomState(
|
||||
val roomIdOrAlias: RoomIdOrAlias,
|
||||
val contentState: ContentState,
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<string name="screen_join_room_invite_required_message">"Du har brug for en invitation for at deltage"</string>
|
||||
<string name="screen_join_room_invited_by">"Inviteret af"</string>
|
||||
<string name="screen_join_room_join_action">"Deltag"</string>
|
||||
<string name="screen_join_room_join_restricted_message">"Du skal muligvis være inviteret eller være medlem af en klynge for at deltage."</string>
|
||||
<string name="screen_join_room_join_restricted_message">"Du skal muligvis være inviteret eller være medlem af en gruppe for at deltage."</string>
|
||||
<string name="screen_join_room_knock_action">"Send anmodning om at deltage"</string>
|
||||
<string name="screen_join_room_knock_message_characters_count">"Tilladte tegn %1$d af %2$d"</string>
|
||||
<string name="screen_join_room_knock_message_description">"Besked (valgfrit)"</string>
|
||||
@@ -25,8 +25,8 @@
|
||||
<string name="screen_join_room_knock_sent_title">"Anmodning om at deltage sendt"</string>
|
||||
<string name="screen_join_room_loading_alert_message">"Vi kunne ikke forhåndsvise rummet. Dette kan skyldes netværks- eller serverproblemer."</string>
|
||||
<string name="screen_join_room_loading_alert_title">"Vi kunne ikke forhåndsvise rummet"</string>
|
||||
<string name="screen_join_room_space_not_supported_description">"%1$s understøtter ikke klynger endnu. Du kan få adgang til klynger på nettet."</string>
|
||||
<string name="screen_join_room_space_not_supported_title">"Klynger er ikke understøttet endnu"</string>
|
||||
<string name="screen_join_room_space_not_supported_description">"%1$s understøtter ikke grupper endnu. Du kan få adgang til grupper på nettet."</string>
|
||||
<string name="screen_join_room_space_not_supported_title">"Grupper er ikke understøttet endnu"</string>
|
||||
<string name="screen_join_room_subtitle_knock">"Klik på knappen nedenfor, og en rumadministrator vil blive underrettet. Du kan deltage i samtalen, når din anmodning er godkendt."</string>
|
||||
<string name="screen_join_room_subtitle_no_preview">"Du skal være medlem af dette rum for at kunne se meddelelseshistorikken."</string>
|
||||
<string name="screen_join_room_title_knock">"Vil du deltage i dette rum?"</string>
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
<string name="screen_join_room_decline_and_block_alert_message">"Kas sa oled kindel, et soovid keelduda kutsest sellesse jututuppa? Samaga kaob kasutajal %1$s võimalus sinuga suhelda ja saata sulle jututubade kutseid."</string>
|
||||
<string name="screen_join_room_decline_and_block_alert_title">"Keeldu kutsest ja blokeeri"</string>
|
||||
<string name="screen_join_room_decline_and_block_button_title">"Keeldu ja blokeeri"</string>
|
||||
<string name="screen_join_room_fail_message">"Jututoaga liitumine ei õnnestunud."</string>
|
||||
<string name="screen_join_room_fail_reason">"Ligipääs siia jututuppa on võimalik vaid kutse alusel või kehtivad siin kogukonnakohased piirangud."</string>
|
||||
<string name="screen_join_room_fail_message">"Jututoaga liitumine ei õnnestunud"</string>
|
||||
<string name="screen_join_room_fail_reason">"Ligipääs siia on võimalik vaid kutse alusel või siin kehtivad ligipääsupiirangud."</string>
|
||||
<string name="screen_join_room_forget_action">"Unusta see jututuba"</string>
|
||||
<string name="screen_join_room_invite_required_message">"Selle jututoaga liitumiseks vajad sa kutset"</string>
|
||||
<string name="screen_join_room_invited_by">"Kutsuja"</string>
|
||||
|
||||
@@ -9,10 +9,12 @@
|
||||
<string name="screen_join_room_decline_and_block_alert_confirmation">"بله. رد و انسداد"</string>
|
||||
<string name="screen_join_room_decline_and_block_alert_title">"رد دعوت و انسداد"</string>
|
||||
<string name="screen_join_room_decline_and_block_button_title">"رد و انسداد"</string>
|
||||
<string name="screen_join_room_fail_message">"پیوستن به اتاق شکست خورد."</string>
|
||||
<string name="screen_join_room_fail_message">"پیوستن شکست خورد"</string>
|
||||
<string name="screen_join_room_forget_action">"فراموشی این اتاق"</string>
|
||||
<string name="screen_join_room_invite_required_message">"برای پیوستن به این اتاق نیاز به دعوت دارید"</string>
|
||||
<string name="screen_join_room_invited_by">"دعوت شده از سوی"</string>
|
||||
<string name="screen_join_room_join_action">"پیوستن"</string>
|
||||
<string name="screen_join_room_join_restricted_message">"برای پیوستن به فضا باید دعوت شده باشید."</string>
|
||||
<string name="screen_join_room_knock_action">"در زدن برای پیوستن"</string>
|
||||
<string name="screen_join_room_knock_message_description">"پیام (اختیاری)"</string>
|
||||
<string name="screen_join_room_knock_sent_title">"درخواست پیوستن فرستاده شد"</string>
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
<string name="screen_join_room_fail_reason">"Sinun on joko saatava kutsu liittyäksesi tai pääsyyn voi olla rajoituksia."</string>
|
||||
<string name="screen_join_room_forget_action">"Unohda"</string>
|
||||
<string name="screen_join_room_invite_required_message">"Tarvitset kutsun liittyäksesi"</string>
|
||||
<string name="screen_join_room_invited_by">"Kutsuja"</string>
|
||||
<string name="screen_join_room_join_action">"Liity"</string>
|
||||
<string name="screen_join_room_join_restricted_message">"Saatat tarvita kutsun tai olla tilan jäsen, jotta voit liittyä."</string>
|
||||
<string name="screen_join_room_knock_action">"Lähetä liittymispyyntö"</string>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_join_room_ban_by_message">"%1$s kitiltotta a szobából."</string>
|
||||
<string name="screen_join_room_ban_message">"Kitiltották ebből a szobából"</string>
|
||||
<string name="screen_join_room_ban_message">"Kitiltották"</string>
|
||||
<string name="screen_join_room_ban_reason">"Ok: %1$s."</string>
|
||||
<string name="screen_join_room_cancel_knock_action">"Kérés visszavonása"</string>
|
||||
<string name="screen_join_room_cancel_knock_alert_confirmation">"Igen, visszavonás"</string>
|
||||
@@ -11,10 +11,10 @@
|
||||
<string name="screen_join_room_decline_and_block_alert_message">"Biztos, hogy elutasítja a meghívást, hogy csatlakozzon ehhez a szobához? Ez azt is megakadályozza, hogy %1$s kapcsolatba lépjen Önnel, vagy szobákba hívja."</string>
|
||||
<string name="screen_join_room_decline_and_block_alert_title">"Meghívó elutasítása és blokkolás"</string>
|
||||
<string name="screen_join_room_decline_and_block_button_title">"Elutasítás és letiltás"</string>
|
||||
<string name="screen_join_room_fail_message">"A szobához való csatlakozás sikertelen."</string>
|
||||
<string name="screen_join_room_fail_reason">"Ebbe a szobába csak meghívóval vagy tértagsággal lehet belépni."</string>
|
||||
<string name="screen_join_room_forget_action">"Szoba elfelejtése"</string>
|
||||
<string name="screen_join_room_invite_required_message">"Meghívóra van szüksége ahhoz, hogy csatlakozzon ehhez a szobához"</string>
|
||||
<string name="screen_join_room_fail_message">"A csatlakozás sikertelen"</string>
|
||||
<string name="screen_join_room_fail_reason">"Csatlakozáshoz meghívóra van szükség, vagy lehet, hogy korlátozva van a hozzáférés."</string>
|
||||
<string name="screen_join_room_forget_action">"Elfelejt"</string>
|
||||
<string name="screen_join_room_invite_required_message">"A csatlakozáshoz meghívóra van szükség."</string>
|
||||
<string name="screen_join_room_invited_by">"Meghívta:"</string>
|
||||
<string name="screen_join_room_join_action">"Csatlakozás"</string>
|
||||
<string name="screen_join_room_join_restricted_message">"A csatlakozáshoz meghívásra vagy tértagságra lehet szüksége."</string>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_join_room_ban_by_message">"Zostałeś zbanowany z tego pokoju przez %1$s."</string>
|
||||
<string name="screen_join_room_ban_message">"Zostałeś zbanowany z tego pokoju"</string>
|
||||
<string name="screen_join_room_ban_by_message">"Zostałeś zbanowany przez %1$s ."</string>
|
||||
<string name="screen_join_room_ban_message">"Zostałeś zbanowany"</string>
|
||||
<string name="screen_join_room_ban_reason">"Powód: %1$s."</string>
|
||||
<string name="screen_join_room_cancel_knock_action">"Anuluj prośbę"</string>
|
||||
<string name="screen_join_room_cancel_knock_alert_confirmation">"Tak, anuluj"</string>
|
||||
@@ -11,10 +11,11 @@
|
||||
<string name="screen_join_room_decline_and_block_alert_message">"Czy na pewno chcesz odrzucić zaproszenie dołączenia do tego pokoju? %1$s nie będzie mógł się również z Tobą skontaktować, ani zaprosić Cię do pokoju."</string>
|
||||
<string name="screen_join_room_decline_and_block_alert_title">"Odrzuć zaproszenie i zablokuj"</string>
|
||||
<string name="screen_join_room_decline_and_block_button_title">"Odrzuć i zablokuj"</string>
|
||||
<string name="screen_join_room_fail_message">"Nie udało się dołączyć do pokoju."</string>
|
||||
<string name="screen_join_room_fail_reason">"Ten pokój wymaga zaproszenia lub jest ograniczony z poziomu przestrzeni."</string>
|
||||
<string name="screen_join_room_forget_action">"Zapomnij o tym pokoju"</string>
|
||||
<string name="screen_join_room_invite_required_message">"Potrzebujesz zaproszenia, aby dołączyć do tego pokoju"</string>
|
||||
<string name="screen_join_room_fail_message">"Nie udało się dołączyć do pokoju"</string>
|
||||
<string name="screen_join_room_fail_reason">"Ten pokój wymaga zaproszenia lub dołączanie zostało ograniczone."</string>
|
||||
<string name="screen_join_room_forget_action">"Zapomnij"</string>
|
||||
<string name="screen_join_room_invite_required_message">"Aby dołączyć, potrzebujesz zaproszenia"</string>
|
||||
<string name="screen_join_room_invited_by">"Zaproszony przez"</string>
|
||||
<string name="screen_join_room_join_action">"Dołącz"</string>
|
||||
<string name="screen_join_room_join_restricted_message">"Aby dołączyć, musisz uzyskać zaproszenie lub być członkiem danej przestrzeni."</string>
|
||||
<string name="screen_join_room_knock_action">"Wyślij prośbę o dołączenie"</string>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_join_room_ban_by_message">"Foste banido desta sala por %1$s."</string>
|
||||
<string name="screen_join_room_ban_message">"Foste banido desta sala."</string>
|
||||
<string name="screen_join_room_ban_by_message">"Foste banido(a) por %1$s."</string>
|
||||
<string name="screen_join_room_ban_message">"Foste banido(a)"</string>
|
||||
<string name="screen_join_room_ban_reason">"Razão: %1$s."</string>
|
||||
<string name="screen_join_room_cancel_knock_action">"Cancelar pedido"</string>
|
||||
<string name="screen_join_room_cancel_knock_alert_confirmation">"Sim, cancelar"</string>
|
||||
@@ -11,10 +11,10 @@
|
||||
<string name="screen_join_room_decline_and_block_alert_message">"Tens a certeza de que queres recusar o convite para entrar nesta sala? Isto também evitará que %1$s te contacte ou te convide para salas."</string>
|
||||
<string name="screen_join_room_decline_and_block_alert_title">"Recusar convite & bloquear"</string>
|
||||
<string name="screen_join_room_decline_and_block_button_title">"Recusar e bloquear"</string>
|
||||
<string name="screen_join_room_fail_message">"Falha ao entrar na sala."</string>
|
||||
<string name="screen_join_room_fail_reason">"A entrada nesta sala ou está limitada a convites ou a alguma configuração de espaço."</string>
|
||||
<string name="screen_join_room_forget_action">"Esquecer esta sala"</string>
|
||||
<string name="screen_join_room_invite_required_message">"Precisas de um convite para entrares nesta sala"</string>
|
||||
<string name="screen_join_room_fail_message">"Falha ao entrar"</string>
|
||||
<string name="screen_join_room_fail_reason">"A entrada pode estar limitada a convites ou pode haver uma outra limitação de acesso."</string>
|
||||
<string name="screen_join_room_forget_action">"Esquecer"</string>
|
||||
<string name="screen_join_room_invite_required_message">"Precisas de um convite para entrares"</string>
|
||||
<string name="screen_join_room_invited_by">"Convidado por"</string>
|
||||
<string name="screen_join_room_join_action">"Entrar"</string>
|
||||
<string name="screen_join_room_join_restricted_message">"Podes ter que ser convidado ou pertenceres a um espaço para poderes entrar."</string>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_join_room_ban_by_message">"您被 %1$s 禁止進入此聊天室。"</string>
|
||||
<string name="screen_join_room_ban_message">"您被禁止進入此聊天室"</string>
|
||||
<string name="screen_join_room_ban_by_message">"您被 %1$s 禁止。"</string>
|
||||
<string name="screen_join_room_ban_message">"您被禁止了"</string>
|
||||
<string name="screen_join_room_ban_reason">"理由:%1$s。"</string>
|
||||
<string name="screen_join_room_cancel_knock_action">"取消請求"</string>
|
||||
<string name="screen_join_room_cancel_knock_alert_confirmation">"是的,取消"</string>
|
||||
@@ -11,10 +11,11 @@
|
||||
<string name="screen_join_room_decline_and_block_alert_message">"您確定要拒絕加入此聊天室的邀請嗎?這也會防止 %1$s 聯絡您或邀請您加入聊天室。"</string>
|
||||
<string name="screen_join_room_decline_and_block_alert_title">"拒絕邀請並封鎖"</string>
|
||||
<string name="screen_join_room_decline_and_block_button_title">"拒絕並封鎖"</string>
|
||||
<string name="screen_join_room_fail_message">"加入聊天室失敗。"</string>
|
||||
<string name="screen_join_room_fail_reason">"此聊天室僅有受邀者才能進入,或是在空間層級有存取限制。"</string>
|
||||
<string name="screen_join_room_forget_action">"忘記此聊天室"</string>
|
||||
<string name="screen_join_room_invite_required_message">"您需要獲得邀請才能加入此聊天室"</string>
|
||||
<string name="screen_join_room_fail_message">"加入失敗。"</string>
|
||||
<string name="screen_join_room_fail_reason">"您必須獲得邀請才能加入,或者可能存在存取限制。"</string>
|
||||
<string name="screen_join_room_forget_action">"忘記"</string>
|
||||
<string name="screen_join_room_invite_required_message">"您需要獲得邀請才能加入"</string>
|
||||
<string name="screen_join_room_invited_by">"邀請者"</string>
|
||||
<string name="screen_join_room_join_action">"加入"</string>
|
||||
<string name="screen_join_room_join_restricted_message">"您可能需要被邀請成為空間的成員才能加入。"</string>
|
||||
<string name="screen_join_room_knock_action">"傳送加入請求"</string>
|
||||
|
||||
@@ -1193,46 +1193,8 @@ class JoinRoomPresenterTest {
|
||||
skipItems(1)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.contentState).isEqualTo(
|
||||
ContentState.Failure(error = AN_EXCEPTION)
|
||||
ContentState.UnknownRoom
|
||||
)
|
||||
state.eventSink(JoinRoomEvents.RetryFetchingContent)
|
||||
}
|
||||
skipItems(1)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.contentState).isEqualTo(ContentState.Loading)
|
||||
}
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.contentState).isEqualTo(
|
||||
ContentState.Failure(error = AN_EXCEPTION)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - when room is not known RoomPreview is loaded with error - dismiss`() = runTest {
|
||||
val client = FakeMatrixClient(
|
||||
getNotJoinedRoomResult = { _, _ ->
|
||||
Result.failure(AN_EXCEPTION)
|
||||
},
|
||||
spaceService = FakeSpaceService(
|
||||
spaceRoomListResult = { FakeSpaceRoomList() },
|
||||
),
|
||||
)
|
||||
val presenter = createJoinRoomPresenter(
|
||||
matrixClient = client
|
||||
)
|
||||
presenter.test {
|
||||
skipItems(1)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.contentState).isEqualTo(
|
||||
ContentState.Failure(error = AN_EXCEPTION)
|
||||
)
|
||||
state.eventSink(JoinRoomEvents.DismissErrorAndHideContent)
|
||||
}
|
||||
skipItems(1)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.contentState).isEqualTo(ContentState.Dismissing)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
|
||||
package io.element.android.features.leaveroom.api
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
|
||||
@Immutable
|
||||
interface LeaveRoomState {
|
||||
val eventSink: (LeaveRoomEvent) -> Unit
|
||||
}
|
||||
|
||||
@@ -2,5 +2,7 @@
|
||||
<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_select_new_owner_action">"گزینش مالکان"</string>
|
||||
<string name="leave_room_alert_select_new_owner_title">"انتقال مالکیت"</string>
|
||||
<string name="leave_room_alert_subtitle">"مطمئنید که میخواهید این اتاق را ترک کنید؟"</string>
|
||||
</resources>
|
||||
|
||||
@@ -24,14 +24,6 @@
|
||||
<string name="screen_app_lock_setup_pin_mismatch_dialog_title">"پینها مطابق نیستند"</string>
|
||||
<string name="screen_app_lock_signout_alert_message">"برای ادامه باید دوباره وارد شده و پینی جدید ایجاد کنید"</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">"پین اشتباه است. شما %1$d شانس دیگر دارید"</item>
|
||||
<item quantity="other">"پین اشتباه است. شما %1$d شانس دیگر دارید"</item>
|
||||
</plurals>
|
||||
<string name="screen_app_lock_use_biometric_android">"استفاده از زیستسنجی"</string>
|
||||
<string name="screen_app_lock_use_pin_android">"استفاده از پین"</string>
|
||||
<string name="screen_signout_in_progress_dialog_content">"خارج شدن…"</string>
|
||||
|
||||
@@ -17,6 +17,7 @@ import io.element.android.features.login.impl.accountprovider.AccountProvider
|
||||
import io.element.android.features.login.impl.changeserver.ChangeServerState
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.core.uri.ensureProtocol
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
@Inject
|
||||
class ChangeAccountProviderPresenter(
|
||||
@@ -39,6 +40,7 @@ class ChangeAccountProviderPresenter(
|
||||
isValid = true,
|
||||
)
|
||||
}
|
||||
.toImmutableList()
|
||||
}
|
||||
|
||||
val canSearchForAccountProviders = remember {
|
||||
|
||||
@@ -9,10 +9,10 @@ package io.element.android.features.login.impl.screens.changeaccountprovider
|
||||
|
||||
import io.element.android.features.login.impl.accountprovider.AccountProvider
|
||||
import io.element.android.features.login.impl.changeserver.ChangeServerState
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
// Do not use default value, so no member get forgotten in the presenters.
|
||||
data class ChangeAccountProviderState(
|
||||
val accountProviders: List<AccountProvider>,
|
||||
val accountProviders: ImmutableList<AccountProvider>,
|
||||
val canSearchForAccountProviders: Boolean,
|
||||
val changeServerState: ChangeServerState,
|
||||
)
|
||||
|
||||
@@ -12,6 +12,7 @@ import io.element.android.features.login.impl.accountprovider.AccountProvider
|
||||
import io.element.android.features.login.impl.accountprovider.anAccountProvider
|
||||
import io.element.android.features.login.impl.changeserver.ChangeServerState
|
||||
import io.element.android.features.login.impl.changeserver.aChangeServerState
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
open class ChangeAccountProviderStateProvider : PreviewParameterProvider<ChangeAccountProviderState> {
|
||||
override val values: Sequence<ChangeAccountProviderState>
|
||||
@@ -29,7 +30,7 @@ fun aChangeAccountProviderState(
|
||||
canSearchForAccountProviders: Boolean = true,
|
||||
changeServerState: ChangeServerState = aChangeServerState(),
|
||||
) = ChangeAccountProviderState(
|
||||
accountProviders = accountProviders,
|
||||
accountProviders = accountProviders.toImmutableList(),
|
||||
canSearchForAccountProviders = canSearchForAccountProviders,
|
||||
changeServerState = changeServerState,
|
||||
)
|
||||
|
||||
@@ -21,6 +21,7 @@ import io.element.android.features.login.impl.login.LoginHelper
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.core.uri.ensureProtocol
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
@Inject
|
||||
class ChooseAccountProviderPresenter(
|
||||
@@ -69,6 +70,7 @@ class ChooseAccountProviderPresenter(
|
||||
isValid = true,
|
||||
)
|
||||
}
|
||||
.toImmutableList()
|
||||
}
|
||||
|
||||
return ChooseAccountProviderState(
|
||||
|
||||
@@ -10,10 +10,10 @@ package io.element.android.features.login.impl.screens.chooseaccountprovider
|
||||
import io.element.android.features.login.impl.accountprovider.AccountProvider
|
||||
import io.element.android.features.login.impl.login.LoginMode
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
// Do not use default value, so no member get forgotten in the presenters.
|
||||
data class ChooseAccountProviderState(
|
||||
val accountProviders: List<AccountProvider>,
|
||||
val accountProviders: ImmutableList<AccountProvider>,
|
||||
val selectedAccountProvider: AccountProvider?,
|
||||
val loginMode: AsyncData<LoginMode>,
|
||||
val eventSink: (ChooseAccountProviderEvents) -> Unit,
|
||||
|
||||
@@ -12,6 +12,7 @@ import io.element.android.features.login.impl.accountprovider.AccountProvider
|
||||
import io.element.android.features.login.impl.accountprovider.anAccountProvider
|
||||
import io.element.android.features.login.impl.login.LoginMode
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
open class ChooseAccountProviderStateProvider : PreviewParameterProvider<ChooseAccountProviderState> {
|
||||
private val server1 = anAccountProvider(
|
||||
@@ -70,7 +71,7 @@ fun aChooseAccountProviderState(
|
||||
loginMode: AsyncData<LoginMode> = AsyncData.Uninitialized,
|
||||
eventSink: (ChooseAccountProviderEvents) -> Unit = {},
|
||||
) = ChooseAccountProviderState(
|
||||
accountProviders = accountProviders,
|
||||
accountProviders = accountProviders.toImmutableList(),
|
||||
selectedAccountProvider = selectedAccountProvider,
|
||||
loginMode = loginMode,
|
||||
eventSink = eventSink,
|
||||
|
||||
@@ -11,7 +11,6 @@ import io.element.android.features.login.impl.accountprovider.AccountProvider
|
||||
import io.element.android.features.login.impl.login.LoginMode
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
|
||||
// Do not use default value, so no member get forgotten in the presenters.
|
||||
data class ConfirmAccountProviderState(
|
||||
val accountProvider: AccountProvider,
|
||||
val isAccountCreation: Boolean,
|
||||
|
||||
@@ -11,8 +11,8 @@ import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import dev.zacsweers.metro.Inject
|
||||
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
|
||||
import io.element.android.libraries.androidutils.json.JsonProvider
|
||||
import io.element.android.libraries.matrix.api.auth.external.ExternalSession
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
interface MessageParser {
|
||||
/**
|
||||
@@ -26,10 +26,10 @@ interface MessageParser {
|
||||
@Inject
|
||||
class DefaultMessageParser(
|
||||
private val accountProviderDataSource: AccountProviderDataSource,
|
||||
private val json: JsonProvider,
|
||||
) : MessageParser {
|
||||
override fun parse(message: String): ExternalSession {
|
||||
val parser = Json { ignoreUnknownKeys = true }
|
||||
val response = parser.decodeFromString(MobileRegistrationResponse.serializer(), message)
|
||||
val response = json().decodeFromString(MobileRegistrationResponse.serializer(), message)
|
||||
val userId = response.userId ?: error("No user ID in response")
|
||||
val homeServer = response.homeServer ?: accountProviderDataSource.flow.value.url
|
||||
val accessToken = response.accessToken ?: error("No access token in response")
|
||||
|
||||
@@ -11,7 +11,6 @@ import io.element.android.features.login.impl.changeserver.ChangeServerState
|
||||
import io.element.android.features.login.impl.resolver.HomeserverData
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
|
||||
// Do not use default value, so no member get forgotten in the presenters.
|
||||
data class SearchAccountProviderState(
|
||||
val userInput: String,
|
||||
val userInputResult: AsyncData<List<HomeserverData>>,
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_qr_code_login_connection_note_secure_state_description">"A secure connection could not be made to the new device. Your existing linked devices are still safe and you don\'t need to worry about them."</string>
|
||||
</resources>
|
||||
@@ -13,6 +13,7 @@
|
||||
<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_element_pro_required_action_android">"پلی گپگل"</string>
|
||||
<string name="screen_change_server_error_invalid_homeserver">"ما نتوانستیم به این کارساز خانگی برسیم. لطفاً بررسی کنید که URL کارساز اصلی را به درستی وارد کرده اید. اگر URL صحیح است، برای کمک بیشتر با مدیر کارساز خانگی خود تماس بگیرید."</string>
|
||||
<string name="screen_change_server_form_header">"نشانی کارساز خانگی"</string>
|
||||
<string name="screen_change_server_form_notice">"ورود نشانی دامنه."</string>
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
<string name="screen_change_account_provider_other">"Inne"</string>
|
||||
<string name="screen_change_account_provider_subtitle">"Użyj innego dostawcy konta, takiego jak własny serwer lub konta służbowego."</string>
|
||||
<string name="screen_change_account_provider_title">"Zmień dostawcę konta"</string>
|
||||
<string name="screen_change_server_error_element_pro_required_action_android">"Google Play"</string>
|
||||
<string name="screen_change_server_error_element_pro_required_message">"Wymagana jest aplikacja Element Pro na %1$s. Znajdziesz ją w sklepie z aplikacjami."</string>
|
||||
<string name="screen_change_server_error_element_pro_required_title">"Wymagany jest Element Pro"</string>
|
||||
<string name="screen_change_server_error_invalid_homeserver">"Nie mogliśmy połączyć się z tym serwerem domowym. Sprawdź, czy adres URL serwera został wprowadzony poprawnie. Jeśli adres URL jest poprawny, skontaktuj się z administratorem serwera w celu uzyskania dalszej pomocy."</string>
|
||||
|
||||
@@ -11,6 +11,7 @@ import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.appconfig.AuthenticationConfig
|
||||
import io.element.android.features.enterprise.test.FakeEnterpriseService
|
||||
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
|
||||
import io.element.android.libraries.androidutils.json.DefaultJsonProvider
|
||||
import io.element.android.libraries.matrix.api.auth.external.ExternalSession
|
||||
import kotlinx.serialization.SerializationException
|
||||
import org.junit.Assert.assertThrows
|
||||
@@ -68,7 +69,8 @@ class DefaultMessageParserTest {
|
||||
|
||||
private fun createDefaultMessageParser(): DefaultMessageParser {
|
||||
return DefaultMessageParser(
|
||||
AccountProviderDataSource(FakeEnterpriseService())
|
||||
accountProviderDataSource = AccountProviderDataSource(FakeEnterpriseService()),
|
||||
json = DefaultJsonProvider(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,10 +34,12 @@ dependencies {
|
||||
implementation(projects.libraries.testtags)
|
||||
implementation(projects.libraries.uiStrings)
|
||||
implementation(projects.libraries.dateformatter.api)
|
||||
implementation(projects.libraries.workmanager.api)
|
||||
api(projects.features.logout.api)
|
||||
|
||||
testCommonDependencies(libs, true)
|
||||
testImplementation(projects.libraries.matrix.test)
|
||||
testImplementation(projects.libraries.featureflag.test)
|
||||
testImplementation(projects.libraries.sessionStorage.test)
|
||||
testImplementation(projects.libraries.workmanager.test)
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.encryption.BackupState
|
||||
import io.element.android.libraries.matrix.api.encryption.BackupUploadState
|
||||
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
||||
import io.element.android.libraries.workmanager.api.WorkManagerScheduler
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -34,6 +35,7 @@ import kotlinx.coroutines.launch
|
||||
class LogoutPresenter(
|
||||
private val matrixClient: MatrixClient,
|
||||
private val encryptionService: EncryptionService,
|
||||
private val workManagerScheduler: WorkManagerScheduler,
|
||||
) : Presenter<LogoutState> {
|
||||
@Composable
|
||||
override fun present(): LogoutState {
|
||||
@@ -109,6 +111,9 @@ class LogoutPresenter(
|
||||
ignoreSdkError: Boolean,
|
||||
) = launch {
|
||||
suspend {
|
||||
// Cancel any pending work (e.g. notification sync)
|
||||
workManagerScheduler.cancel(matrixClient.sessionId)
|
||||
|
||||
matrixClient.logout(userInitiated = true, ignoreSdkError)
|
||||
}.runCatchingUpdatingState(logoutAction)
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_signout_key_backup_offline_subtitle">"Your messages were still being backed up when you went offline. Reconnect so that your messages can be backed up before signing out."</string>
|
||||
<string name="screen_signout_key_backup_offline_title">"Your messages are still being backed up"</string>
|
||||
<string name="screen_signout_key_backup_ongoing_title">"Your messages are still being backed up"</string>
|
||||
<string name="screen_signout_recovery_disabled_title">"Backup not set up"</string>
|
||||
<string name="screen_signout_save_recovery_key_title">"Have you saved your backup password?"</string>
|
||||
</resources>
|
||||
@@ -14,6 +14,7 @@ import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.encryption.BackupState
|
||||
import io.element.android.libraries.matrix.api.encryption.BackupUploadState
|
||||
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
||||
@@ -21,7 +22,9 @@ import io.element.android.libraries.matrix.api.encryption.RecoveryState
|
||||
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService
|
||||
import io.element.android.libraries.workmanager.test.FakeWorkManagerScheduler
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.test.runTest
|
||||
@@ -145,7 +148,9 @@ class LogoutPresenterTest {
|
||||
|
||||
@Test
|
||||
fun `present - logout then confirm`() = runTest {
|
||||
val presenter = createLogoutPresenter()
|
||||
val cancelWorkManagerJobsLambda = lambdaRecorder<SessionId, Unit> {}
|
||||
val workManagerScheduler = FakeWorkManagerScheduler(cancelLambda = cancelWorkManagerJobsLambda)
|
||||
val presenter = createLogoutPresenter(workManagerScheduler = workManagerScheduler)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
@@ -158,6 +163,8 @@ class LogoutPresenterTest {
|
||||
assertThat(loadingState.logoutAction).isInstanceOf(AsyncAction.Loading::class.java)
|
||||
val successState = awaitItem()
|
||||
assertThat(successState.logoutAction).isInstanceOf(AsyncAction.Success::class.java)
|
||||
|
||||
cancelWorkManagerJobsLambda.assertions().isCalledOnce()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,7 +237,9 @@ class LogoutPresenterTest {
|
||||
internal fun createLogoutPresenter(
|
||||
matrixClient: MatrixClient = FakeMatrixClient(),
|
||||
encryptionService: EncryptionService = FakeEncryptionService(),
|
||||
workManagerScheduler: FakeWorkManagerScheduler = FakeWorkManagerScheduler(cancelLambda = {}),
|
||||
): LogoutPresenter = LogoutPresenter(
|
||||
matrixClient = matrixClient,
|
||||
encryptionService = encryptionService,
|
||||
workManagerScheduler = workManagerScheduler,
|
||||
)
|
||||
|
||||
@@ -8,15 +8,14 @@
|
||||
package io.element.android.features.messages.api.timeline.voicemessages.composer
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.libraries.designsystem.components.media.createFakeWaveform
|
||||
import io.element.android.libraries.designsystem.components.media.WaveFormSamples
|
||||
import io.element.android.libraries.textcomposer.model.VoiceMessageState
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
open class VoiceMessageComposerStateProvider : PreviewParameterProvider<VoiceMessageComposerState> {
|
||||
override val values: Sequence<VoiceMessageComposerState>
|
||||
get() = sequenceOf(
|
||||
aVoiceMessageComposerState(voiceMessageState = VoiceMessageState.Recording(duration = 61.seconds, levels = aWaveformLevels)),
|
||||
aVoiceMessageComposerState(voiceMessageState = VoiceMessageState.Recording(duration = 61.seconds, levels = WaveFormSamples.allRangeWaveForm)),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -39,7 +38,5 @@ fun aVoiceMessagePreviewState() = VoiceMessageState.Preview(
|
||||
showCursor = false,
|
||||
playbackProgress = 0f,
|
||||
time = 10.seconds,
|
||||
waveform = createFakeWaveform(),
|
||||
waveform = WaveFormSamples.realisticWaveForm,
|
||||
)
|
||||
|
||||
internal var aWaveformLevels = List(100) { it.toFloat() / 100 }.toImmutableList()
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
package io.element.android.features.messages.impl
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import io.element.android.features.messages.api.timeline.voicemessages.composer.VoiceMessageComposerState
|
||||
import io.element.android.features.messages.impl.actionlist.ActionListState
|
||||
import io.element.android.features.messages.impl.crypto.identity.IdentityChangeState
|
||||
@@ -29,7 +28,6 @@ import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
|
||||
import io.element.android.libraries.matrix.api.room.tombstone.SuccessorRoom
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@Immutable
|
||||
data class MessagesState(
|
||||
val roomId: RoomId,
|
||||
val roomName: String?,
|
||||
|
||||
@@ -84,7 +84,6 @@ import io.element.android.libraries.designsystem.text.toAnnotatedString
|
||||
import io.element.android.libraries.designsystem.theme.components.BottomSheetDragHandle
|
||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.utils.HideKeyboardWhenDisposed
|
||||
import io.element.android.libraries.designsystem.utils.KeepScreenOn
|
||||
import io.element.android.libraries.designsystem.utils.OnLifecycleEvent
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarHost
|
||||
@@ -124,8 +123,6 @@ fun MessagesView(
|
||||
|
||||
KeepScreenOn(state.voiceMessageComposerState.keepScreenOn)
|
||||
|
||||
HideKeyboardWhenDisposed()
|
||||
|
||||
val snackbarHostState = rememberSnackbarHostState(snackbarMessage = state.snackbarMessage)
|
||||
|
||||
// This is needed because the composer is inside an AndroidView that can't be affected by the FocusManager in Compose
|
||||
|
||||
@@ -13,7 +13,6 @@ import io.element.android.features.messages.impl.crypto.sendfailure.VerifiedUser
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@Immutable
|
||||
data class ActionListState(
|
||||
val target: Target,
|
||||
val eventSink: (ActionListEvents) -> Unit,
|
||||
|
||||
@@ -9,11 +9,9 @@ package io.element.android.features.messages.impl.actionlist.model
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.runtime.Immutable
|
||||
import io.element.android.libraries.designsystem.icons.CompoundDrawables
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
@Immutable
|
||||
enum class TimelineItemAction(
|
||||
@StringRes val titleRes: Int,
|
||||
@DrawableRes val icon: Int,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user